import { describe, expect, it } from "vitest";
import {
  hashControlUiTranslationText,
  materializeControlUiLocaleCatalog,
  mergeControlUiTranslationMaps,
} from "../../scripts/lib/control-ui-i18n-catalog.ts";
import {
  createControlUiLocaleSyncPlan,
  flattenTranslations,
  resolveLocaleMetaProvenance,
  type LocaleEntry,
  type LocaleMeta,
  type TranslationMemoryEntry,
} from "../../scripts/lib/control-ui-i18n-sync-plan.ts";

const entry: LocaleEntry = {
  exportName: "fr",
  fileName: "fr.ts",
  languageKey: "fr",
  locale: "fr",
};

const hashText = (text: string) => `hash:${text}`;
const cacheKeyFor = (key: string, textHash: string) => `cache:${key}:${textHash}`;

function memoryEntry(overrides: Partial<TranslationMemoryEntry> = {}): TranslationMemoryEntry {
  return {
    cache_key: "legacy-cache",
    model: "legacy-model",
    provider: "legacy-provider",
    segment_id: "legacy.segment",
    source_path: "ui/src/i18n/locales/fr.ts",
    src_lang: "en",
    text: "Shared",
    text_hash: hashText("Shared"),
    tgt_lang: "fr",
    translated: "Partage",
    updated_at: "2026-01-01T00:00:00.000Z",
    ...overrides,
  };
}

function localeMeta(overrides: Partial<LocaleMeta> = {}): LocaleMeta {
  return {
    fallbackKeys: [],
    generatedAt: "2026-01-01T00:00:00.000Z",
    locale: "fr",
    model: "legacy-model",
    provider: "legacy-provider",
    sourceHash: "old-source",
    totalKeys: 0,
    translatedKeys: 0,
    workflow: 1,
    ...overrides,
  };
}

describe("createControlUiLocaleSyncPlan", () => {
  it("merges lazy English source catalogs without losing sibling keys", () => {
    expect(
      mergeControlUiTranslationMaps(
        { activity: { title: "Activity" }, common: { ok: "OK" } },
        { activity: { runInspector: { title: "Run inspector" } } },
      ),
    ).toEqual({
      activity: { title: "Activity", runInspector: { title: "Run inspector" } },
      common: { ok: "OK" },
    });
  });

  it("preserves provenance when a configured provider performs no translation", () => {
    const previousMeta = localeMeta();

    expect(
      resolveLocaleMetaProvenance({
        didTranslate: false,
        model: "next-model",
        previousMeta,
        provider: "next-provider",
      }),
    ).toEqual({ model: previousMeta.model, provider: previousMeta.provider });
    expect(
      resolveLocaleMetaProvenance({
        didTranslate: true,
        model: "next-model",
        previousMeta,
        provider: "next-provider",
      }),
    ).toEqual({ model: "next-model", provider: "next-provider" });
  });

  it("plans reuse and renders deterministic locale artifacts", () => {
    const sourceFlat = flattenTranslations({
      group: {
        cached: "Cached source",
        existing: "Existing source",
        pending: "Pending source",
        reused: "Shared",
      },
    });
    const exactCacheKey = cacheKeyFor("group.cached", hashText("Cached source"));
    const exactCache = memoryEntry({
      cache_key: exactCacheKey,
      segment_id: "group.cached",
      text: "Cached source",
      text_hash: hashText("Cached source"),
      translated: "En cache",
    });
    const sharedCache = memoryEntry();
    const plan = createControlUiLocaleSyncPlan({
      allowTranslate: false,
      cacheKeyFor,
      entry,
      existingFlat: new Map([
        ["group.cached", "Ancien cache"],
        ["group.existing", "Existant"],
      ]),
      force: false,
      hashText,
      previousMeta: localeMeta({ fallbackKeys: ["group.cached"] }),
      sourceFlat,
      sourceHash: "next-source",
      translationMemory: new Map([
        [sharedCache.cache_key, sharedCache],
        [exactCache.cache_key, exactCache],
      ]),
    });

    expect(plan.pending.map((item) => item.key)).toEqual(["group.pending"]);
    expect(plan.newFallbackCount).toBe(1);

    const artifacts = plan.render({
      defaultGlossary: [{ source: "OpenClaw", target: "OpenClaw" }],
      generatedAt: "2026-02-02T00:00:00.000Z",
      glossary: [],
      model: "legacy-model",
      provider: "legacy-provider",
      workflow: 1,
    });

    expect(artifacts.meta).toBe(
      `${JSON.stringify(
        {
          fallbackKeys: ["group.cached", "group.pending"],
          generatedAt: "2026-02-02T00:00:00.000Z",
          locale: "fr",
          model: "legacy-model",
          provider: "legacy-provider",
          sourceHash: "next-source",
          totalKeys: 4,
          translatedKeys: 2,
          workflow: 1,
        },
        null,
        2,
      )}\n`,
    );
    expect(artifacts.glossary).toBe(
      `${JSON.stringify([{ source: "OpenClaw", target: "OpenClaw" }], null, 2)}\n`,
    );
    const reusedCache = {
      ...sharedCache,
      cache_key: cacheKeyFor("group.reused", hashText("Shared")),
      segment_id: "group.reused",
    };
    expect(artifacts.translationMemory).toBe(
      `${[reusedCache, exactCache]
        .toSorted((left, right) => left.cache_key.localeCompare(right.cache_key))
        .map((value) => JSON.stringify(value))
        .join("\n")}\n`,
    );
  });

  it("reuses grouped segment aliases only while their source text still matches", () => {
    const sourceFlat = flattenTranslations({ group: { alias: "Shared" } });
    const grouped = memoryEntry({ segment_ids: ["group.alias"] });
    const createPlan = (source: ReadonlyMap<string, string>) =>
      createControlUiLocaleSyncPlan({
        allowTranslate: false,
        cacheKeyFor,
        entry,
        existingFlat: new Map(),
        force: false,
        hashText,
        previousMeta: localeMeta(),
        sourceFlat: source,
        sourceHash: "source",
        translationMemory: new Map([[grouped.cache_key, grouped]]),
      });

    expect(createPlan(sourceFlat).pending).toEqual([]);
    expect(createPlan(new Map([["group.alias", "Changed"]])).pending).toHaveLength(1);
  });

  it("materializes grouped aliases in source order and discards stale or retired segments", () => {
    const grouped = memoryEntry({
      segment_id: "group.first",
      segment_ids: ["group.second", "removed"],
      text_hash: hashControlUiTranslationText("Shared"),
      translated: "Partagé",
    });
    const source = flattenTranslations({ group: { first: "Shared", second: "Shared" } });

    expect(
      materializeControlUiLocaleCatalog(source, new Map([[grouped.cache_key, grouped]])),
    ).toEqual({
      group: { first: "Partagé", second: "Partagé" },
    });
    expect(
      materializeControlUiLocaleCatalog(
        new Map([["group.first", "Changed"]]),
        new Map([[grouped.cache_key, grouped]]),
      ),
    ).toEqual({});
  });

  it("refreshes recorded fallbacks and records translated replacements", () => {
    const sourceFlat = flattenTranslations({ title: "New English" });
    const previousMeta = localeMeta({
      fallbackKeys: ["title"],
      sourceHash: "previous-source",
      totalKeys: 1,
      translatedKeys: 0,
    });
    const plan = createControlUiLocaleSyncPlan({
      allowTranslate: true,
      cacheKeyFor,
      entry,
      existingFlat: new Map([["title", "Old English"]]),
      force: true,
      hashText,
      previousMeta,
      sourceFlat,
      sourceHash: "next-source",
      translationMemory: new Map(),
    });

    expect(plan.newFallbackCount).toBe(0);
    plan.recordTranslations(plan.pending, new Map([["title", "Nouveau"]]), {
      model: "next-model",
      provider: "next-provider",
      sourceLocale: "en",
      updatedAt: () => "2026-02-02T00:00:00.000Z",
    });

    const artifacts = plan.render({
      defaultGlossary: [],
      generatedAt: "2026-03-03T00:00:00.000Z",
      glossary: [],
      model: "next-model",
      provider: "next-provider",
      workflow: 1,
    });

    expect(artifacts.fallbackCount).toBe(0);
    expect(artifacts.nextFlat.get("title")).toBe("Nouveau");
    expect(JSON.parse(artifacts.meta)).toMatchObject({
      fallbackKeys: [],
      generatedAt: "2026-03-03T00:00:00.000Z",
      translatedKeys: 1,
    });
    expect(artifacts.translationMemory).toBe(
      `${JSON.stringify(
        memoryEntry({
          cache_key: cacheKeyFor("title", hashText("New English")),
          model: "next-model",
          provider: "next-provider",
          segment_id: "title",
          text: "New English",
          text_hash: hashText("New English"),
          translated: "Nouveau",
          updated_at: "2026-02-02T00:00:00.000Z",
        }),
      )}\n`,
    );
  });

  it("preserves generatedAt when semantic metadata is unchanged", () => {
    const sourceFlat = flattenTranslations({ title: "Titre" });
    const previousMeta = localeMeta({
      sourceHash: "same-source",
      totalKeys: 1,
      translatedKeys: 1,
    });
    const plan = createControlUiLocaleSyncPlan({
      allowTranslate: false,
      cacheKeyFor,
      entry,
      existingFlat: new Map([["title", "Titre"]]),
      force: false,
      hashText,
      previousMeta,
      sourceFlat,
      sourceHash: "same-source",
      translationMemory: new Map(),
    });

    const artifacts = plan.render({
      defaultGlossary: [],
      generatedAt: "2026-03-03T00:00:00.000Z",
      glossary: [],
      model: "legacy-model",
      provider: "legacy-provider",
      workflow: 1,
    });

    expect(JSON.parse(artifacts.meta)).toMatchObject({
      generatedAt: previousMeta.generatedAt,
    });
  });
});
