import { printWarn } from "./utils";
import crypto from "crypto";
import fs from "fs";

/** Bump when the on-disk shape changes; older caches are ignored. */
export const CACHE_VERSION = 1;

/** Default cache file written into the cwd when `--cache` has no path. */
export const DEFAULT_CACHE_PATH = ".i18n-ai-translate-cache.json";

/**
 * A persistent translation memory. `entries` maps a content hash (see
 * {@link cacheKey}) to the previously translated string. The key is
 * scoped to the source text plus the input/output languages and the
 * `--context`, but deliberately *not* the engine or model — so a cached
 * translation is reused across runs even after switching providers.
 *
 * The object is mutated in place by `translate()`; the CLI loads it
 * before a run and writes it back after.
 */
export type TranslationCache = {
    version: number;
    entries: { [hash: string]: string };
};

/**
 * @returns a fresh, empty cache at the current schema version
 */
export function createCache(): TranslationCache {
    return { entries: {}, version: CACHE_VERSION };
}

/**
 * Compute the content-addressed key for one translatable string.
 * @param inputLanguageCode - the source language code
 * @param outputLanguageCode - the target language code
 * @param context - the `--context` string ("" when unset)
 * @param source - the source text being translated
 * @returns a hex sha256 digest uniquely identifying the entry
 */
export function cacheKey(
    inputLanguageCode: string,
    outputLanguageCode: string,
    context: string,
    source: string,
): string {
    return crypto
        .createHash("sha256")
        .update(
            `${inputLanguageCode} ${outputLanguageCode} ${context} ${source}`,
        )
        .digest("hex");
}

/**
 * Look up a previously cached translation.
 * @param cache - the translation memory
 * @param inputLanguageCode - the source language code
 * @param outputLanguageCode - the target language code
 * @param context - the `--context` string ("" when unset)
 * @param source - the source text being translated
 * @returns the cached translation, or undefined on a miss
 */
export function getCachedTranslation(
    cache: TranslationCache,
    inputLanguageCode: string,
    outputLanguageCode: string,
    context: string,
    source: string,
): string | undefined {
    return cache.entries[
        cacheKey(inputLanguageCode, outputLanguageCode, context, source)
    ];
}

/**
 * Record a translation for reuse on later runs.
 * @param cache - the translation memory
 * @param inputLanguageCode - the source language code
 * @param outputLanguageCode - the target language code
 * @param context - the `--context` string ("" when unset)
 * @param source - the source text being translated
 * @param translated - the translated text to store
 */
export function setCachedTranslation(
    cache: TranslationCache,
    inputLanguageCode: string,
    outputLanguageCode: string,
    context: string,
    source: string,
    translated: string,
): void {
    cache.entries[
        cacheKey(inputLanguageCode, outputLanguageCode, context, source)
    ] = translated;
}

/**
 * Load a cache from disk, tolerating a missing or incompatible file by
 * returning a fresh cache rather than throwing — a stale cache should
 * never break a translation run.
 * @param filePath - path to the cache JSON file
 * @returns the loaded cache, or a fresh one
 */
export function loadCache(filePath: string): TranslationCache {
    let raw: string;
    try {
        raw = fs.readFileSync(filePath, "utf-8");
    } catch {
        // Missing file on the first run is expected.
        return createCache();
    }

    try {
        const parsed = JSON.parse(raw);
        if (
            parsed &&
            typeof parsed === "object" &&
            parsed.version === CACHE_VERSION &&
            parsed.entries &&
            typeof parsed.entries === "object"
        ) {
            return parsed as TranslationCache;
        }
    } catch {
        // Fall through to the warning below.
    }

    printWarn(`Ignoring incompatible cache at ${filePath}; starting fresh.`);
    return createCache();
}

/**
 * Persist a cache to disk as pretty-printed JSON with a trailing newline.
 * @param filePath - path to the cache JSON file
 * @param cache - the translation memory to write
 */
export function saveCache(filePath: string, cache: TranslationCache): void {
    fs.writeFileSync(filePath, `${JSON.stringify(cache, null, 4)}\n`);
}
