// src/kb/KbChunker.ts

const MAX_CHUNK_BYTES = 4096;

/**
 * Chunk content into KB-indexable segments.
 *
 * Rules:
 * 1. Split at markdown headings (##, ###, ####) — each heading starts a new chunk
 * 2. Keep code blocks intact — never split mid-fence
 * 3. Within sections: if bytes > MAX_CHUNK_BYTES, split at paragraph boundary
 * 4. Oversized paragraphs: stride-split at MAX_CHUNK_BYTES
 * Returns non-empty trimmed strings only.
 */
export function chunkContent(content: string): string[] {
  if (!content.trim()) return [];

  const headings: string[] = [];
  const headingRe = /^(#{2,}\s+[^\n]*)/gm;
  let m: RegExpExecArray | null;
  while ((m = headingRe.exec(content)) !== null) headings.push(m[1]!);

  const rawSections = content.split(/^#{2,}\s+/m);

  const sections: string[] = rawSections.map((sec, i) => {
    const prefix = i > 0 && headings[i - 1] ? headings[i - 1]! + "\n" : "";
    return prefix + sec;
  });

  const result: string[] = [];

  for (const section of sections) {
    if (!section.trim()) continue;
    if (Buffer.byteLength(section) <= MAX_CHUNK_BYTES) {
      result.push(section.trim());
      continue;
    }

    // Large section: split at paragraph boundaries preserving code fences
    const paragraphs = splitPreservingFences(section);
    let buf = "";
    for (const para of paragraphs) {
      const candidate = buf ? buf + "\n\n" + para : para;
      if (Buffer.byteLength(candidate) <= MAX_CHUNK_BYTES) {
        buf = candidate;
      } else {
        if (buf) result.push(buf.trim());
        if (Buffer.byteLength(para) > MAX_CHUNK_BYTES) {
          result.push(...strideSplit(para));
        } else {
          buf = para;
        }
      }
    }
    if (buf.trim()) result.push(buf.trim());
  }

  return result.filter(s => s.length > 0);
}

function splitPreservingFences(text: string): string[] {
  const lines = text.split("\n");
  const segments: string[] = [];
  let cur: string[] = [];
  let inFence = false;

  for (const line of lines) {
    if (/^```/.test(line)) inFence = !inFence;
    if (!inFence && line === "" && cur.length > 0) {
      const seg = cur.join("\n");
      if (seg.trim()) segments.push(seg);
      cur = [];
    } else {
      cur.push(line);
    }
  }
  if (cur.length > 0) {
    const seg = cur.join("\n");
    if (seg.trim()) segments.push(seg);
  }
  return segments;
}

function strideSplit(text: string): string[] {
  const buf = Buffer.from(text);
  const parts: string[] = [];
  for (let off = 0; off < buf.length; off += MAX_CHUNK_BYTES) {
    parts.push(buf.subarray(off, off + MAX_CHUNK_BYTES).toString("utf-8").trim());
  }
  return parts.filter(p => p.length > 0);
}
