// Check Deadcode Exports tests cover parsing and hard-zero enforcement.
import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
import YAML from "yaml";
import allExportsKnipConfig from "../../config/knip.all-exports.config.ts";
import knipConfig from "../../config/knip.config.ts";
import scriptExportsKnipConfig from "../../config/knip.scripts-exports.config.ts";
import {
  checkExportScan,
  checkUnusedExports,
  parseKnipCompactUnusedExports,
  parseKnipCompactUnusedExportsResult,
} from "../../scripts/check-deadcode-exports.mts";

const fullRootWorkspace = allExportsKnipConfig.workspaces["."];
const fullExtensionWorkspace = allExportsKnipConfig.workspaces["extensions/*"];
const fullUiWorkspace = allExportsKnipConfig.workspaces.ui;
const scriptRootWorkspace = scriptExportsKnipConfig.workspaces["."];
if (!fullRootWorkspace || !fullExtensionWorkspace || !fullUiWorkspace || !scriptRootWorkspace) {
  throw new Error("deadcode Knip configs must define root, extension, and UI workspaces");
}

function listQaScenarioExecutionPaths(dir = "qa/scenarios"): string[] {
  return fs
    .readdirSync(dir, { withFileTypes: true })
    .flatMap((entry) => {
      const entryPath = path.join(dir, entry.name);
      if (entry.isDirectory()) {
        return listQaScenarioExecutionPaths(entryPath);
      }
      if (!entry.isFile() || (!entry.name.endsWith(".yaml") && !entry.name.endsWith(".yml"))) {
        return [];
      }
      const document = YAML.parse(fs.readFileSync(entryPath, "utf8")) as {
        scenario?: { execution?: { kind?: unknown; path?: unknown } };
      };
      const execution = document.scenario?.execution;
      return execution?.kind !== "flow" && typeof execution?.path === "string"
        ? [execution.path]
        : [];
    })
    .toSorted();
}

describe("check-deadcode-exports", () => {
  it("requests every unused-export issue class from Knip", () => {
    const script = fs.readFileSync(
      new URL("../../scripts/check-deadcode-exports.mts", import.meta.url),
      "utf8",
    );
    expect(script).toContain('"exports,nsExports,types,nsTypes,enumMembers,namespaceMembers"');
    expect(script).toContain('"config/knip.config.ts", "--production"');
    expect(script).toContain('"config/knip.all-exports.config.ts"');
    expect(script).toContain('"config/knip.scripts-exports.config.ts"');
    expect(script).toContain(
      'args: ["--config", "config/knip.scripts-exports.config.ts", "--include-entry-exports"]',
    );
  });

  it("excludes test support only from the production scan", () => {
    expect(knipConfig.ignore).toContain("dist/**");
    expect(knipConfig.ignore).toContain("**/test-helpers/**");
    expect(knipConfig.ignore).toContain("**/*.test-utils.ts");
    expect(knipConfig.ignoreFiles).not.toContain("**/test-helpers/**");
    expect(knipConfig.ignoreFiles).toContain("scripts/**");
    expect(allExportsKnipConfig.ignoreFiles).not.toContain("scripts/**");
    expect(knipConfig.ignoreFiles).toContain("dist/**");
    expect(knipConfig.ignore).not.toContain("**/live-*.ts");
    expect(knipConfig.ignoreFiles).toContain("**/live-*.ts");
    expect(allExportsKnipConfig.ignore).toEqual([
      "dist/**",
      "packages/*/dist/**",
      "**/.boundary-stubs/**",
    ]);
    expect(allExportsKnipConfig.ignoreIssues).toHaveProperty("test/fixtures/ts-topology/basic/**");
    expect(knipConfig.workspaces["."].project).toContain("scripts/**/*.{js,mjs,cjs,ts,mts,cts}!");
  });

  it("makes tests in every workspace roots of the full-tree export audit", () => {
    expect(knipConfig.workspaces["."].entry).not.toContain(
      "test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
    );
    expect(knipConfig.workspaces["."].entry).toEqual(
      expect.arrayContaining([
        "config/knip.config.ts!",
        "config/knip.all-exports.config.ts!",
        "config/knip.scripts-exports.config.ts!",
      ]),
    );
    expect(knipConfig.workspaces["."].project).toContain("config/**/*.{ts,mts,cts}!");
    expect(fullRootWorkspace.entry).toEqual(
      expect.arrayContaining([
        ".agents/skills/**/scripts/**/*.{js,mjs,cjs,ts,mts,cts}!",
        "src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
        "scripts/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
        "test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
        "test/vitest/vitest*.config.ts!",
      ]),
    );
    expect(fullExtensionWorkspace.entry).toContain("**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!");
    expect(fullUiWorkspace.entry).toContain("**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!");
  });

  it("models every QA scenario execution path as a full-tree root", () => {
    const rootEntries = fullRootWorkspace.entry;
    const executionPaths = listQaScenarioExecutionPaths();

    expect(executionPaths.length).toBeGreaterThan(0);
    for (const executionPath of executionPaths) {
      expect(fs.existsSync(executionPath), executionPath).toBe(true);
      expect(rootEntries, executionPath).toContain(`${executionPath}!`);
    }
    expect(rootEntries).toContain(
      "test/e2e/qa-lab/runtime/fixtures/voice-call-runtime-plugin/index.js!",
    );
  });

  it("keeps the script unused-export scan scoped to real executable roots", () => {
    expect(scriptRootWorkspace.entry).toEqual(
      expect.arrayContaining([
        ".agents/skills/**/scripts/**/*.{js,mjs,cjs,ts,mts,cts}!",
        ".github/actions/setup-node-env/dependency-fingerprint.mjs!",
        ".github/actions/register-bind-mount-cleanup/main.cjs!",
        "apps/android/scripts/build-release-artifacts.ts!",
        "security/opengrep/check-rule-metadata.mjs!",
        "skills/meme-maker/scripts/meme.mjs!",
        "scripts/check-live-cache.ts!",
        "scripts/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
        "test/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}!",
        "src/plugin-sdk/api-baseline.ts!",
      ]),
    );
    expect(scriptRootWorkspace.entry).not.toContain("scripts/**/*.{js,mjs,cjs,ts,mts,cts}!");
    expect(scriptExportsKnipConfig.ignoreIssues).toHaveProperty("src/**");
    expect(scriptExportsKnipConfig.ignoreIssues).toHaveProperty(
      "scripts/e2e/lib/bundled-plugin-install-uninstall/runtime-smoke.mjs",
    );
  });

  it("audits executable code outside the main source trees", () => {
    expect(knipConfig.workspaces["."].project).toEqual(
      expect.arrayContaining([
        ".github/actions/**/*.{js,mjs,cjs,ts,mts,cts}!",
        "apps/**/*.{js,mjs,cjs,ts,mts,cts}!",
        "config/**/*.{ts,mts,cts}!",
        "docs/**/*.js!",
        "security/**/*.{js,mjs,cjs,ts,mts,cts}!",
        "skills/**/*.{js,mjs,cjs,ts,mts,cts}!",
      ]),
    );
    expect(knipConfig.workspaces["."].entry).toContain("docs/nav-tabs-underline.js!");
    expect(knipConfig.workspaces["."].entry).toEqual(
      expect.arrayContaining([
        "config/knip.config.ts!",
        "config/knip.all-exports.config.ts!",
        "config/knip.scripts-exports.config.ts!",
      ]),
    );
    expect(knipConfig.workspaces["examples/ai-chat"]).toEqual({
      entry: ["index.mjs!"],
      project: ["**/*.{js,mjs,cjs,ts,mts,cts}!"],
    });
    expect(knipConfig.workspaces["qa/convex-credential-broker"].project).toContain(
      "convex/**/*.ts!",
    );
    expect(knipConfig.workspaces["qa/convex-credential-broker"].ignoreBinaries).toEqual(["convex"]);
  });

  it("tracks production script consumers of plugin exports", () => {
    expect(knipConfig.workspaces["."].entry).toContain("scripts/qa/render-maturity-docs.ts!");
  });

  it("runs exhaustive dead-code hygiene against production and full-tree configs", () => {
    const packageJson = JSON.parse(
      fs.readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
    ) as { scripts: Record<string, string> };
    expect(packageJson.scripts["deadcode:dependencies"]).toBe("pnpm deadcode:full");
    expect(packageJson.scripts["deadcode:full"]).toContain(
      "--config config/knip.config.ts --production",
    );
    expect(packageJson.scripts["deadcode:full"]).toContain(
      "--config config/knip.all-exports.config.ts",
    );
    expect(packageJson.scripts["deadcode:full"]).toContain("--exclude duplicates");
  });

  it("models the jiti virtual agent-sessions SDK entry", () => {
    expect(knipConfig.workspaces["."].entry).toContain("src/agents/sessions/extension-sdk.ts!");
  });

  it("models the spawned system-agent MCP stdio entry", () => {
    expect(knipConfig.workspaces["."].entry).toContain("src/mcp/openclaw-tools-serve.ts!");
  });

  it("scans every nested bundled-plugin source file without broad entry masking", () => {
    const extensionWorkspaces = Object.entries(knipConfig.workspaces).filter(([workspace]) =>
      workspace.startsWith("extensions/"),
    );
    expect(extensionWorkspaces.length).toBeGreaterThan(1);
    for (const [workspace, settings] of extensionWorkspaces) {
      expect(settings.entry, workspace).not.toContain("*.ts!");
      expect(settings.project, workspace).toContain("**/*.{js,mjs,ts}!");
      expect(settings.entry, workspace).toEqual(
        expect.arrayContaining([
          "index.ts!",
          "setup-entry.ts!",
          "*-api.ts!",
          "cli-metadata.ts!",
          "channel-entry.ts!",
          "provider-discovery.ts!",
          "{web-search,web-fetch}-provider.ts!",
        ]),
      );
    }
    expect(knipConfig.workspaces["extensions/*"].project).toContain("**/*.{js,mjs,ts}!");
    expect(knipConfig.workspaces["extensions/llama-cpp"].project).toContain("**/*.{js,mjs,ts}!");
    expect(knipConfig.workspaces["extensions/reef"].project).toContain("**/*.{js,mjs,ts}!");
  });

  it("models non-imported runtime and build entrypoints explicitly", () => {
    expect(knipConfig.workspaces["."].entry).toEqual(
      expect.arrayContaining([
        "src/agents/subagents/registry/subagent-registry.runtime.ts!",
        "src/mcp/plugin-tools-serve.ts!",
        "src/plugins/build-smoke-entry.ts!",
        "src/config/doc-baseline.ts!",
        "src/plugins/runtime-sidecar-paths-baseline.ts!",
        "tsdown.ai.config.ts!",
      ]),
    );
    expect(knipConfig.workspaces["extensions/acpx"].entry).toEqual(
      expect.arrayContaining([
        "src/runtime-internals/mcp-command-line.mjs!",
        "src/runtime-internals/mcp-proxy.mjs!",
      ]),
    );
    expect(knipConfig.workspaces["extensions/canvas"].entry).toEqual(
      expect.arrayContaining([
        "src/host/a2ui-app/bootstrap.js!",
        "src/host/a2ui-app/rolldown.config.mjs!",
      ]),
    );
    expect(knipConfig.workspaces["extensions/diffs"].entry).toContain("src/viewer-client.ts!");
    expect(knipConfig.workspaces["extensions/matrix"].entry).toContain(
      "src/plugin-entry.runtime.js!",
    );
    expect(knipConfig.workspaces["extensions/mxc"].entry).toContain("src/mxc-spawn-launcher.mjs!");
    expect(knipConfig.workspaces["extensions/qa-lab"].entry).toContain("src/ci-smoke-plan.ts!");
  });

  it("models the Browser facades loaded by basename", () => {
    const workspace = knipConfig.workspaces["extensions/browser"];
    expect(workspace.entry).toEqual(
      expect.arrayContaining([
        "browser-control-auth.ts!",
        "browser-config.ts!",
        "browser-doctor.ts!",
        "browser-maintenance.ts!",
        "browser-profiles.ts!",
      ]),
    );
  });

  it.each([
    "packages/agent-core",
    "packages/markdown-core",
    "packages/media-core",
    "packages/acp-core",
    "packages/terminal-core",
  ] as const)("mirrors the published entry map for %s", (workspace) => {
    const packageJson = JSON.parse(
      fs.readFileSync(new URL(`../../${workspace}/package.json`, import.meta.url), "utf8"),
    ) as { exports: Record<string, unknown> };
    const conventionalEntries = Object.keys(packageJson.exports).map((subpath) =>
      subpath === "." ? "src/index.ts!" : `src/${subpath.slice("./".length)}.ts!`,
    );
    const missingConventionalEntries = conventionalEntries.filter(
      (entry) =>
        !fs.existsSync(
          new URL(`../../${workspace}/${entry.slice(0, -"!".length)}`, import.meta.url),
        ),
    );
    expect(missingConventionalEntries).toEqual(
      workspace === "packages/agent-core"
        ? ["src/harness/compaction.ts!", "src/harness/branch-summarization.ts!"]
        : [],
    );
    if (workspace === "packages/agent-core") {
      for (const sourcePath of [
        "src/harness/compaction/compaction.ts",
        "src/harness/compaction/branch-summarization.ts",
      ]) {
        expect(
          fs.existsSync(new URL(`../../${workspace}/${sourcePath}`, import.meta.url)),
          sourcePath,
        ).toBe(true);
      }
    }
    const expected = conventionalEntries
      .filter((entry) => !missingConventionalEntries.includes(entry))
      .toSorted();
    expect([...knipConfig.workspaces[workspace].entry].toSorted()).toEqual(expected);
  });

  it("parses all compact export sections and expands symbol lists", () => {
    expect(
      parseKnipCompactUnusedExports(`
Unused exports (2)
src/b.ts: beta, alpha
/tmp/outside.ts: noise
C:\\tmp\\outside.ts: noise
C:outside.ts: noise
\\\\server\\share\\outside.ts: noise

Unused exported types (1)
extensions/example/src/types.ts: ExampleType

Unused exported enum members (1)
packages/example/src/state.ts: Ready

Exports in used namespace (1)
src/namespace.ts: runtimeHelper

Exported types in used namespace (1)
src/namespace.ts: RuntimeType

Unused exported namespace members (1)
src/protocol.ts: Result (v2)

Unused files (1)
src/noise.ts: src/noise.ts
`),
    ).toEqual([
      "extensions/example/src/types.ts: ExampleType",
      "packages/example/src/state.ts: Ready",
      "src/b.ts: alpha",
      "src/b.ts: beta",
      "src/namespace.ts: runtimeHelper",
      "src/namespace.ts: RuntimeType",
      "src/protocol.ts: Result (v2)",
    ]);
  });

  it("keeps findings from dot-directories and root entry files", () => {
    expect(
      parseKnipCompactUnusedExports(`Unused exports (2)
.agents/skills/example/scripts/check.mts: checkExample
tsdown.ai.config.ts: default
`),
    ).toEqual([
      ".agents/skills/example/scripts/check.mts: checkExample",
      "tsdown.ai.config.ts: default",
    ]);
  });

  it("distinguishes a failed scan with no export sections from zero findings", () => {
    expect(parseKnipCompactUnusedExportsResult("Configuration error: invalid project\n")).toEqual({
      entries: [],
      sawExportSection: false,
    });
    expect(parseKnipCompactUnusedExportsResult("Unused exports (0)\n")).toEqual({
      entries: [],
      sawExportSection: true,
    });
  });

  it("accepts an empty compact report with zero unused exports", () => {
    expect(checkUnusedExports("")).toEqual({
      ok: true,
      entries: [],
      message: "",
    });
  });

  it("rejects every unused export without an allowlist", () => {
    expect(
      checkUnusedExports(`Unused exports (2)
src/z.ts: zebra
src/a.ts: alpha
`),
    ).toEqual({
      ok: false,
      entries: ["src/a.ts: alpha", "src/z.ts: zebra"],
      message: `Unused exports are not allowed:
  src/a.ts: alpha
  src/z.ts: zebra
Delete the exports or model their real production consumers in Knip.`,
    });
  });

  it("discards export findings after workspace module resolution failures", () => {
    const output = `ERROR: Error loading vitest.config.ts (Cannot find module 'vitest/config')
ERROR: Error loading ui/vite.config.ts (Cannot find module 'vite')
Unused exports (1)
src/config/types.ts: TelegramConfig
`;

    const result = checkExportScan("production unused-export scan", output);
    expect(result.ok).toBe(false);
    expect(result.entries).toEqual([]);
    expect(result.message).toContain(
      "deadcode production unused-export scan could not resolve workspace modules; export findings would be unreliable and are discarded.",
    );
    expect(result.message).toContain("ERROR: Error loading vitest.config.ts");
    expect(result.message).toContain("ERROR: Error loading ui/vite.config.ts");
    expect(result.message).toContain("Install workspace dependencies in-tree (pnpm install)");
    expect(result.message).not.toContain("Unused exports are not allowed");
    expect(result.message).not.toContain("src/config/types.ts: TelegramConfig");
  });

  it("accepts clean export scan output unchanged", () => {
    expect(checkExportScan("clean scan", "")).toEqual({ entries: [], message: "", ok: true });
  });

  it("reports genuine export findings unchanged", () => {
    expect(
      checkExportScan("finding scan", "Unused exports (1)\nsrc/config/types.ts: TelegramConfig\n"),
    ).toEqual({
      entries: ["src/config/types.ts: TelegramConfig"],
      message: `finding scan:
Unused exports are not allowed:
  src/config/types.ts: TelegramConfig
Delete the exports or model their real production consumers in Knip.`,
      ok: false,
    });
  });
});
