import type { Command } from "commander";
import { danger } from "../globals.js";
import { defaultRuntime } from "../runtime.js";
import { shortenHomePath } from "../utils.js";
import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js";
import { runCommandWithRuntime } from "./cli-utils.js";

const BROWSER_DEBUG_TIMEOUT_MS = 20000;

type BrowserRequestParams = Parameters<typeof callBrowserRequest>[1];

type DebugContext = {
  parent: BrowserParentOpts;
  profile?: string;
};

function runBrowserDebug(action: () => Promise<void>) {
  return runCommandWithRuntime(defaultRuntime, action, (err) => {
    defaultRuntime.error(danger(String(err)));
    defaultRuntime.exit(1);
  });
}

async function withDebugContext(
  cmd: Command,
  parentOpts: (cmd: Command) => BrowserParentOpts,
  action: (context: DebugContext) => Promise<void>,
) {
  const parent = parentOpts(cmd);
  await runBrowserDebug(() =>
    action({
      parent,
      profile: parent.browserProfile,
    }),
  );
}

function printJsonResult(parent: BrowserParentOpts, result: unknown): boolean {
  if (!parent.json) {
    return false;
  }
  defaultRuntime.log(JSON.stringify(result, null, 2));
  return true;
}

async function callDebugRequest<T>(
  parent: BrowserParentOpts,
  params: BrowserRequestParams,
): Promise<T> {
  return callBrowserRequest<T>(parent, params, { timeoutMs: BROWSER_DEBUG_TIMEOUT_MS });
}

function resolveProfileQuery(profile?: string) {
  return profile ? { profile } : undefined;
}

function resolveDebugQuery(params: {
  targetId?: unknown;
  clear?: unknown;
  profile?: string;
  filter?: unknown;
}) {
  return {
    targetId: typeof params.targetId === "string" ? params.targetId.trim() || undefined : undefined,
    filter: typeof params.filter === "string" ? params.filter.trim() || undefined : undefined,
    clear: Boolean(params.clear),
    profile: params.profile,
  };
}

export function registerBrowserDebugCommands(
  browser: Command,
  parentOpts: (cmd: Command) => BrowserParentOpts,
) {
  browser
    .command("highlight")
    .description("Highlight an element by ref")
    .argument("<ref>", "Ref id from snapshot")
    .option("--target-id <id>", "CDP target id (or unique prefix)")
    .action(async (ref: string, opts, cmd) => {
      await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
        const result = await callDebugRequest(parent, {
          method: "POST",
          path: "/highlight",
          query: resolveProfileQuery(profile),
          body: {
            ref: ref.trim(),
            targetId: opts.targetId?.trim() || undefined,
          },
        });
        if (printJsonResult(parent, result)) {
          return;
        }
        defaultRuntime.log(`highlighted ${ref.trim()}`);
      });
    });

  browser
    .command("errors")
    .description("Get recent page errors")
    .option("--clear", "Clear stored errors after reading", false)
    .option("--target-id <id>", "CDP target id (or unique prefix)")
    .action(async (opts, cmd) => {
      await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
        const result = await callDebugRequest<{
          errors: Array<{ timestamp: string; name?: string; message: string }>;
        }>(parent, {
          method: "GET",
          path: "/errors",
          query: resolveDebugQuery({
            targetId: opts.targetId,
            clear: opts.clear,
            profile,
          }),
        });
        if (printJsonResult(parent, result)) {
          return;
        }
        if (!result.errors.length) {
          defaultRuntime.log("No page errors.");
          return;
        }
        defaultRuntime.log(
          result.errors
            .map((e) => `${e.timestamp} ${e.name ? `${e.name}: ` : ""}${e.message}`)
            .join("\n"),
        );
      });
    });

  browser
    .command("requests")
    .description("Get recent network requests (best-effort)")
    .option("--filter <text>", "Only show URLs that contain this substring")
    .option("--clear", "Clear stored requests after reading", false)
    .option("--target-id <id>", "CDP target id (or unique prefix)")
    .action(async (opts, cmd) => {
      await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
        const result = await callDebugRequest<{
          requests: Array<{
            timestamp: string;
            method: string;
            status?: number;
            ok?: boolean;
            url: string;
            failureText?: string;
          }>;
        }>(parent, {
          method: "GET",
          path: "/requests",
          query: resolveDebugQuery({
            targetId: opts.targetId,
            filter: opts.filter,
            clear: opts.clear,
            profile,
          }),
        });
        if (printJsonResult(parent, result)) {
          return;
        }
        if (!result.requests.length) {
          defaultRuntime.log("No requests recorded.");
          return;
        }
        defaultRuntime.log(
          result.requests
            .map((r) => {
              const status = typeof r.status === "number" ? ` ${r.status}` : "";
              const ok = r.ok === true ? " ok" : r.ok === false ? " fail" : "";
              const fail = r.failureText ? ` (${r.failureText})` : "";
              return `${r.timestamp} ${r.method}${status}${ok} ${r.url}${fail}`;
            })
            .join("\n"),
        );
      });
    });

  const trace = browser.command("trace").description("Record a Playwright trace");

  trace
    .command("start")
    .description("Start trace recording")
    .option("--target-id <id>", "CDP target id (or unique prefix)")
    .option("--no-screenshots", "Disable screenshots")
    .option("--no-snapshots", "Disable snapshots")
    .option("--sources", "Include sources (bigger traces)", false)
    .action(async (opts, cmd) => {
      await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
        const result = await callDebugRequest(parent, {
          method: "POST",
          path: "/trace/start",
          query: resolveProfileQuery(profile),
          body: {
            targetId: opts.targetId?.trim() || undefined,
            screenshots: Boolean(opts.screenshots),
            snapshots: Boolean(opts.snapshots),
            sources: Boolean(opts.sources),
          },
        });
        if (printJsonResult(parent, result)) {
          return;
        }
        defaultRuntime.log("trace started");
      });
    });

  trace
    .command("stop")
    .description("Stop trace recording and write a .zip")
    .option(
      "--out <path>",
      "Output path within openclaw temp dir (e.g. trace.zip or /tmp/openclaw/trace.zip)",
    )
    .option("--target-id <id>", "CDP target id (or unique prefix)")
    .action(async (opts, cmd) => {
      await withDebugContext(cmd, parentOpts, async ({ parent, profile }) => {
        const result = await callDebugRequest<{ path: string }>(parent, {
          method: "POST",
          path: "/trace/stop",
          query: resolveProfileQuery(profile),
          body: {
            targetId: opts.targetId?.trim() || undefined,
            path: opts.out?.trim() || undefined,
          },
        });
        if (printJsonResult(parent, result)) {
          return;
        }
        defaultRuntime.log(`TRACE:${shortenHomePath(result.path)}`);
      });
    });
}
