// Generate (or verify) packages/cli/src/utils/agentDirs.generated.ts — the
// home-relative GLOBAL skills directory for every agent the upstream
// vercel-labs/skills CLI knows about, plus a marker dir that means "this agent
// is installed on this machine".
//
//   bun run --cwd packages/cli gen:agent-dirs           # write/update (fetches upstream)
//   bun run --cwd packages/cli gen:agent-dirs --check   # verify only (CI / pre-commit)
//   bun packages/cli/scripts/sync-agent-dirs.ts --src <path/to/agents.ts>   # offline
//
// Why generated, not hand-maintained: `skills add --global` installs into the
// per-agent dirs encoded in upstream's `src/agents.ts` (~70 agents). We mirror
// the canonical store into those same dirs, so the list must track upstream. The
// `skills` npm package exports nothing importable (CLI-only, bundled dist), so
// we parse the source at a PINNED tag and commit the result — deterministic at
// runtime, no network at install time. Bump SKILLS_REF and re-run on upgrade.

import { readFileSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

// Pin to the upstream release whose dir layout we install against. Bump this
// (and re-run) when the bundled `skills` version moves.
const SKILLS_REPO = "vercel-labs/skills";
const SKILLS_REF = "v1.5.13";
const AGENTS_TS_URL = `https://raw.githubusercontent.com/${SKILLS_REPO}/${SKILLS_REF}/src/agents.ts`;

const here = dirname(fileURLToPath(import.meta.url));
const outPath = join(here, "..", "src", "utils", "agentDirs.generated.ts");

const isCheck = process.argv.includes("--check");
const srcFlag = process.argv.indexOf("--src");
const srcPath = srcFlag !== -1 ? process.argv[srcFlag + 1] : undefined;

// The base directories agents.ts builds globalSkillsDir from. Each is an env
// override with a documented default — the mirror resolves them at runtime so a
// machine with XDG_CONFIG_HOME / CODEX_HOME / CLAUDE_CONFIG_DIR set lands in the
// same place the agent actually reads. We store the base NAME + suffix here
// rather than a frozen path so that resolution stays faithful to upstream.
const BASE_VARS = [
  "home",
  "configHome",
  "codexHome",
  "claudeHome",
  "vibeHome",
  "hermesHome",
  "autohandHome",
] as const;
type BaseVar = (typeof BASE_VARS)[number];

interface AgentGlobalDir {
  agent: string;
  base: BaseVar;
  sub: string;
}

function posixJoin(parts: string[]): string {
  return parts
    .flatMap((p) => p.split("/"))
    .filter((s) => s && s !== ".")
    .join("/");
}

/** Resolve a globalSkillsDir expression to { base, sub }, or null if none. */
function resolveGlobalExpr(expr: string): { base: BaseVar; sub: string } | null {
  const e = expr.trim();
  if (e === "undefined") return null; // agent defines no global skills dir
  // openclaw's helper falls back to ~/.openclaw/skills when no variant is present.
  if (e.startsWith("getOpenClawGlobalSkillsDir")) return { base: "home", sub: ".openclaw/skills" };
  const m = e.match(/^join\(([\s\S]+)\)$/);
  if (!m) throw new Error(`Unparseable globalSkillsDir: ${expr}`);
  const args = m[1]!.split(",").map((a) => a.trim());
  const first = args[0]!;
  if (!(BASE_VARS as readonly string[]).includes(first)) {
    throw new Error(`globalSkillsDir does not start with a known base var: ${expr}`);
  }
  const segs: string[] = [];
  for (const raw of args.slice(1)) {
    const lit = raw.match(/^['"]([^'"]*)['"]$/);
    if (!lit) throw new Error(`Non-literal segment "${raw}" in globalSkillsDir: ${expr}`);
    segs.push(lit[1]!);
  }
  return { base: first as BaseVar, sub: posixJoin(segs) };
}

function parseAgents(source: string): AgentGlobalDir[] {
  const re = /^ {2}(?:"([a-z0-9-]+)"|'([a-z0-9-]+)'|([a-z0-9-]+)):\s*\{/gm;
  const blocks: { key: string; pos: number }[] = [];
  let m: RegExpExecArray | null;
  while ((m = re.exec(source))) blocks.push({ key: m[1] || m[2] || m[3]!, pos: m.index });
  if (blocks.length < 60) {
    throw new Error(`Parsed only ${blocks.length} agent blocks — upstream layout likely changed`);
  }

  const out: AgentGlobalDir[] = [];
  for (let i = 0; i < blocks.length; i++) {
    const seg = source.slice(blocks[i]!.pos, blocks[i + 1] ? blocks[i + 1]!.pos : source.length);
    const gd = seg.match(/globalSkillsDir:\s*([\s\S]+?),\n/);
    const expr = gd ? gd[1]!.trim().replace(/\s+/g, " ") : "undefined";
    const resolved = resolveGlobalExpr(expr);
    if (resolved === null) continue; // no global dir (e.g. eve, promptscript)
    out.push({ agent: blocks[i]!.key, base: resolved.base, sub: resolved.sub });
  }
  return out;
}

function render(rows: AgentGlobalDir[]): string {
  const lines = rows.map(
    (r) =>
      `  { agent: ${JSON.stringify(r.agent)}, base: ${JSON.stringify(r.base)}, sub: ${JSON.stringify(r.sub)} },`,
  );
  return `// @generated by packages/cli/scripts/sync-agent-dirs.ts — DO NOT EDIT.
// Source: ${SKILLS_REPO}@${SKILLS_REF} (src/agents.ts). Regenerate with:
//   bun run --cwd packages/cli gen:agent-dirs
//
// Each entry is one agent the upstream \`skills\` CLI installs to. The agent's
// GLOBAL skills directory is \`join(<base>, <sub>)\`, where \`base\` is one of the
// env-overridable home dirs below (resolved at runtime by skillsMirror.ts, so
// XDG_CONFIG_HOME / CODEX_HOME / CLAUDE_CONFIG_DIR are honored). Agents with no
// global skills dir upstream (eve, promptscript) are omitted.

/** Env-overridable base dirs, matching upstream agents.ts. */
export type AgentDirBase =
${BASE_VARS.map((b) => `  | ${JSON.stringify(b)}`).join("\n")};

export interface AgentGlobalDir {
  /** Upstream agent key. */
  agent: string;
  /** Base directory the global skills dir is rooted at. */
  base: AgentDirBase;
  /** POSIX suffix joined onto the resolved base. */
  sub: string;
}

export const AGENT_GLOBAL_DIRS: readonly AgentGlobalDir[] = [
${lines.join("\n")}
];
`;
}

async function loadAgentsSource(): Promise<string> {
  if (srcPath) return readFileSync(srcPath, "utf8");
  const res = await fetch(AGENTS_TS_URL);
  if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${AGENTS_TS_URL}`);
  return res.text();
}

const source = await loadAgentsSource();
const rows = parseAgents(source);
const next = render(rows);

if (isCheck) {
  let current = "";
  try {
    current = readFileSync(outPath, "utf8");
  } catch {
    /* missing → drift */
  }
  if (current !== next) {
    console.error(
      `agentDirs.generated.ts is out of date (parsed ${rows.length} agents from ${SKILLS_REPO}@${SKILLS_REF}).\n` +
        `Run: bun run --cwd packages/cli gen:agent-dirs`,
    );
    process.exit(1);
  }
  console.log(`agentDirs.generated.ts is up to date (${rows.length} agents).`);
} else {
  writeFileSync(outPath, next, "utf8");
  console.log(`Wrote ${rows.length} agents → ${outPath}`);
}
