/**
 * Markdown document parser for TRUEREF-0005.
 *
 * Splits Markdown/MDX files into heading-based sections and extracts fenced
 * code blocks as separate code snippets.
 */

import { basename } from 'node:path';
import type { NewSnippet } from '$lib/server/db/schema.js';
import { estimateTokens, chunkText, MAX_TOKENS, OVERLAP_TOKENS, MIN_CONTENT_LENGTH } from './chunker.js';

// ---------------------------------------------------------------------------
// Internal types
// ---------------------------------------------------------------------------

interface CodeBlock {
	language: string;
	code: string;
}

interface MarkdownSection {
	/** Heading stack at this point, e.g. ["Getting Started", "Installation"] */
	headings: string[];
	/** Prose text content (code blocks stripped out) */
	content: string;
	/** Fenced code blocks found within this section */
	codeBlocks: CodeBlock[];
}

// ---------------------------------------------------------------------------
// Section splitting
// ---------------------------------------------------------------------------

/**
 * Split the full Markdown source into sections delimited by ATX headings
 * (# … ####).  Code blocks inside headings are extracted separately.
 */
function splitIntoSections(source: string): MarkdownSection[] {
	const lines = source.split('\n');
	const sections: MarkdownSection[] = [];

	// Heading stack: index 0 = H1, 1 = H2, … (we track up to H4)
	const headingStack: string[] = [];

	// Accumulator for the current section
	let textLines: string[] = [];
	const codeBlocks: CodeBlock[] = [];

	// Fenced-code-block tracking
	let inCodeBlock = false;
	let codeFence = '';
	let codeLanguage = '';
	let codeLines: string[] = [];

	function flushSection() {
		sections.push({
			headings: [...headingStack],
			content: textLines.join('\n'),
			codeBlocks: [...codeBlocks]
		});
		textLines = [];
		codeBlocks.length = 0;
	}

	for (const line of lines) {
		// ---- Fenced code block handling ----
		if (!inCodeBlock) {
			const fenceMatch = line.match(/^(`{3,}|~{3,})([\w-]*)/);
			if (fenceMatch) {
				inCodeBlock = true;
				codeFence = fenceMatch[1].charAt(0).repeat(fenceMatch[1].length);
				codeLanguage = fenceMatch[2].trim().toLowerCase();
				codeLines = [];
				continue;
			}
		} else {
			// Check for closing fence (must be same char and at least same length)
			const closingFence = new RegExp(`^${codeFence[0]}{${codeFence.length},}\\s*$`);
			if (closingFence.test(line)) {
				inCodeBlock = false;
				const code = codeLines.join('\n');
				if (code.trim().length >= MIN_CONTENT_LENGTH) {
					codeBlocks.push({ language: codeLanguage, code });
				}
				codeLines = [];
				continue;
			}
			codeLines.push(line);
			continue;
		}

		// ---- Heading detection (ATX only, H1–H4) ----
		const headingMatch = line.match(/^(#{1,4})\s+(.*)/);
		if (headingMatch) {
			// Emit whatever has accumulated before this heading
			flushSection();

			const level = headingMatch[1].length; // 1–4
			const title = headingMatch[2].trim();

			// Trim the stack to the depth above this heading and push the new title
			headingStack.splice(level - 1, headingStack.length - (level - 1), title);
			continue;
		}

		// ---- Ordinary prose line ----
		textLines.push(line);
	}

	// Flush any trailing content (unclosed fence treated as prose)
	if (inCodeBlock) {
		// Treat remaining code lines as prose if the fence was never closed
		textLines.push(...codeLines);
	}
	flushSection();

	return sections;
}

// ---------------------------------------------------------------------------
// Public parser
// ---------------------------------------------------------------------------

type RawSnippet = Omit<NewSnippet, 'id' | 'repositoryId' | 'documentId' | 'versionId' | 'createdAt'>;

/**
 * Parse a Markdown/MDX file into raw snippets (before IDs and DB fields are
 * attached).
 */
export function parseMarkdown(content: string, filePath: string): RawSnippet[] {
	const sections = splitIntoSections(content);
	const snippets: RawSnippet[] = [];

	for (const section of sections) {
		const breadcrumb = section.headings.join(' > ') || undefined;
		const title = section.headings.at(-1) ?? basename(filePath);

		// ---- Info snippet for prose content ----
		const prose = section.content.trim();
		if (prose.length >= MIN_CONTENT_LENGTH) {
			const chunks = chunkText(prose, MAX_TOKENS, OVERLAP_TOKENS);
			for (const chunk of chunks) {
				snippets.push({
					type: 'info',
					title,
					content: chunk,
					breadcrumb: breadcrumb ?? null,
					language: null,
					tokenCount: estimateTokens(chunk)
				});
			}
		}

		// ---- Code snippets for each fenced code block ----
		for (const block of section.codeBlocks) {
			const code = block.code.trim();
			if (code.length < MIN_CONTENT_LENGTH) continue;

			snippets.push({
				type: 'code',
				title,
				content: code,
				language: block.language || null,
				breadcrumb: breadcrumb ?? null,
				tokenCount: estimateTokens(code)
			});
		}
	}

	return snippets;
}
