import { defineCommand } from "citty";
import { resolve } from "node:path";
import type { Example } from "./_examples.js";
import { normalizeErrorMessage } from "../utils/errorMessage.js";

export const examples: Example[] = [
  ["Capture a website into ./capture/", "hyperframes capture https://stripe.com"],
  ["Capture to a different directory", "hyperframes capture https://linear.app -o linear-video"],
  ["JSON output for AI agents", "hyperframes capture https://example.com --json"],
  [
    "Pull a video from the captured manifest by index",
    "hyperframes capture --video ./linear-video --index 0",
  ],
  [
    "List videos referenced in the captured manifest",
    "hyperframes capture --video ./linear-video --list",
  ],
];

export default defineCommand({
  meta: {
    name: "capture",
    description: "Capture a website as editable HyperFrames components",
  },
  args: {
    url: {
      type: "positional",
      description: "Website URL to capture (omit when using --video)",
      required: false,
    },
    output: {
      type: "string",
      description: "Output directory name (default: ./capture, then ./capture-2/, ./capture-3/, …)",
      alias: "o",
    },
    "skip-assets": {
      type: "boolean",
      description: "Skip downloading assets (images, SVGs)",
      default: false,
    },
    "max-screenshots": {
      type: "string",
      description: "Maximum screenshots to capture (default: 24)",
    },
    timeout: {
      type: "string",
      description: "Page load timeout in ms (default: 120000)",
    },
    json: {
      type: "boolean",
      description: "Output JSON (for AI agents / programmatic use)",
      default: false,
    },
    video: {
      type: "string",
      description:
        "Switch to video-download mode: path to a captured project directory whose video-manifest.json should be read. Pair with --index, --video-url, or --list.",
    },
    index: {
      type: "string",
      description: "(--video mode) Manifest entry index to download (0-based)",
    },
    "video-url": {
      type: "string",
      description: "(--video mode) Exact video URL to download (must match a manifest entry)",
    },
    list: {
      type: "boolean",
      description: "(--video mode) List manifest entries and exit",
      default: false,
    },
  },
  // fallow-ignore-next-line complexity
  async run({ args }) {
    if (args.video) {
      const { runVideoMode } = await import("./capture/video.js");
      await runVideoMode({
        project: args.video as string,
        index: (args.index as string | undefined) ?? null,
        url: (args["video-url"] as string | undefined) ?? null,
        list: args.list as boolean,
      });
      return;
    }

    const url = args.url as string | undefined;
    if (!url) {
      console.error(
        "Missing URL. Pass a website URL, or use --video <project> for video download.",
      );
      process.exit(1);
    }

    try {
      new URL(url);
    } catch {
      console.error(`Invalid URL: ${url}`);
      process.exit(1);
    }

    const isDefaultOutput = !args.output;
    let outputName = (args.output as string | undefined) ?? "capture";
    let outputDir = resolve(outputName);

    if (isDefaultOutput) {
      const { existsSync } = await import("node:fs");
      // Auto-suffix when ./capture/ is taken: capture-2, capture-3, … so re-runs
      // never silently merge into a previous capture's artifacts.
      let n = 2;
      while (existsSync(outputDir) && n < 100) {
        outputName = `capture-${n}`;
        outputDir = resolve(outputName);
        n++;
      }
      if (existsSync(outputDir)) {
        console.error(`./capture-{2..99} are all taken. Pass -o <name> to pick a directory.`);
        process.exit(1);
      }
    }

    const isJson = args.json as boolean;

    if (!isJson) {
      const { c } = await import("../ui/colors.js");
      console.log();
      console.log(c.dim("◆") + "  Capturing " + c.bold(url));
      if (isDefaultOutput && outputName !== "capture") {
        console.log(`  ${c.dim(`(./capture/ exists; writing to ./${outputName}/)`)}`);
      }
      console.log();
    }

    const { captureWebsite } = await import("../capture/index.js");

    try {
      const result = await captureWebsite(
        {
          url,
          outputDir,
          skipAssets: args["skip-assets"] as boolean,
          maxScreenshots: args["max-screenshots"]
            ? parseInt(args["max-screenshots"] as string)
            : undefined,
          timeout: args.timeout ? parseInt(args.timeout as string) : undefined,
          json: isJson,
        },
        isJson
          ? undefined
          : (stage: string, detail?: string) => {
              const stages: Record<string, string> = {
                browser: "  Launching browser...",
                navigate: "  Loading page...",
                extract: "  Extracting HTML & CSS...",
                tokens: "  Extracting design tokens...",
                screenshots: "  Capturing screenshots...",
                assets: "  Downloading assets...",
                style: "  Generating visual style...",
                done: "  Done",
              };
              const label = stages[stage] || `  ${stage}`;
              console.log(detail ? `${label} ${detail}` : label);
            },
      );

      if (isJson) {
        // Output structured JSON for Claude Code / programmatic use
        console.log(
          JSON.stringify(
            {
              ok: result.ok,
              projectDir: result.projectDir,
              url: result.url,
              title: result.title,
              screenshots: result.screenshots.length,
              assets: result.assets.length,
              detectedSections: result.tokens.sections.length,
              fonts: result.tokens.fonts.map((f) => f.family),
              fontsDetailed: result.tokens.fonts,
              animations: result.animationCatalog?.summary,
              warnings: result.warnings,
            },
            null,
            2,
          ),
        );
      } else {
        const { c } = await import("../ui/colors.js");
        console.log();
        console.log(c.success("◇") + `  Captured ${c.bold(result.title)} → ${c.dim(outputDir)}`);
        console.log();
        console.log(`  ${c.dim("Screenshots:")} ${result.screenshots.length}`);
        console.log(`  ${c.dim("Assets:")} ${result.assets.length}`);
        console.log(`  ${c.dim("Sections:")} ${result.tokens.sections.length}`);
        console.log(
          `  ${c.dim("Fonts:")} ${result.tokens.fonts
            .map(function (f) {
              return (
                f.family +
                " (" +
                (f.variable && f.weightRange
                  ? f.weightRange[0] + "-" + f.weightRange[1] + " variable"
                  : f.weights.join(",")) +
                ")"
              );
            })
            .join(", ")}`,
        );
        if (result.warnings.length > 0) {
          console.log();
          for (const w of result.warnings) {
            console.log(`  ${c.warn("⚠")} ${w}`);
          }
        }
        console.log();
      }
    } catch (err) {
      const errMsg = normalizeErrorMessage(err);
      // Write BLOCKED.md so the user/agent knows the capture failed
      try {
        const { mkdirSync, writeFileSync } = await import("node:fs");
        mkdirSync(outputDir, { recursive: true });
        const isTimeout = /timeout|timed out/i.test(errMsg);
        const reason = isTimeout
          ? "Page navigation timed out — the site may be blocking headless browsers or requires authentication."
          : `Capture failed: ${errMsg}`;
        writeFileSync(
          `${outputDir}/BLOCKED.md`,
          `# Capture Failed\n\n${reason}\n\nURL: ${url}\n\n## What to try\n\n- Re-run with a longer timeout: \`--timeout 60000\`\n- The site may block headless browsers (anti-bot protection)\n- Try capturing a different page on the same domain\n`,
          "utf-8",
        );
      } catch {
        /* best-effort */
      }
      if (isJson) {
        console.log(JSON.stringify({ ok: false, error: errMsg }));
      } else {
        console.error(`\n  ✗ Capture failed: ${errMsg}\n`);
      }
      process.exit(1);
    }
  },
});
