import chalk from "chalk";
import concat from "concat-stream";
import _ from "lodash";
import { loadWASM } from "vscode-oniguruma";
import yargs from "yargs";

import { promises as fs } from "node:fs";
import os from "node:os";

import { hideBin } from "yargs/helpers";
import { name, version } from "../package.json";
import { BladeFormatter } from "./main";

export default async function cli() {
	// @ts-ignore
	const parsed = await yargs(hideBin(process.argv))
		.usage(
			`${chalk.green(
				name,
			)} ${version}\nAn opinionated blade template formatter for Laravel. \n\n ${chalk.yellow(
				"Usage:",
			)} $0 [options] [file glob | ...]`,
		)
		.wrap(null)
		.example(
			'$0 "resources/views/**/*.blade.php" --write',
			"Format all files in views directory",
		)
		.option("check-formatted", {
			alias: "c",
			type: "boolean",
			description: "Only checks files are formatted or not",
			default: false,
		})
		.option("write", {
			alias: "w",
			type: "boolean",
			description: "Write to file",
			default: false,
		})
		.option("diff", {
			alias: "d",
			type: "boolean",
			description: "Show diffs",
			default: false,
		})
		.option("end-with-newline", {
			alias: "e",
			type: "boolean",
			description: "End output with newline",
			default: true,
		})
		.option("end-of-line", {
			type: "string",
			description: "End of line character(s). [LF|CRLF]",
			default: os.EOL === "\r\n" ? "CRLF" : "LF",
		})
		.option("indent-size", {
			alias: "i",
			type: "integer",
			description: "Indentation size",
			default: 4,
		})
		.option("wrap-line-length", {
			alias: "wrap",
			type: "integer",
			description: "The length of line wrap size",
			default: 120,
		})
		.option("wrap-attributes", {
			alias: "wrap-atts",
			type: "string",
			description:
				"The way to wrap attributes.\n[auto|force|force-aligned|force-expand-multiline|aligned-multiple|preserve|preserve-aligned]",
			default: "auto",
		})
		.option("wrap-attributes-min-attrs", {
			type: "integer",
			alias: "M",
			description: `Minimum number of html tag attributes for force wrap attribute options. Wrap the first attribute only if 'force-expand-multiline' is specified in wrap attributes`,
			default: "2",
		})
		.option("indent-inner-html", {
			alias: "I",
			type: "boolean",
			description: "Indent <head> and <body> sections in html.",
			default: false,
		})
		.option("sort-tailwindcss-classes", {
			alias: "sort-classes",
			type: "boolean",
			description: "Sort tailwindcss classes",
			default: false,
		})
		.option("tailwindcss-config-path", {
			alias: ["tailwindcssConfigPath"],
			type: "string",
			description: "Specify path of tailwind config",
			default: null,
		})
		.option("sort-html-attributes", {
			alias: "sort-attributes",
			type: "string",
			choices: [
				"none",
				"alphabetical",
				"code-guide",
				"idiomatic",
				"vuejs",
				"custom",
			],
			description: "Sort HTML attributes.",
			default: "none",
			defaultDescription: "none",
		})
		.option("custom-html-attributes-order", {
			type: "string",
			description:
				"Comma separated custom HTML attributes order. To enable this you must specify sort html attributes option as `custom`. You can use regex for attribute names.",
			default: null,
		})
		.option("no-single-quote", {
			type: "boolean",
			description:
				"Use double quotes instead of single quotes for php expression.",
			default: false,
		})
		.option("single-quote", {
			type: "boolean",
			description: "this is a workaround for combine strict && boolean option",
			hidden: true,
			default: true,
		})
		.option("extra-liners", {
			alias: "E",
			type: "string",
			description:
				"Comma separated list of tags that should have an extra newline before them.",
			default: "head,body,/html",
			nullable: true,
			coerce(formats) {
				// Make sure we support comma-separated syntax: --extra-liners head,body
				return _.flatten(
					_.flatten([formats]).map((format) => format.split(",")),
				);
			},
		})
		.option("component-prefix", {
			alias: "P",
			type: "string",
			description: "Component prefix use to preserve style in html attributes.",
			default: "x-,livewire:",
			nullable: true,
			coerce(formats) {
				// Make sure we support comma-separated syntax: --component-prefix x-,livewire:
				return _.flatten(
					_.flatten([formats]).map((format) => format.split(",")),
				);
			},
		})
		.option("no-multiple-empty-lines", {
			type: "boolean",
			description: "Merge multiple blank lines into a single blank line",
			default: false,
		})
		.option("multiple-empty-lines", {
			type: "boolean",
			description: "this is a workaround for combine strict && boolean option",
			hidden: true,
			default: true,
		})
		.option("no-php-syntax-check", {
			type: "boolean",
			description: "Disable PHP syntax checking",
			default: false,
		})
		.option("php-syntax-check", {
			type: "boolean",
			description: "this is a workaround for combine strict && boolean option",
			hidden: true,
			default: true,
		})
		.option("no-trailing-comma-php", {
			type: "boolean",
			description:
				"If set to true, no trailing commas are printed for php expression.",
			default: false,
		})
		.option("trailing-comma-php", {
			type: "boolean",
			description: "this is a workaround for combine strict && boolean option",
			hidden: true,
			default: true,
		})
		.option("progress", {
			alias: "p",
			type: "boolean",
			description: "Print progress",
			default: false,
		})
		.option("stdin", {
			type: "boolean",
			description: "Format code provided on <STDIN>",
			default: false,
		})
		.option("config", {
			alias: ["runtimeConfigPath"],
			type: "string",
			description:
				"Use this configuration, overriding .bladeformatterrc config options if present",
			default: null,
		})
		.option("ignore-path", {
			alias: ["ignoreFilePath"],
			type: "string",
			description: "Specify path of ignore file",
			default: null,
		})
		.help("h")
		.alias("h", "help")
		.strictOptions()
		.fail((msg, err) => {
			if (err) throw err; // preserve stack
			process.stdout.write(`${chalk.red("error: ")}${msg}\n\n`);
			process.stdout.write(
				`${chalk.yellow("Usage: ")} ${name} [options] [file glob | ...]\n\n`,
			);
			process.stdout.write(
				`For more information try ${chalk.green("--help")}\n`,
			);
			process.exit(1);
		})
		.epilog(
			"Copyright Shuhei Hayashibara 2022\nFor more information, see https://github.com/shufo/blade-formatter",
		);

	// @ts-ignore
	// eslint-disable-next-line
	const wasm = await fs.readFile(
		require.resolve("vscode-oniguruma/release/onig.wasm"),
	);
	await loadWASM(wasm.buffer);

	const options = _.chain(parsed.argv)
		.set("noMultipleEmptyLines", !parsed.argv.multipleEmptyLines)
		.set("noPhpSyntaxCheck", !parsed.argv.phpSyntaxCheck)
		.set("noSingleQuote", !parsed.argv.singleQuote)
		.set("noTrailingCommaPhp", !parsed.argv.trailingCommaPhp)
		.value();

	if (parsed.argv.stdin) {
		await process.stdin.pipe(
			concat({ encoding: "string" }, (text: Buffer) =>
				new BladeFormatter(options)
					.format(text)
					.then((result: string | undefined) => {
						if (result !== undefined) {
							process.stdout.write(result);
						}
					})
					.catch((error) => {
						process.stdout.write(`${error.toString()}\n`);
						process.exit(1);
					}),
			),
		);
		return;
	}

	if (parsed.argv._.length === 0) {
		parsed.showHelp();
		return;
	}

	const formatter = new BladeFormatter(options, parsed.argv._);
	await formatter.formatFromCLI();
}
