import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";

import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";

type ProcessResult = { ok: boolean; exitCode: number; stdout: string; stderr: string };

function resolveSkillRoot(cwd: string): string {
  const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
  const candidates = [
    process.env.DEVFLOW_KIT_ROOT,
    path.join(cwd, ".skills"),
    path.resolve(moduleDirectory, "../skills"),
    path.resolve(moduleDirectory, "../../.."),
  ].filter(Boolean) as string[];
  for (const candidate of candidates) {
    if (existsSync(path.join(candidate, "workflow-code-index/scripts/code_index.py"))) {
      return path.resolve(candidate);
    }
  }
  throw new Error("Devflow intel skills not found");
}

/**
 * Resolve the Devflow project root from the runtime directory. A codebase
 * (for example `drama-ad/backend`) may be nested below the project root;
 * `.planning` belongs to the nearest ancestor project, never to the codebase
 * directory itself.
 */
function resolveProjectRoot(cwd: string): string {
  let current = path.resolve(cwd);
  while (true) {
    if (
      existsSync(path.join(current, ".devflow", "project.json")) ||
      existsSync(path.join(current, ".planning", "code-index", "index.sqlite")) ||
      existsSync(path.join(current, ".planning", "PROJECT.md"))
    ) {
      return current;
    }
    const parent = path.dirname(current);
    if (parent === current) break;
    current = parent;
  }
  return path.resolve(cwd);
}

async function run(command: string[], cwd: string): Promise<ProcessResult> {
  const child = Bun.spawn(command, { cwd, stdout: "pipe", stderr: "pipe" });
  const [exitCode, stdout, stderr] = await Promise.all([
    child.exited,
    new Response(child.stdout).text(),
    new Response(child.stderr).text(),
  ]);
  return { ok: exitCode === 0, exitCode, stdout: stdout.trim(), stderr: stderr.trim() };
}

function render(result: ProcessResult, details: Record<string, unknown>) {
  return {
    content: [
      {
        type: "text" as const,
        text: result.stdout || result.stderr || `Process exited with ${result.exitCode}`,
      },
    ],
    details: { ...details, exitCode: result.exitCode, stderr: result.stderr || undefined },
    isError: !result.ok,
  };
}

export default function devflowIntel(pi: ExtensionAPI) {
  const { z } = pi.zod;

  pi.registerTool({
    name: "devflow_code_index",
    label: "Devflow Code Index",
    description: "Scan, validate, audit, or query the deterministic project code index.",
    parameters: z.object({
      action: z.enum(["scan", "validate", "audit", "query", "relations", "impact", "search-content", "resolve-domain", "query-plan", "domain-import"]),
      term: z.string().optional(),
      object: z.string().optional(),
      entity: z.string().optional(),
      projectId: z.string().default("default"),
      plan: z.record(z.string(), z.unknown()).optional(),
      bundle: z.record(z.string(), z.unknown()).optional(),
      repository: z.string().optional(),
      pathPrefix: z.string().optional(),
      language: z.string().optional(),
      matchKind: z.enum(["literal", "identifier", "phrase", "substring"]).default("literal"),
      direction: z.enum(["incoming", "outgoing", "both"]).default("outgoing"),
      expectedRevision: z.string().optional(),
      changeType: z.enum(["remove_field", "rename_field", "change_type", "optional_to_required", "required_to_optional", "change_nullability", "change_enum"]).optional(),
      snapshot: z.string().optional(),
      mappingRevision: z.string().optional(),
      depth: z.number().int().min(0).max(20).default(3),
      includeHeuristic: z.boolean().default(false),
      limit: z.number().int().min(1).max(100).default(20),
      changedFiles: z.array(z.string()).default([]),
      validationLevel: z.enum(["fast", "publish", "full"]).default("publish"),
    }),
    approval: (args) => {
      const action = (args as { action?: string }).action;
      // scan 写 .planning/code-index，按 exec 询问；validate/audit/query 只读，免确认。
      return ["scan", "domain-import"].includes(action ?? "") ? "exec" : "read";
    },
    async execute(_id, params, _signal, _onUpdate, ctx) {
      const projectRoot = resolveProjectRoot(ctx.cwd);
      const skillRoot = resolveSkillRoot(projectRoot);
      const script = path.join(skillRoot, "workflow-code-index/scripts/code_index.py");
      const args = ["python3", script, params.action];
      if (params.action === "scan") {
        args.push("--root", projectRoot, "--output", ".planning/code-index");
        if (params.changedFiles.length > 0) args.push("--changed-files", ...params.changedFiles);
      } else if (params.action === "validate") {
        args.push("--index", ".planning/code-index");
        args.push("--level", params.validationLevel);
        if (params.snapshot) args.push("--snapshot", params.snapshot);
      } else if (params.action === "audit") {
        args.push("--root", projectRoot, "--index", ".planning/code-index", "--skill-root", skillRoot);
        args.push("--level", params.validationLevel === "fast" ? "publish" : params.validationLevel);
        if (params.snapshot) args.push("--snapshot", params.snapshot);
      } else if (params.action === "query") {
        if (!params.term) {
          return render({ ok: false, exitCode: 2, stdout: "", stderr: "term is required" }, {});
        }
        args.push("--index", ".planning/code-index", "--term", params.term, "--limit", String(params.limit));
        if (params.snapshot) args.push("--snapshot", params.snapshot);
      } else {
        if (params.action === "search-content") {
          if (!params.term) return render({ ok: false, exitCode: 2, stdout: "", stderr: "term is required" }, {});
          args.push("--index", ".planning/code-index", "--term", params.term, "--limit", String(params.limit));
          args.push("--project-id", params.projectId);
          args.push("--match-kind", params.matchKind === "phrase" ? "exact_phrase" : params.matchKind);
          if (params.repository) args.push("--repository", params.repository);
          if (params.pathPrefix) args.push("--path-prefix", params.pathPrefix);
          if (params.language) args.push("--language", params.language);
          return render(await run(args, projectRoot), { action: params.action, projectRoot });
        } else if (params.action === "resolve-domain") {
          if (!params.term) return render({ ok: false, exitCode: 2, stdout: "", stderr: "term is required" }, {});
          args.push("--index", ".planning/code-index", "--term", params.term, "--project-id", params.projectId, "--limit", String(params.limit));
          if (params.mappingRevision) args.push("--mapping-revision", params.mappingRevision);
          return render(await run(args, projectRoot), { action: params.action, projectRoot });
        } else if (params.action === "query-plan") {
          if (!params.plan) return render({ ok: false, exitCode: 2, stdout: "", stderr: "plan is required" }, {});
          const plan = { ...params.plan };
          if (plan.limit === undefined) plan.limit = params.limit;
          args.push("--index", ".planning/code-index", "--project-id", params.projectId, "--plan", JSON.stringify(plan));
          if (params.snapshot) args.push("--snapshot", params.snapshot);
          return render(await run(args, projectRoot), { action: params.action, projectRoot });
        } else if (params.action === "domain-import") {
          if (!params.bundle) return render({ ok: false, exitCode: 2, stdout: "", stderr: "bundle is required" }, {});
          const bundleDir = mkdtempSync(path.join(tmpdir(), "devflow-domain-"));
          const bundlePath = path.join(bundleDir, "bundle.json");
          try {
            writeFileSync(bundlePath, JSON.stringify(params.bundle), { encoding: "utf8", mode: 0o600 });
            args.push("--index", ".planning/code-index", "--project-id", params.projectId, "--bundle", bundlePath);
            if (params.expectedRevision) args.push("--expected-revision", params.expectedRevision);
            return render(await run(args, projectRoot), { action: params.action, projectRoot });
          } finally {
            rmSync(bundleDir, { recursive: true, force: true });
          }
        }
        if (params.action === "impact") {
          if (!params.entity) return render({ ok: false, exitCode: 2, stdout: "", stderr: "entity is required" }, {});
          args.push("--index", ".planning/code-index", "--entity", params.entity, "--depth", String(params.depth), "--limit", String(params.limit));
          if (params.snapshot) args.push("--snapshot", params.snapshot);
          if (params.changeType) args.push("--change-type", params.changeType);
          return render(await run(args, projectRoot), { action: params.action, projectRoot });
        }
        if (!params.object) return render({ ok: false, exitCode: 2, stdout: "", stderr: "object is required" }, {});
        args.push("--index", ".planning/code-index", "--object", params.object, "--depth", String(params.depth), "--limit", String(params.limit));
        args.push("--direction", params.direction);
        if (params.snapshot) args.push("--snapshot", params.snapshot);
        if (params.includeHeuristic) args.push("--include-heuristic");
      }
      return render(await run(args, projectRoot), { action: params.action, projectRoot });
    },
  });

  pi.registerTool({
    name: "devflow_context_cursor",
    label: "Devflow Context Cursor",
    description: "Inspect or update repository cursors used by deterministic context reconciliation.",
    parameters: z.object({ args: z.array(z.string()).default([]) }),
    approval: "exec",
    async execute(_id, params, _signal, _onUpdate, ctx) {
      const skillRoot = resolveSkillRoot(ctx.cwd);
      const script = path.join(
        skillRoot,
        "workflow-context-sync/scripts/context_sync_cursor.py",
      );
      return render(await run(["python3", script, ...params.args], ctx.cwd), {
        action: "context-cursor",
      });
    },
  });
}
