import { Command } from '@commander-js/extra-typings'
import Table from 'cli-table3'

import { getRepoRoot } from '../path'
import { getOutcome, SHFMT_SKIPPED_EXIT_CODE } from '../proc'

export const fixCmd = new Command('fix')
	.description('Fix deps/lint/format issues. If no options are provided, all fixes are run.')

	.option('-r, --root', 'Run fixes from root of repo. Defaults to cwd', false)
	.option('-d, --deps', 'Fix dependency versions with syncpack')
	.option('-l, --lint', 'Fix oxlint issues')
	.option('-f, --format', 'Format code with prettier')
	.option(
		'-w, --workers-types',
		'Generate Workers runtime types (worker-configuration.d.ts) via wrangler types'
	)

	.action(async ({ root, deps, lint, format, workersTypes }) => {
		const repoRoot = getRepoRoot()
		if (root) {
			cd(repoRoot)
		}

		// Run all if none are selected
		if (!deps && !lint && !format && !workersTypes) {
			deps = true
			lint = true
			format = true
			workersTypes = true
		}

		const cwd = process.cwd()
		const runFromRoot = cwd === repoRoot
		const cwdName = path.basename(cwd)

		const fixes = {
			deps: ['run-fix-deps'],
			lint: ['run-oxlint', '--fix'],
			workersTypes: ['turbo', 'fix:workers-types'],
			format: ['prettier', '.', '--cache', '--write', '--log-level=warn'],
			formatShell: ['runx', 'shfmt', 'fix', '--skip-if-unavailable'],
		} as const satisfies { [key: string]: string[] }

		type TableRow = [string, string, string, string]
		const table = new Table({
			head: [
				chalk.whiteBright('Name'),
				chalk.whiteBright('Command'),
				chalk.whiteBright('Outcome'),
				chalk.whiteBright('Ran From'),
			] satisfies TableRow,
		})

		$.stdio = 'inherit'
		$.verbose = true
		$.nothrow = true

		let didErr = false
		function getAndCheckOutcome({
			exitCode,
			skippedCode,
		}: {
			exitCode: number | null
			skippedCode?: number
		}): string {
			if (exitCode !== 0 && exitCode !== skippedCode) {
				didErr = true
			}
			return getOutcome({ exitCode, skippedCode })
		}

		if (deps) {
			const exitCode = await $({
				cwd: repoRoot, // Must be run from root
			})`${fixes.deps}`.exitCode
			table.push([
				'deps',
				fixes.deps.join(' '),
				getAndCheckOutcome({ exitCode }),
				'Root',
			] satisfies TableRow)
		}

		if (lint) {
			const exitCode = await $`${fixes.lint}`.exitCode
			table.push([
				'lint',
				fixes.lint.join(' '),
				getAndCheckOutcome({ exitCode }),
				runFromRoot ? 'Root' : `cwd (${cwdName})`,
			] satisfies TableRow)
		}

		if (workersTypes) {
			const exitCode = await $({
				cwd: repoRoot, // Must be run from root
			})`${fixes.workersTypes}`.exitCode
			table.push([
				'workers types',
				fixes.workersTypes.join(' '),
				getAndCheckOutcome({ exitCode }),
				runFromRoot ? 'Root' : `cwd (${cwdName})`,
			] satisfies TableRow)
		}

		if (format) {
			echo(chalk.dim('formatting with prettier (and shfmt if available)...'))

			const [prettierProc, shfmtProc] = await Promise.all([
				$({
					cwd: repoRoot, // Must be run from root
				})`${fixes.format}`,

				$({
					cwd: repoRoot, // Must be run from root
				})`${fixes.formatShell}`,
			])

			table.push(
				[
					'format',
					fixes.format.join(' '),
					getAndCheckOutcome({ exitCode: prettierProc.exitCode }),
					'Root',
				] satisfies TableRow,
				[
					'format shell',
					fixes.formatShell.join(' '),
					getAndCheckOutcome({
						exitCode: shfmtProc.exitCode,
						skippedCode: SHFMT_SKIPPED_EXIT_CODE,
					}),
					'Root',
				] satisfies TableRow
			)
		}

		echo(table.toString())

		if (didErr) {
			process.exit(1)
		}
	})
