import fs from "node:fs";
import path from "node:path";
import Ajv, { type JSONSchemaType } from "ajv";
import findConfig from "find-config";

const ajv = new Ajv();

export type WrapAttributes =
	| "auto"
	| "force"
	| "force-aligned"
	| "force-expand-multiline"
	| "aligned-multiple"
	| "preserve"
	| "preserve-aligned";

export type SortHtmlAttributes =
	| "none"
	| "alphabetical"
	| "code-guide"
	| "idiomatic"
	| "vuejs"
	| "custom";

export type EndOfLine = "LF" | "CRLF";

export interface RuntimeConfig {
	indentSize?: number;
	wrapLineLength?: number;
	wrapAttributes?: WrapAttributes;
	wrapAttributesMinAttrs?: number;
	indentInnerHtml?: boolean;
	endWithNewline?: boolean;
	endOfLine?: EndOfLine;
	useTabs?: boolean;
	sortTailwindcssClasses?: boolean;
	tailwindcssConfigPath?: string;
	sortHtmlAttributes?: SortHtmlAttributes;
	customHtmlAttributesOrder?: string[] | string;
	noMultipleEmptyLines?: boolean;
	noPhpSyntaxCheck?: boolean;
	noSingleQuote?: boolean;
	noTrailingCommaPhp?: boolean;
	extraLiners?: string[];
	componentPrefix?: string[];
}

const defaultConfigNames = [".bladeformatterrc.json", ".bladeformatterrc"];

export function findRuntimeConfig(filePath: string): string | null {
	for (let i = 0; i < defaultConfigNames.length; i += 1) {
		const result: string | null = findConfig(defaultConfigNames[i], {
			cwd: path.dirname(filePath),
			home: false,
		});

		if (result) {
			return result;
		}
	}

	return null;
}

export async function readRuntimeConfig(
	filePath: string | null,
): Promise<RuntimeConfig | undefined> {
	if (filePath === null) {
		return undefined;
	}

	const options = JSON.parse((await fs.promises.readFile(filePath)).toString());

	const schema: JSONSchemaType<RuntimeConfig> = {
		type: "object",
		properties: {
			indentSize: { type: "integer", nullable: true },
			wrapLineLength: { type: "integer", nullable: true },
			wrapAttributes: {
				type: "string",
				enum: [
					"auto",
					"force",
					"force-aligned",
					"force-expand-multiline",
					"aligned-multiple",
					"preserve",
					"preserve-aligned",
				],
				nullable: true,
			},
			wrapAttributesMinAttrs: { type: "integer", nullable: true, default: 2 },
			indentInnerHtml: { type: "boolean", nullable: true },
			endWithNewline: { type: "boolean", nullable: true },
			endOfLine: { type: "string", enum: ["LF", "CRLF"], nullable: true },
			useTabs: { type: "boolean", nullable: true },
			sortTailwindcssClasses: { type: "boolean", nullable: true },
			tailwindcssConfigPath: { type: "string", nullable: true },
			sortHtmlAttributes: {
				type: "string",
				enum: [
					"none",
					"alphabetical",
					"code-guide",
					"idiomatic",
					"vuejs",
					"custom",
				],
				nullable: true,
			},
			customHtmlAttributesOrder: {
				type: "array",
				nullable: true,
				items: { type: "string" },
				default: [],
			},
			noMultipleEmptyLines: { type: "boolean", nullable: true },
			noPhpSyntaxCheck: { type: "boolean", nullable: true },
			noSingleQuote: { type: "boolean", nullable: true },
			noTrailingCommaPhp: { type: "boolean", nullable: true },
			extraLiners: {
				type: "array",
				nullable: true,
				items: { type: "string" },
				default: ["head", "body", "/html"],
			},
			componentPrefix: {
				type: "array",
				nullable: true,
				items: { type: "string" },
				default: ["x-", "livewire:"],
			},
		},
		additionalProperties: true,
	};
	const validate = ajv.compile(schema);

	if (!validate(options)) {
		throw validate;
	}

	return options;
}
