import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { renderDocsHeadingMap } from "../../scripts/docs-list.js";
import {
  composeDocsConfig,
  parseArgs,
  reportOrphanLocaleDocs,
  writePublishedDocsMap,
} from "../../scripts/docs-sync-publish.mjs";

function collectPages(entry: unknown, pages: string[] = []): string[] {
  if (typeof entry === "string") {
    pages.push(entry);
    return pages;
  }
  if (Array.isArray(entry)) {
    for (const item of entry) {
      collectPages(item, pages);
    }
    return pages;
  }
  if (!entry || typeof entry !== "object") {
    return pages;
  }
  const record = entry as Record<string, unknown>;
  if (typeof record.page === "string") {
    pages.push(record.page);
  }
  collectPages(record.pages, pages);
  collectPages(record.groups, pages);
  collectPages(record.tabs, pages);
  return pages;
}

describe("docs-sync-publish", () => {
  it("materializes the public docs map only in the publish tree", () => {
    const targetDocsDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docs-map-publish-"));
    try {
      const outputPath = writePublishedDocsMap(targetDocsDir);
      expect(fs.readFileSync(outputPath, "utf8")).toBe(
        renderDocsHeadingMap(path.resolve(import.meta.dirname, "../../docs")),
      );
    } finally {
      fs.rmSync(targetDocsDir, { recursive: true, force: true });
    }
  });

  it("parses docs sync provenance args", () => {
    expect(
      parseArgs([
        "--target",
        "generated-docs",
        "--source-repo",
        "openclaw/openclaw",
        "--source-sha",
        "abc123",
        "--clawhub-repo",
        "../clawhub",
        "--clawhub-source-repo",
        "openclaw/clawhub",
        "--clawhub-source-sha",
        "def456",
      ]),
    ).toMatchObject({
      clawhubRepo: "../clawhub",
      clawhubSourceRepo: "openclaw/clawhub",
      clawhubSourceSha: "def456",
      sourceRepo: "openclaw/openclaw",
      sourceSha: "abc123",
      target: "generated-docs",
    });
  });

  it("rejects missing docs sync option values", () => {
    for (const flag of [
      "--target",
      "--source-repo",
      "--source-sha",
      "--clawhub-repo",
      "--clawhub-source-repo",
      "--clawhub-source-sha",
    ]) {
      expect(() => parseArgs([flag])).toThrow(`${flag} requires a value`);
      expect(() => parseArgs([flag, "--target", "generated-docs"])).toThrow(
        `${flag} requires a value`,
      );
      expect(() => parseArgs([flag, "-h"])).toThrow(`${flag} requires a value`);
    }
  });

  it("defers orphan locale deletion to translation finalization", () => {
    const docsDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-docs-sync-"));
    const mirroredEnglish = path.join(docsDir, "clawhub", "api.md");
    const localizedMirror = path.join(docsDir, "de", "clawhub", "api.md");
    const orphan = path.join(docsDir, "de", "removed.md");

    fs.mkdirSync(path.dirname(mirroredEnglish), { recursive: true });
    fs.mkdirSync(path.dirname(localizedMirror), { recursive: true });
    fs.writeFileSync(mirroredEnglish, "# ClawHub API\n");
    fs.writeFileSync(localizedMirror, "# ClawHub-API\n");
    fs.writeFileSync(orphan, "# Removed\n");

    try {
      expect(reportOrphanLocaleDocs(docsDir)).toBe(1);
      expect(fs.existsSync(localizedMirror)).toBe(true);
      expect(fs.existsSync(orphan)).toBe(true);
    } finally {
      fs.rmSync(docsDir, { recursive: true, force: true });
    }
  });

  it("keeps generated locale navigation aligned with English routes", () => {
    const config = composeDocsConfig() as {
      navigation: {
        languages: Array<{
          language: string;
          tabs: Array<{
            tab: string;
            groups?: Array<{ group: string; pages?: unknown }>;
          }>;
        }>;
      };
    };
    const english = config.navigation.languages.find((entry) => entry.language === "en");
    const simplifiedChinese = config.navigation.languages.find(
      (entry) => entry.language === "zh-Hans",
    );
    const german = config.navigation.languages.find((entry) => entry.language === "de");

    expect(english).toBeDefined();
    expect(simplifiedChinese).toBeDefined();
    expect(german).toBeDefined();
    expect(english!.tabs.slice(-4).map((tab) => tab.tab)).toEqual([
      "Gateway & Ops",
      "Reference",
      "Release & CI",
      "Help",
    ]);

    const releaseRoutes = [
      "releases/index",
      "releases/2026.7.1",
      "releases/2026.6.11",
      "maturity/scorecard",
      "maturity/taxonomy",
      "reference/RELEASING",
      "reference/full-release-validation",
      "reference/release-performance-sweep",
      "reference/test",
      "ci",
      "help/scripts",
    ];
    const releaseTab = english!.tabs.find((tab) => tab.tab === "Release & CI");
    expect(releaseTab?.groups?.map((group) => group.group)).toEqual([
      "Release notes",
      "Maturity",
      "Release process",
      "Testing and CI",
    ]);
    expect(releaseTab?.groups?.[0]?.pages).toEqual([
      "releases/index",
      "releases/2026.7.1",
      "releases/2026.6.11",
    ]);
    expect(collectPages(releaseTab)).toEqual(releaseRoutes);
    expect(new Set(collectPages(releaseTab))).toHaveLength(releaseRoutes.length);

    const englishWithoutClawHub = {
      ...english,
      tabs: english!.tabs.filter((tab) => tab.tab !== "ClawHub"),
    };
    const expectedZhPages = collectPages(englishWithoutClawHub)
      .map((page) => `zh-CN/${page}`)
      .toSorted();
    expect(collectPages(simplifiedChinese).toSorted()).toEqual(expectedZhPages);
    expect(simplifiedChinese!.tabs[0]?.tab).toBe("快速开始");
    expect(simplifiedChinese!.tabs[0]?.groups?.[0]?.group).toBe("首页");
    const simplifiedChineseReleaseTab = simplifiedChinese!.tabs.find(
      (tab) => tab.tab === "发布与 CI",
    );
    expect(simplifiedChineseReleaseTab?.groups?.map((group) => group.group)).toEqual([
      "发布说明",
      "成熟度",
      "发布流程",
      "测试与 CI",
    ]);
    expect(simplifiedChineseReleaseTab?.groups?.[0]?.pages).toEqual([
      "zh-CN/releases/index",
      "zh-CN/releases/2026.7.1",
      "zh-CN/releases/2026.6.11",
    ]);
    expect(collectPages(simplifiedChineseReleaseTab)).toEqual(
      releaseRoutes.map((page) => `zh-CN/${page}`),
    );
    expect(new Set(collectPages(simplifiedChineseReleaseTab))).toHaveLength(releaseRoutes.length);

    expect(collectPages(german)).toHaveLength(collectPages(englishWithoutClawHub).length);
    expect(german!.tabs[0]?.tab).toBe("Loslegen");
    expect(german!.tabs[0]?.groups?.[0]?.group).toBe("Überblick");

    for (const locale of config.navigation.languages.filter(
      (entry) => entry.language !== "en" && entry.language !== "zh-Hans",
    )) {
      const localeDir = collectPages(locale)[0]?.split("/")[0];
      const localizedRoutes = releaseRoutes.map((page) => `${localeDir}/${page}`);
      const localizedReleaseTab = locale.tabs.find((tab) =>
        collectPages(tab).includes(`${localeDir}/releases/index`),
      );
      expect(collectPages(localizedReleaseTab)).toEqual(localizedRoutes);
      expect(new Set(collectPages(localizedReleaseTab))).toHaveLength(localizedRoutes.length);
    }
  });
});
