import type { Heading, PhrasingContent } from "mdast";
import type { Section } from "./libs/sections";
import type { Program } from "estree-jsx";
import type { Plugin } from "unified";
import type { Root } from "mdast";

import { slugifyWithCounter } from "@sindresorhus/slugify";
import { visit } from "unist-util-visit";

type PhrasingContentWithParent = PhrasingContent & {
	children: PhrasingContent[];
};

export type TableOfContents = Array<Section>;

interface MDXJSEsm {
	type: "mdxjsEsm";
	value: string;
	data?: {
		estree?: Program;
	};
}

const tableOfContents: TableOfContents = [];

const doesHaveChildren = (child: PhrasingContent): boolean => {
	return ["delete", "emphasis", "strong", "link", "linkReference"].includes(
		child.type,
	);
};

const setNodeId = (node: Heading, id: string) => {
	if (!node.data) node.data = {};
	if (!node.data.hProperties) node.data.hProperties = {};
	node.data.hProperties.id = id;
};

const extractText = (children: PhrasingContent[]): string => {
	return children
		.map((child) => {
			if (child.type === "text" && child.value.length > 0) {
				return child.value;
			}

			if (
				doesHaveChildren(child) &&
				(child as PhrasingContentWithParent).children.length > 0
			) {
				return extractText((child as PhrasingContentWithParent).children);
			}

			return "";
		})
		.join(" ");
};

const formatExportNode = (): MDXJSEsm => {
	return {
		type: "mdxjsEsm",
		value: `export const tableOfContents = ${JSON.stringify(tableOfContents)};`,
		data: {
			estree: {
				type: "Program",
				body: [
					{
						type: "ExportNamedDeclaration",
						declaration: {
							type: "VariableDeclaration",
							kind: "const",
							declarations: [
								{
									type: "VariableDeclarator",
									id: {
										type: "Identifier",
										name: "tableOfContents",
									},
									init: {
										type: "ArrayExpression",
										elements: tableOfContents.map((section) => ({
											type: "ObjectExpression",
											properties: [
												{
													type: "Property",
													key: {
														type: "Identifier",
														name: "id",
													},
													value: {
														type: "Literal",
														value: section.id,
													},
													kind: "init",
													computed: false,
													method: false,
													shorthand: false,
												},
												{
													type: "Property",
													key: {
														type: "Identifier",
														name: "title",
													},
													value: {
														type: "Literal",
														value: section.title,
													},
													kind: "init",
													computed: false,
													method: false,
													shorthand: false,
												},
												{
													type: "Property",
													key: {
														type: "Identifier",
														name: "level",
													},
													value: {
														type: "Literal",
														value: section.level,
													},
													kind: "init",
													computed: false,
													method: false,
													shorthand: false,
												},
											],
										})),
									},
								},
							],
						},
						specifiers: [],
						source: null,
					},
				],
				sourceType: "module",
			} as unknown as Program,
		},
	};
};

const remarkHeadingId: Plugin<[], Root> = () => (tree: Root) => {
	const slugify = slugifyWithCounter();

	visit(tree, "heading", (node) => {
		const lastChild = node.children[node.children.length - 1];

		if (lastChild && lastChild.type === "text") {
			const string = lastChild.value.replace(/ +$/, "");
			const matched = string.match(/ {#(.*?)}$/);

			if (matched) return;
		}

		const slug = slugify(extractText(node.children));
		setNodeId(node, slug);

		const depth = node.depth as 2 | 3;
		if (depth > 3) return;

		tableOfContents.push({
			id: slug,
			title: extractText(node.children),
			level: depth,
		});
	});

	const exportNode = formatExportNode();
	tree.children.push(exportNode);
};

export default remarkHeadingId;
