/**
 * Response formatters for the context7-compatible REST API.
 *
 * Provides two output shapes for each endpoint:
 *   - JSON  (`type=json`, default): structured data for programmatic consumers.
 *   - Text  (`type=txt`): plain Markdown formatted for direct LLM injection.
 *
 * State mapping (TrueRef → context7):
 *   pending  → initial
 *   indexing → initial
 *   indexed  → finalized
 *   error    → error
 */

import type { Repository, RepositoryVersion, Snippet } from '$lib/types';
import type { LibrarySearchResult } from '$lib/server/search/search.service';
import type { SnippetSearchResult } from '$lib/server/search/search.service';

// ---------------------------------------------------------------------------
// State mapping
// ---------------------------------------------------------------------------

type TrueRefState = 'pending' | 'indexing' | 'indexed' | 'error';
type Context7State = 'initial' | 'finalized' | 'error';

export function mapState(state: TrueRefState): Context7State {
	switch (state) {
		case 'indexed':
			return 'finalized';
		case 'error':
			return 'error';
		default:
			return 'initial';
	}
}

// ---------------------------------------------------------------------------
// CORS headers
// ---------------------------------------------------------------------------

export const CORS_HEADERS = {
	'Access-Control-Allow-Origin': '*',
	'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS',
	'Access-Control-Allow-Headers': 'Content-Type, Authorization'
} as const;

// ---------------------------------------------------------------------------
// /api/v1/libs/search — JSON response shape
// ---------------------------------------------------------------------------

export interface LibrarySearchJsonResult {
	id: string;
	title: string;
	description: string | null;
	branch: string | null;
	lastUpdateDate: string | null;
	state: Context7State;
	totalTokens: number | null;
	totalSnippets: number | null;
	stars: number | null;
	trustScore: number | null;
	benchmarkScore: number | null;
	versions: string[];
	source: string;
}

export interface LibrarySearchJsonResponse {
	results: LibrarySearchJsonResult[];
}

/**
 * Convert internal LibrarySearchResult[] to the context7-compatible JSON body.
 */
export function formatLibrarySearchJson(results: LibrarySearchResult[]): LibrarySearchJsonResponse {
	return {
		results: results.map(({ repository, versions }) =>
			formatSingleLibraryJson(repository, versions)
		)
	};
}

export function formatSingleLibraryJson(
	repository: Repository,
	versions: RepositoryVersion[]
): LibrarySearchJsonResult {
	return {
		id: repository.id,
		title: repository.title,
		description: repository.description ?? null,
		branch: repository.branch ?? null,
		lastUpdateDate: repository.lastIndexedAt ? repository.lastIndexedAt.toISOString() : null,
		state: mapState(repository.state as TrueRefState),
		totalTokens: repository.totalTokens ?? null,
		totalSnippets: repository.totalSnippets ?? null,
		stars: repository.stars ?? null,
		trustScore: repository.trustScore ?? null,
		benchmarkScore: repository.benchmarkScore ?? null,
		versions: versions.map((v) => v.tag),
		source: repository.sourceUrl
	};
}

// ---------------------------------------------------------------------------
// /api/v1/context — JSON response shapes
// ---------------------------------------------------------------------------

export interface CodeListItem {
	language: string;
	code: string;
}

export interface CodeSnippetJson {
	type: 'code';
	title: string | null;
	description: string | null;
	language: string | null;
	codeList: CodeListItem[];
	id: string;
	tokenCount: number | null;
	pageTitle: string | null;
}

export interface InfoSnippetJson {
	type: 'info';
	text: string;
	breadcrumb: string | null;
	pageId: string;
	tokenCount: number | null;
}

export type SnippetJson = CodeSnippetJson | InfoSnippetJson;

export interface ContextJsonResponse {
	snippets: SnippetJson[];
	rules: string[];
	totalTokens: number;
}

/**
 * Convert a ranked list of snippets to the context7-compatible JSON body.
 *
 * @param snippets - Ranked snippet search results (already token-budget trimmed).
 * @param rules    - Rules from `trueref.json` / `repository_configs`.
 */
export function formatContextJson(
	snippets: SnippetSearchResult[],
	rules: string[]
): ContextJsonResponse {
	const mapped: SnippetJson[] = snippets.map(({ snippet }) => {
		if (snippet.type === 'code') {
			const codeSnippet: CodeSnippetJson = {
				type: 'code',
				title: snippet.title ?? null,
				description: snippet.breadcrumb ?? null,
				language: snippet.language ?? null,
				codeList: [
					{
						language: snippet.language ?? '',
						code: snippet.content
					}
				],
				id: snippet.id,
				tokenCount: snippet.tokenCount ?? null,
				pageTitle: extractPageTitle(snippet.breadcrumb)
			};
			return codeSnippet;
		} else {
			const infoSnippet: InfoSnippetJson = {
				type: 'info',
				text: snippet.content,
				breadcrumb: snippet.breadcrumb ?? null,
				pageId: snippet.id,
				tokenCount: snippet.tokenCount ?? null
			};
			return infoSnippet;
		}
	});

	const totalTokens = snippets.reduce((sum, { snippet }) => sum + (snippet.tokenCount ?? 0), 0);

	return {
		snippets: mapped,
		rules,
		totalTokens
	};
}

/**
 * Extract the top-level page title from a breadcrumb string.
 * e.g. "Getting Started > Components" → "Getting Started"
 */
function extractPageTitle(breadcrumb: string | null | undefined): string | null {
	if (!breadcrumb) return null;
	const parts = breadcrumb.split('>');
	return parts[0].trim() || null;
}

// ---------------------------------------------------------------------------
// /api/v1/context — txt response
// ---------------------------------------------------------------------------

/**
 * Format snippets as plain Markdown text suitable for direct LLM injection.
 *
 * @param snippets - Ranked snippet search results (already token-budget trimmed).
 * @param rules    - Rules from `trueref.json` / `repository_configs`.
 */
export function formatContextTxt(snippets: SnippetSearchResult[], rules: string[]): string {
	const parts: string[] = [];

	if (rules.length > 0) {
		parts.push('## Library Rules\n' + rules.map((r) => `- ${r}`).join('\n'));
		parts.push('---');
	}

	for (const { snippet } of snippets) {
		const section: string[] = [];

		if (snippet.type === 'code') {
			if (snippet.title) section.push(`### ${snippet.title}`);
			if (snippet.breadcrumb) section.push(`*${snippet.breadcrumb}*`);
			section.push(`\`\`\`${snippet.language ?? ''}\n${snippet.content}\n\`\`\``);
		} else {
			if (snippet.title) section.push(`### ${snippet.title}`);
			if (snippet.breadcrumb) section.push(`*${snippet.breadcrumb}*`);
			section.push(snippet.content);
		}

		parts.push(section.filter(Boolean).join('\n'));
		parts.push('---');
	}

	// Remove trailing separator
	if (parts.at(-1) === '---') parts.pop();

	return parts.join('\n\n');
}
