import { readFileSync, writeFileSync } from 'node:fs';
import { dirname, join, extname, basename, relative } from 'node:path';
import jsdoc2md from 'jsdoc-to-markdown';
import { type DeclarationReflection, Application, TSConfigReader } from 'typedoc';
import type { CLIOptions } from '../bin/index.js';
import { Documentation } from './documentation.js';
import type { RootTypes, ChildTypes, CustomDocs } from './interfaces/index.js';

interface CustomFiles {
	files: {
		id?: string;
		name: string;
		path: string;
	}[];
	id?: string;
	name: string;
	path?: string;
}

export async function build({ input, custom: customDocs, root, output, newOutput, typescript }: CLIOptions) {
	let data: (ChildTypes & RootTypes)[] | DeclarationReflection[] = [];
	if (typescript) {
		console.log('Parsing Typescript in source files...');
		const app = await Application.bootstrap({ entryPoints: input }, [new TSConfigReader()]);
		const project = await app.convert();
		if (project) {
			// @ts-expect-error: Types are lost with this method
			data = app.serializer.toObject(project).children!;
			console.log(`${data.length} items parsed.`);
		}
	} else {
		console.log('Parsing JSDocs in source files...');

		data = jsdoc2md.getTemplateDataSync({ files: input }) as (ChildTypes & RootTypes)[];
		console.log(`${data.length} JSDoc items parsed.`);
	}

	const custom: Record<string, CustomDocs> = {};
	if (customDocs) {
		console.log('Loading custom docs files...');
		const customDir = dirname(customDocs);
		const file = readFileSync(customDocs, 'utf8');
		const data = JSON.parse(file) as CustomFiles[];

		for (const category of data) {
			const categoryId = category.id ?? category.name.toLowerCase();
			const dir = join(customDir, category.path ?? categoryId);
			custom[categoryId] = {
				name: category.name || category.id!,
				files: {},
			};

			for (const file of category.files) {
				const fileRootPath = join(dir, file.path);
				const extension = extname(file.path);
				const fileId = file.id ?? basename(file.path, extension);
				const fileData = readFileSync(fileRootPath, 'utf8');
				custom[categoryId]!.files[fileId] = {
					name: file.name,
					type: extension.toLowerCase().replace(/^\./, ''),
					content: fileData,
					path: relative(root, fileRootPath).replaceAll('\\', '/'),
				};
			}
		}

		const fileCount = Object.keys(custom)
			.map((key) => Object.keys(custom[key]!))
			.reduce((prev, content) => prev + content.length, 0);
		const categoryCount = Object.keys(custom).length;
		console.log(
			`${fileCount} custom docs file${fileCount === 1 ? '' : 's'} in ` +
				`${categoryCount} categor${categoryCount === 1 ? 'y' : 'ies'} loaded.`,
		);
	}

	console.log(`Serializing documentation with format version ${Documentation.FORMAT_VERSION}...`);
	const docs = new Documentation(data, { input, custom: customDocs, root, output, typescript }, custom);

	if (output) {
		console.log(`Writing to ${output}...`);
		writeFileSync(output, JSON.stringify(docs.serialize()));
	}

	if (newOutput) {
		console.log(`Writing to ${newOutput}...`);
		writeFileSync(newOutput, JSON.stringify(docs.serializeNew()));
	}

	console.log('Done!');
}
