const cfcBase =
  "https://cfc.aroic.workers.dev/" || "http://localhost:8787/" || ""

export function isMatch(u, includes) {
  if (typeof u == "string") {
    u = new URL(u, location?.origin)
  }
  return includes.some((v) => {
    if (u.host == v) return !0
    if (u.href.startsWith(v)) return !0
    if (u.pathname.startsWith(v)) return !0
    if (v[0] == "*" && (u.host + u.pathname).indexOf(v.slice(1)) != -1)
      return !0
    return !1
  })
}

async function clearApiKeyLogin() {
  const { accessToken } = await chrome.storage.local.get({ accessToken: "" })
  const payload = JSON.parse(
    (accessToken && atob(accessToken.split(".")[1] || "")) || "{}"
  )
  if (payload && payload.iss == "auth") {
    await chrome.storage.local.set({
      accessToken: "",
      refreshToken: "",
      tokenExpiry: 0,
    })
    await getOptions(!0)
  }
}

if (!globalThis.__cfc_options) {
  globalThis.__cfc_options = {
    mode: "",
    cfcBase: cfcBase,
    anthropicBaseUrl: "",
    apiBaseIncludes: ["https://api.anthropic.com/v1/"],
    proxyIncludes: [
      "cdn.segment.com",
      "featureassets.org",
      "assetsconfigcdn.org",
      "featuregates.org",
      "api.segment.io",
      "prodregistryv2.org",
      "beyondwickedmapping.org",
      "api.honeycomb.io",
      "statsigapi.net",
      "events.statsigapi.net",
      "api.statsigcdn.com",
      "*ingest.us.sentry.io",
      "https://api.anthropic.com/api/oauth/profile",
      "https://console.anthropic.com/v1/oauth/token",

      "https://api.anthropic.com/api/oauth/account",
      "https://api.anthropic.com/api/oauth/organizations",
      "https://api.anthropic.com/api/oauth/chat_conversations",

      "/api/web/domain_info/browser_extension",
    ],
    discardIncludes: [
      "cdn.segment.com",
      "api.segment.io",
      "events.statsigapi.net",
      "api.honeycomb.io",
      "prodregistryv2.org",
    ],
    modelAlias: {},
    ui: {},
    uiNodes: [],
  }
}

let _optionsPromise = null
let _updateAt = 0

export async function getOptions(force = false) {
  const fetch = globalThis.__fetch
  const options = globalThis.__cfc_options
  const baseUrl = options.cfcBase || cfcBase

  if (!_optionsPromise && (force || Date.now() - _updateAt > 1000 * 3600)) {
    _optionsPromise = new Promise(async (resolve) => {
      setTimeout(resolve, 1000 * 2.8)
      try {
        const id = chrome.runtime.id
        const manifest = chrome.runtime.getManifest()
        const url = baseUrl + `api/options?id=${id}&v=${manifest.version}`
        const res = await fetch(url, {
          headers: force ? { "Cache-Control": "no-cache" } : {},
        })
        const {
          mode,
          cfcBase,
          anthropicBaseUrl,
          apiBaseIncludes,
          proxyIncludes,
          discardIncludes,
          modelAlias,
          ui,
          uiNodes,
        } = await res.json()
        options.mode = mode
        options.cfcBase = cfcBase || options.cfcBase
        options.anthropicBaseUrl = anthropicBaseUrl || options.anthropicBaseUrl
        options.apiBaseIncludes = apiBaseIncludes || options.apiBaseIncludes
        options.proxyIncludes = proxyIncludes || options.proxyIncludes
        options.discardIncludes = discardIncludes || options.discardIncludes
        options.modelAlias = modelAlias || options.modelAlias
        options.ui = ui || options.ui
        options.uiNodes = uiNodes || options.uiNodes
        _updateAt = Date.now()

        if (mode == "claude") {
          await clearApiKeyLogin()
        }
      } finally {
        resolve()
        _optionsPromise = null
      }
    })
  }

  if (_optionsPromise) {
    await _optionsPromise
  }

  return options
}

if (!globalThis.__fetch) {
  globalThis.__fetch = fetch
}

export async function request(input, init) {
  const fetch = globalThis.__fetch
  const u = new URL(input, location?.origin)
  const {
    proxyIncludes,
    mode,
    cfcBase,
    anthropicBaseUrl,
    apiBaseIncludes,
    discardIncludes,
    modelAlias,
  } = await getOptions()

  try {
    if (
      u.href.startsWith("https://console.anthropic.com/v1/oauth/token") &&
      typeof init?.body == "string"
    ) {
      const p = new URLSearchParams(init.body)
      const code = p.get("code")
      if (code && !code.startsWith("cfc-")) {
        return fetch(input, init)
      }
    }
  } catch (e) {
    console.log(e)
  }
  if (mode != "claude" && isMatch(u, apiBaseIncludes)) {
    const apiBase =
      globalThis.localStorage?.getItem("apiBaseUrl") ||
      anthropicBaseUrl ||
      u.origin
    const url = apiBase + u.pathname + u.search
    try {
      if (init?.method == "POST" && typeof init.body == "string") {
        const body = JSON.parse(init.body)
        const { model } = body
        if (model && modelAlias[model]) {
          body.model = modelAlias[model]
          init.body = JSON.stringify(body)
        }
      }
    } catch (e) {}
    return fetch(url, init)
  }
  if (isMatch(u, discardIncludes)) {
    const url = (cfcBase + u.href).replace("/https://", "/")
    return new Response(null, { status: 204 })
  }
  if (isMatch(u, proxyIncludes)) {
    const url = cfcBase + u.href
    return fetch(url, init)
  }
  return fetch(input, init)
}

request.toString = () => globalThis.__fetch.toString()

globalThis.fetch = request

if (globalThis.XMLHttpRequest) {
  if (!globalThis.__xhrOpen) {
    globalThis.__xhrOpen = XMLHttpRequest?.prototype?.open
  }
  XMLHttpRequest.prototype.open = function (method, url, ...args) {
    const originalOpen = globalThis.__xhrOpen
    const { cfcBase, proxyIncludes, discardIncludes } = globalThis.__cfc_options
    let finalUrl = url

    console.log("open", url, isMatch(url, discardIncludes), discardIncludes)
    if (isMatch(url, proxyIncludes)) {
      finalUrl = cfcBase + url
    }
    if (isMatch(url, discardIncludes)) {
      finalUrl = (cfcBase + url).replace("/https://", "/")
      // finalUrl = "data:text/plain;base64,"
      method = "GET"
    }
    originalOpen.call(this, method, finalUrl, ...args)
  }
}

if (!globalThis.__createTab) {
  globalThis.__createTab = chrome?.tabs?.create
}
chrome.tabs.create = async function (...args) {
  const url = args[0]?.url
  if (url && url.startsWith("https://claude.ai/oauth/authorize")) {
    const { cfcBase, mode } = await getOptions()
    const m = chrome.runtime.getManifest()
    if (mode !== "claude") {
      args[0].url =
        url
          .replace("https://claude.ai/", cfcBase)
          .replace("fcoeoabgfenejglbffodgkkbkcdhcgfn", chrome.runtime.id) +
        `&v=${m.version}`
    }
  }
  if (url && url == "https://claude.ai/upgrade?max=c") {
    const { cfcBase, mode } = await getOptions()
    if (mode !== "claude") {
      args[0].url = cfcBase + "?from=" + encodeURIComponent(url)
    }
  }
  return __createTab.apply(chrome.tabs, args)
}

chrome.runtime.onMessageExternal.addListener(async (msg) => {
  switch (msg?.type) {
    case "_claude_account_mode":
      await clearApiKeyLogin()
      break
    case "_api_key_mode":
      await getOptions(true)
      break
    case "_update_options":
      await getOptions(true)
      break
    case "_set_storage_local":
      await chrome.storage.local.set(msg.data)
      break
    case "_open_options":
      await chrome.runtime.openOptionsPage()
      break
  }
})

if (globalThis.window) {
  function render() {
    const { ui } = globalThis.__cfc_options
    const pageUi = ui[location.pathname]
    if (pageUi) {
      Object.values(optionsUi).forEach((item) => {
        const el = document.querySelector(item.selector)
        if (el) el.innerHTML = item.html
      })
    }
  }
  window.addEventListener("DOMContentLoaded", render)
  window.addEventListener("popstate", render)

  if (location.pathname == "/sidepanel.html" && location.search == "") {
    chrome.tabs.query({ active: !0, currentWindow: !0 }).then(([tab]) => {
      const u = new URL(location.href)
      u.searchParams.set("tabId", tab.id)
      history.replaceState(null, "", u.href)
    })
  }
  if (location.pathname == "/options.html") {
  }
  if (location.pathname == "/arc.html") {
    const fetch = globalThis.__fetch
    fetch(cfcBase + "api/arc-split-view")
      .then((res) => {
        return res.json()
      })
      .then((data) => {
        document.querySelector(".animate-spin").outerHTML = data.html
      })

    fetch("/options.html")
      .then((res) => res.text())
      .then((html) => {
        const matches = html.match(/[^"\s]+?\.css/g)
        for (const url of matches) {
          const link = document.createElement("link")
          link.rel = "stylesheet"
          link.href = url
          document.head.appendChild(link)
        }
      })

    window.addEventListener("resize", async () => {
      const tabs = await chrome.tabs.query({ currentWindow: true })
      const tab = await new Promise((resolve) => {
        tabs.forEach(async (t) => {
          if (t.url.startsWith(location.origin)) return
          const [value] = await chrome.scripting.executeScript({
            target: { tabId: t.id },
            func: () => {
              return document.visibilityState
            },
          })
          if (value.result == "visible") {
            resolve(t)
          }
        })
      })
      if (tab) {
        location.href = "/sidepanel.html?tabId=" + tab.id
        chrome.tabs.update(tab.id, { active: true })
      }
    })

    chrome.system.display.getInfo().then(([info]) => {
      location.hash = "id=" + info?.id
      console.log(info)
    })
  }
}

if (!globalThis.__openSidePanel) {
  globalThis.__openSidePanel = chrome?.sidePanel?.open
}
const isChrome = navigator.userAgentData?.brands?.some(
  (b) => b.brand == "Google Chrome"
)
if (!isChrome && chrome.sidePanel) {
  chrome.sidePanel.open = async (...args) => {
    const open = globalThis.__openSidePanel
    const result = await open.apply(chrome.sidePanel, args)
    const contexts = await chrome.runtime.getContexts({
      contextTypes: ["SIDE_PANEL"],
    })
    const success = contexts.length > 0
    if (!success) {
      chrome.tabs.create({ url: "/arc.html" })
    }
    return result
  }
}

function matchJsx(node, selector) {
  if (!node || !selector) return false
  if (selector.type && node.type != selector.type) return false
  if (selector.key && node.key != selector.key) return false

  let p = node.props
  let m = selector.props
  for (let k of Object.keys(m)) {
    if (k == "children") continue
    if (m[k] != p?.[k]) {
      return false
    }
  }
  if (m.children === undefined) return true
  if (m.children === p?.children) return true
  if (m.children && !p?.children) return false
  if (Array.isArray(m.children)) {
    if (!Array.isArray(p?.children)) return false
    return m.children.every((c, i) => c == null || matchJsx(p?.children[i], c))
  }
  return matchJsx(p?.children, m.children)
}

function remixJsx(node, renderNode) {
  const { uiNodes } = globalThis.__cfc_options
  const { props } = node
  for (const item of uiNodes) {
    if (!matchJsx(node, item.selector)) {
      continue
    }
    if (item.prepend) {
      if (!Array.isArray(props.children)) {
        props.children = [props.children]
      }
      props.children = [renderNode(item.prepend), ...props.children]
    }
    if (item.append) {
      if (!Array.isArray(props.children)) {
        props.children = [props.children]
      }
      props.children = [...props.children, renderNode(item.append)]
    }
    if (item.replace) {
      node = renderNode(item.replace)
    }
  }
  return node
}

export function setJsx(n) {
  const t = (l) => l

  function renderNode(node) {
    if (typeof node == "string") return node
    if (typeof node == "number") return node
    if (node && typeof node == "object" && !node.$$typeof) {
      const { type, props, key } = node
      const children = props?.children
      if (Array.isArray(children)) {
        for (let i = children.length - 1; i >= 0; i--) {
          const child = children[i]
          if (child && typeof child == "object" && !child.$$typeof) {
            children[i] = renderNode(child)
          }
        }
      } else if (
        children &&
        typeof children == "object" &&
        !children.$$typeof
      ) {
        props.children = renderNode(children)
      }
      return jsx(type, props, key)
    }
    return null
  }

  function _jsx(type, props, key) {
    const n = remixJsx({ type, props, key }, renderNode)
    return jsx(n.type, n.props, n.key)
  }

  if (n.jsx.name == "_jsx") return
  const jsx = n.jsx
  n.jsx = _jsx
  n.jsxs = _jsx
}
