import { readFile } from "node:fs/promises"
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent"

// browse_page — cloud browser (Browserbase) page fetch over raw CDP, ported from
// ahel/.opencode/tools/browse.ts (opencode plugin) to an omp extension.
// No local browser, no deps. Returns rendered-page text, trimmed hard to 4k chars.

const ENV_PATH = `${process.env.HOME}/.config/ahel/browserbase.env`
const MAX_CHARS = 4000
const NAV_TIMEOUT_MS = 20000

async function loadEnv(): Promise<Record<string, string>> {
  const out: Record<string, string> = {}
  const text = await readFile(ENV_PATH, "utf8")
  for (const line of text.split("\n")) {
    const m = line.match(/^([A-Z_]+)=(.+)$/)
    if (m) out[m[1]] = m[2]
  }
  return out
}

async function fetchPage(url: string): Promise<string> {
  const env = await loadEnv()
  if (!env.BROWSERBASE_API_KEY || !env.BROWSERBASE_PROJECT_ID) {
    return JSON.stringify({ error: "browserbase credentials missing" })
  }
  const sess: any = await (await fetch("https://api.browserbase.com/v1/sessions", {
    method: "POST",
    headers: { "X-BB-API-Key": env.BROWSERBASE_API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ projectId: env.BROWSERBASE_PROJECT_ID }),
  })).json()
  if (!sess.connectUrl) return JSON.stringify({ error: "session create failed", detail: sess })

  const ws = new WebSocket(sess.connectUrl)
  let msgId = 0
  const pending = new Map<number, (v: any) => void>()
  let loaded = false
  let attached = ""

  const send = (method: string, params: any = {}, sessionId?: string): Promise<any> => {
    const id = ++msgId
    return new Promise((resolve) => {
      pending.set(id, resolve)
      ws.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) }))
    })
  }

  try {
    await new Promise((resolve, reject) => {
      ws.onopen = resolve
      ws.onerror = () => reject(new Error("cdp connect failed"))
    })
    ws.onmessage = (e) => {
      const m = JSON.parse(e.data as string)
      if (m.id && pending.has(m.id)) { pending.get(m.id)!(m); pending.delete(m.id) }
      else if (m.method === "Page.loadEventFired" && m.sessionId === attached) loaded = true
    }

    const targets = await send("Target.getTargets")
    const page = targets.result.targetInfos.find((t: any) => t.type === "page")
    const attach = await send("Target.attachToTarget", { targetId: page.targetId, flatten: true })
    attached = attach.result.sessionId

    await send("Page.enable", {}, attached)
    await send("Page.navigate", { url }, attached)
    const deadline = Date.now() + NAV_TIMEOUT_MS
    while (!loaded && Date.now() < deadline) await new Promise((r) => setTimeout(r, 200))
    await new Promise((r) => setTimeout(r, 1000))

    const res = await send("Runtime.evaluate", {
      expression:
        "JSON.stringify({title: document.title, text: document.body.innerText})",
      returnByValue: true,
    }, attached)
    const parsed = JSON.parse(res.result.result.value || "{}")
    const text = (parsed.text || "").replace(/\n{3,}/g, "\n\n").slice(0, MAX_CHARS)
    return `# ${parsed.title || url}\n${text}`
  } catch (err: any) {
    return JSON.stringify({ error: String(err?.message || err) })
  } finally {
    try { ws.close() } catch {}
  }
}

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

  pi.registerTool({
    name: "browse_page",
    label: "Browse Page",
    description:
      "Fetch a web page in a real cloud browser (renders JS, passes bot walls) and return its text, trimmed to 4k chars. Use when curl/webfetch fails or the page needs JS.",
    parameters: z.object({
      url: z.string().describe("Full URL to fetch"),
    }),
    async execute(_toolCallId, params) {
      const text = await fetchPage(params.url)
      return { content: [{ type: "text", text }] }
    },
  })
}
