import {
    CLI_HELP,
    DEFAULT_TEMPLATED_STRING_PREFIX,
    DEFAULT_TEMPLATED_STRING_SUFFIX,
} from "./constants";
import { Command } from "commander";
import { DEFAULT_CACHE_PATH, loadCache, saveCache } from "./cache";
import { loadGlossary } from "./glossary";
import { printError, printInfo, resolveInputPath } from "./utils";
import { processModelArgs, processOverridePromptFile } from "./cli_helpers";
import { translateDirectoryDiff } from "./translate_directory";
import { translateFileDiff } from "./translate_file";
import ChatPool from "./chat_pool";
import RateLimiter from "./rate_limiter";
import fs, { mkdtempSync } from "fs";
import path from "path";
import type { TranslationCache } from "./cache";
import type DryRun from "./interfaces/dry_run";
import type Glossary from "./interfaces/glossary";
import type OverridePrompt from "./interfaces/override_prompt";

/**
 * Builds the diff command for comparing i18n files or directories.
 * @returns the diff command with its options and action.
 */
export default function buildDiffCommand(): Command {
    return new Command("diff")
        .requiredOption(
            "-b, --before <fileOrDirectoryBefore>",
            "Source i18n file or directory before changes, in the jsons/ directory if a relative path is given",
        )
        .requiredOption(
            "-a, --after <fileOrDirectoryAfter>",
            "Source i18n file or directory after changes, in the jsons/ directory if a relative path is given",
        )
        .requiredOption(
            "-l, --input-language <inputLanguageCode>",
            "The input language's code, in ISO6391 (e.g. en, fr)",
        )
        .requiredOption("-e, --engine <engine>", CLI_HELP.Engine)
        .option("-m, --model <model>", CLI_HELP.Model)
        .option("-r, --rate-limit-ms <rateLimitMs>", CLI_HELP.RateLimit)
        .option("-k, --api-key <API key>", "API key")
        .option("-h, --host <hostIP:port>", CLI_HELP.OllamaHost)
        .option(
            "--ensure-changed-translation",
            CLI_HELP.EnsureChangedTranslation,
            false,
        )
        .option(
            "-p, --templated-string-prefix <prefix>",
            "Prefix for templated strings",
            DEFAULT_TEMPLATED_STRING_PREFIX,
        )
        .option(
            "-s, --templated-string-suffix <suffix>",
            "Suffix for templated strings",
            DEFAULT_TEMPLATED_STRING_SUFFIX,
        )
        .option("-n, --batch-size <batchSize>", CLI_HELP.BatchSize)
        .option(
            "--skip-translation-verification",
            CLI_HELP.SkipTranslationVerification,
            false,
        )
        .option(
            "--skip-styling-verification",
            CLI_HELP.SkipStylingVerification,
            false,
        )
        .option(
            "--override-prompt <path to JSON file>",
            CLI_HELP.OverridePromptFile,
        )
        .option("--verbose", CLI_HELP.Verbose, false)
        .option("--prompt-mode <prompt-mode>", CLI_HELP.PromptMode)
        .option("--batch-max-tokens <batch-max-tokens>", CLI_HELP.MaxTokens)
        .option("--dry-run", CLI_HELP.DryRun, false)
        .option("--no-continue-on-error", CLI_HELP.NoContinueOnError)
        .option("--concurrency <concurrency>", CLI_HELP.Concurrency)
        .option("--context <context>", CLI_HELP.Context)
        .option(
            "--exclude-languages [language codes...]",
            CLI_HELP.ExcludeLanguages,
        )
        .option("--tokens-per-minute <tpm>", CLI_HELP.TokensPerMinute)
        .option("--file-format <format>", CLI_HELP.FileFormat)
        .option("--cache [path]", CLI_HELP.Cache)
        .option("--glossary <path>", CLI_HELP.Glossary)
        .action(async (options: any) => {
            const modelArgs = processModelArgs(options);

            // Shared pool + limiter mirroring cli_translate.ts. Diff
            // currently has a single run per invocation (no language
            // fan-out here), but plumbing it through keeps the option
            // shape symmetric and prevents surprises if diff grows a
            // --language-concurrency later.
            const sharedRateLimiter = new RateLimiter(
                modelArgs.rateLimitMs,
                Boolean(options.verbose),
                modelArgs.tokensPerMinute,
            );

            const sharedPool = ChatPool.create({
                apiKey: modelArgs.apiKey,
                chatParams: modelArgs.chatParams,
                concurrency: Math.max(1, modelArgs.concurrency),
                engine: options.engine,
                host: modelArgs.host,
                model: modelArgs.model,
                rateLimiter: sharedRateLimiter,
            });

            let cachePath: string | undefined;
            let cache: TranslationCache | undefined;
            if (options.cache) {
                const resolvedPath =
                    typeof options.cache === "string"
                        ? options.cache
                        : DEFAULT_CACHE_PATH;

                cachePath = resolvedPath;
                cache = loadCache(resolvedPath);
            }

            let glossary: Glossary | undefined;
            if (options.glossary) {
                try {
                    glossary = loadGlossary(options.glossary);
                } catch (e) {
                    printError(`${e}`);
                    process.exit(2);
                }
            }

            const sharedOptions = {
                ...modelArgs,
                cache,
                context: options.context,
                continueOnError: options.continueOnError,
                ensureChangedTranslation: options.ensureChangedTranslation,
                excludeLanguages: options.excludeLanguages,
                format: options.fileFormat,
                glossary,
                pool: sharedPool,
                rateLimiter: sharedRateLimiter,
                skipStylingVerification: options.skipStylingVerification,
                skipTranslationVerification:
                    options.skipTranslationVerification,
                templatedStringPrefix: options.templatedStringPrefix,
                templatedStringSuffix: options.templatedStringSuffix,
                verbose: options.verbose,
            };

            let overridePrompt: OverridePrompt | undefined;
            if (options.overridePrompt) {
                overridePrompt = processOverridePromptFile(
                    options.overridePrompt,
                );
            }

            let dryRun: DryRun | undefined;
            if (options.dryRun) {
                dryRun = {
                    basePath: mkdtempSync(
                        `/tmp/i18n-ai-translate-${new Date().toISOString().replace(/[:.]/g, "-")}-`,
                    ),
                };
            }

            const beforeInputPath = resolveInputPath(options.before);
            const afterInputPath = resolveInputPath(options.after);

            if (
                fs.statSync(beforeInputPath).isFile() !==
                fs.statSync(afterInputPath).isFile()
            ) {
                printError(
                    "--before and --after arguments must be both files or both directories",
                );
                return;
            }

            if (fs.statSync(beforeInputPath).isFile()) {
                // Ensure they're in the same path
                if (
                    path.dirname(beforeInputPath) !==
                    path.dirname(afterInputPath)
                ) {
                    printError("Input files are not in the same directory");
                    return;
                }

                await translateFileDiff({
                    ...sharedOptions,
                    dryRun,
                    engine: options.engine,
                    inputAfterFileOrPath: afterInputPath,
                    inputBeforeFileOrPath: beforeInputPath,
                    inputLanguageCode: options.inputLanguage,
                    overridePrompt,
                });
            } else {
                await translateDirectoryDiff({
                    ...sharedOptions,
                    baseDirectory: path.resolve(beforeInputPath, ".."),
                    dryRun,
                    engine: options.engine,
                    inputFolderNameAfter: afterInputPath,
                    inputFolderNameBefore: beforeInputPath,
                    inputLanguageCode: options.inputLanguage,
                    overridePrompt,
                });
            }

            // Persist the translation memory after the diff completes.
            // Dry-run is a no-write preview, so leave the cache untouched.
            if (cache && cachePath && !options.dryRun) {
                saveCache(cachePath, cache);
                if (options.verbose) {
                    printInfo(`Wrote translation cache to ${cachePath}`);
                }
            }
        });
}
