/**
 * Runtime API for taxonomies
 *
 * Provides functions to query taxonomy definitions and terms.
 */

import { getDb } from "../loader.js";
import type { TaxonomyDef, TaxonomyTerm, TaxonomyTermRow } from "./types.js";

/**
 * Get all taxonomy definitions
 */
export async function getTaxonomyDefs(): Promise<TaxonomyDef[]> {
	const db = await getDb();

	const rows = await db.selectFrom("_emdash_taxonomy_defs").selectAll().execute();

	return rows.map((row) => ({
		id: row.id,
		name: row.name,
		label: row.label,
		labelSingular: row.label_singular ?? undefined,
		hierarchical: row.hierarchical === 1,
		collections: row.collections ? JSON.parse(row.collections) : [],
	}));
}

/**
 * Get a single taxonomy definition by name
 */
export async function getTaxonomyDef(name: string): Promise<TaxonomyDef | null> {
	const db = await getDb();

	const row = await db
		.selectFrom("_emdash_taxonomy_defs")
		.selectAll()
		.where("name", "=", name)
		.executeTakeFirst();

	if (!row) return null;

	return {
		id: row.id,
		name: row.name,
		label: row.label,
		labelSingular: row.label_singular ?? undefined,
		hierarchical: row.hierarchical === 1,
		collections: row.collections ? JSON.parse(row.collections) : [],
	};
}

/**
 * Get all terms for a taxonomy (as tree for hierarchical, flat for tags)
 */
export async function getTaxonomyTerms(taxonomyName: string): Promise<TaxonomyTerm[]> {
	const db = await getDb();

	// Get taxonomy definition to check if hierarchical
	const def = await getTaxonomyDef(taxonomyName);
	if (!def) return [];

	// Get all terms for this taxonomy
	const rows = await db
		.selectFrom("taxonomies")
		.selectAll()
		.where("name", "=", taxonomyName)
		.orderBy("label", "asc")
		.execute();

	// Count entries for each term
	const countsResult = await db
		.selectFrom("content_taxonomies")
		.select(["taxonomy_id"])
		.select((eb) => eb.fn.count<number>("entry_id").as("count"))
		.groupBy("taxonomy_id")
		.execute();

	const counts = new Map<string, number>();
	for (const row of countsResult) {
		counts.set(row.taxonomy_id, row.count);
	}

	const flatTerms: TaxonomyTermRow[] = rows.map((row) => ({
		id: row.id,
		name: row.name,
		slug: row.slug,
		label: row.label,
		parent_id: row.parent_id,
		data: row.data,
	}));

	// If hierarchical, build tree. Otherwise return flat
	if (def.hierarchical) {
		return buildTree(flatTerms, counts);
	}

	return flatTerms.map((term) => ({
		id: term.id,
		name: term.name,
		slug: term.slug,
		label: term.label,
		children: [],
		count: counts.get(term.id) ?? 0,
	}));
}

/**
 * Get a single term by taxonomy and slug
 */
export async function getTerm(taxonomyName: string, slug: string): Promise<TaxonomyTerm | null> {
	const db = await getDb();

	const row = await db
		.selectFrom("taxonomies")
		.selectAll()
		.where("name", "=", taxonomyName)
		.where("slug", "=", slug)
		.executeTakeFirst();

	if (!row) return null;

	// Get entry count
	const countResult = await db
		.selectFrom("content_taxonomies")
		.select((eb) => eb.fn.count<number>("entry_id").as("count"))
		.where("taxonomy_id", "=", row.id)
		.executeTakeFirst();

	const count = countResult?.count ?? 0;

	// Get children if hierarchical
	const childRows = await db
		.selectFrom("taxonomies")
		.selectAll()
		.where("parent_id", "=", row.id)
		.orderBy("label", "asc")
		.execute();

	const children = childRows.map((child) => ({
		id: child.id,
		name: child.name,
		slug: child.slug,
		label: child.label,
		parentId: child.parent_id ?? undefined,
		children: [],
	}));

	return {
		id: row.id,
		name: row.name,
		slug: row.slug,
		label: row.label,
		parentId: row.parent_id ?? undefined,
		description: row.data ? JSON.parse(row.data).description : undefined,
		children,
		count,
	};
}

/**
 * Get terms assigned to an entry
 */
export async function getEntryTerms(
	collection: string,
	entryId: string,
	taxonomyName?: string,
): Promise<TaxonomyTerm[]> {
	const db = await getDb();

	let query = db
		.selectFrom("content_taxonomies")
		.innerJoin("taxonomies", "taxonomies.id", "content_taxonomies.taxonomy_id")
		.selectAll("taxonomies")
		.where("content_taxonomies.collection", "=", collection)
		.where("content_taxonomies.entry_id", "=", entryId);

	if (taxonomyName) {
		query = query.where("taxonomies.name", "=", taxonomyName);
	}

	const rows = await query.execute();

	return rows.map((row) => ({
		id: row.id,
		name: row.name,
		slug: row.slug,
		label: row.label,
		parentId: row.parent_id ?? undefined,
		children: [],
	}));
}

/**
 * Get terms for multiple entries in a single query (batched API)
 *
 * This is more efficient than calling getEntryTerms for each entry
 * when you need terms for a list of entries.
 *
 * @param collection - The collection type (e.g., "posts")
 * @param entryIds - Array of entry IDs
 * @param taxonomyName - The taxonomy name (e.g., "categories")
 * @returns Map from entry ID to array of terms
 */
export async function getTermsForEntries(
	collection: string,
	entryIds: string[],
	taxonomyName: string,
): Promise<Map<string, TaxonomyTerm[]>> {
	const result = new Map<string, TaxonomyTerm[]>();

	// Initialize all entry IDs with empty arrays
	for (const id of entryIds) {
		result.set(id, []);
	}

	if (entryIds.length === 0) {
		return result;
	}

	const db = await getDb();

	const rows = await db
		.selectFrom("content_taxonomies")
		.innerJoin("taxonomies", "taxonomies.id", "content_taxonomies.taxonomy_id")
		.select([
			"content_taxonomies.entry_id",
			"taxonomies.id",
			"taxonomies.name",
			"taxonomies.slug",
			"taxonomies.label",
			"taxonomies.parent_id",
		])
		.where("content_taxonomies.collection", "=", collection)
		.where("content_taxonomies.entry_id", "in", entryIds)
		.where("taxonomies.name", "=", taxonomyName)
		.execute();

	for (const row of rows) {
		const entryId = row.entry_id;
		const term: TaxonomyTerm = {
			id: row.id,
			name: row.name,
			slug: row.slug,
			label: row.label,
			parentId: row.parent_id ?? undefined,
			children: [],
		};

		const terms = result.get(entryId);
		if (terms) {
			terms.push(term);
		}
	}

	return result;
}

/**
 * Get entries by term (wraps getEmDashCollection)
 */
export async function getEntriesByTerm(
	collection: string,
	taxonomyName: string,
	termSlug: string,
): Promise<Array<{ id: string; data: Record<string, unknown> }>> {
	const { getEmDashCollection } = await import("../query.js");

	// Build options as the expected type — getEmDashCollection accepts
	// a generic options object with `where` for filtering by taxonomy
	const options: Record<string, unknown> = {
		where: { [taxonomyName]: termSlug },
	};
	const { entries } = await getEmDashCollection(collection, options);

	return entries;
}

/**
 * Build tree structure from flat terms
 */
function buildTree(flatTerms: TaxonomyTermRow[], counts: Map<string, number>): TaxonomyTerm[] {
	const map = new Map<string, TaxonomyTerm>();
	const roots: TaxonomyTerm[] = [];

	// First pass: create nodes
	for (const term of flatTerms) {
		map.set(term.id, {
			id: term.id,
			name: term.name,
			slug: term.slug,
			label: term.label,
			parentId: term.parent_id ?? undefined,
			description: term.data ? JSON.parse(term.data).description : undefined,
			children: [],
			count: counts.get(term.id) ?? 0,
		});
	}

	// Second pass: build tree
	for (const term of map.values()) {
		if (term.parentId && map.has(term.parentId)) {
			map.get(term.parentId)!.children.push(term);
		} else {
			roots.push(term);
		}
	}

	return roots;
}
