#!/usr/bin/env node
import { spawnSync } from "child_process"
import fs from "fs"
import path from "path"
import { fileURLToPath } from "url"

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const root = path.join(__dirname, "..")
const vendor = path.join(root, "vendor")
const loader = path.join(vendor, "ld-musl.so")
const bin = path.join(vendor, "opencode")
const PKG = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")) as { version: string; opencodeUpstream: string }
const PREFIX = process.env.TERMUX_PREFIX || "/data/data/com.termux/files/usr"

function ready(): boolean {
  return fs.existsSync(loader) && fs.existsSync(bin)
}

function heal(): boolean {
  if (ready()) return true
  console.log("[opencode-termux] vendor belum ada — menjalankan installer…")
  const r = spawnSync(process.execPath, [path.join(root, "install.mjs")], {
    stdio: "inherit",
    env: process.env,
  })
  if (r.status !== 0 || !ready()) {
    console.error("[opencode-termux] instalasi bundle gagal. Coba manual:")
    console.error("  npm rebuild @nemoobc/opencode-termux")
    return false
  }
  return true
}

function ensureDns(): void {
  try {
    const etc = path.join(PREFIX, "etc")
    fs.mkdirSync(etc, { recursive: true })
    const rc = path.join(etc, "resolv.conf")
    if (!fs.existsSync(rc)) fs.writeFileSync(rc, "nameserver 1.1.1.1\nnameserver 8.8.8.8\n")
    const hh = path.join(etc, "hosts")
    if (!fs.existsSync(hh)) fs.writeFileSync(hh, "127.0.0.1 localhost\n")
  } catch {}
}

function runBinary(args: string[]): number {
  const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
  ensureDns()
  const r = spawnSync(loader, [bin, ...args], {
    stdio: "inherit",
    env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
  })
  if (r.error) {
    console.error("[opencode-termux] gagal menjalankan binary:", r.error.message)
    return 1
  }
  const sigExit: Record<string, number> = { SIGINT: 130, SIGQUIT: 131, SIGTERM: 143 }
  return r.status ?? sigExit[r.signal ?? ""] ?? 1
}

async function cmdUpdate(): Promise<number> {
  console.log(`[opencode-termux] memperbarui bundle (paket v${PKG.version})…`)
  const env = { ...process.env }
  delete env.LD_PRELOAD
  delete env.LD_PRELOAD_32BIT
  const r = spawnSync(process.execPath, [path.join(root, "install.mjs")], {
    stdio: "inherit",
    env: env,
  })
  if (r.status !== 0) {
    console.error("[opencode-termux] ❌ update gagal.")
    return 1
  }
  try {
    const latest = await (await fetch("https://registry.npmjs.org/opencode-ai/latest")).json() as { version: string }
    if (latest.version && latest.version !== PKG.opencodeUpstream) {
      PKG.opencodeUpstream = latest.version
      fs.writeFileSync(path.join(root, "package.json"), JSON.stringify(PKG, null, 2) + "\n")
    }
  } catch {}
  console.log("[opencode-termux] ✅ update selesai.")
  return 0
}

function cmdDoctor(): number {
  let critical = 0
  const cek = (name: string, fn: () => string | void, { crit = true } = {}): void => {
    try {
      const info = fn()
      console.log(`✅ ${name}${info ? ` — ${info}` : ""}`)
    } catch (e) {
      if (crit) critical++
      console.log(`${crit ? "❌" : "⚠️ "} ${name} — ${(e as Error).message}`)
    }
  }

  console.log(`[opencode-termux] doctor v${PKG.version} (upstream ${PKG.opencodeUpstream})`)
  cek("platform", () => {
    if (process.platform !== "android") throw new Error(`process.platform=${process.platform} (bukan android)`)
    return "android"
  }, { crit: false })
  cek("arsitektur", () => {
    if (process.arch !== "arm64" && process.arch !== "x64") throw new Error(`${process.arch} tidak didukung`)
    return process.arch
  })
  cek("node >= 18", () => {
    const [M] = process.versions.node.split(".").map(Number)
    if (M < 18) throw new Error(`node ${process.versions.node}`)
    return process.versions.node
  })
  cek("tar tersedia", () => {
    const r = spawnSync("tar", ["--version"], { stdio: "ignore" })
    if (r.error || r.status !== 0) throw new Error("tidak ditemukan — pkg install tar")
  })
  cek("vendor lengkap", () => {
    if (!ready()) throw new Error("vendor/ tidak lengkap — jalankan 'opencode-termux update'")
    return `${fs.readdirSync(vendor).length} file`
  })
  cek("DNS resolv.conf", () => {
    const rc = path.join(PREFIX, "etc", "resolv.conf")
    if (!fs.existsSync(rc)) throw new Error(`${rc} hilang`)
    return "ada"
  }, { crit: false })
  cek("jaringan registry npm", () => {
    const r = spawnSync(process.execPath, ["-e", "fetch('https://registry.npmjs.org/-/ping').then(r=>{if(!r.ok)process.exit(1)})"], {
      timeout: 10000,
    })
    if (r.status !== 0) throw new Error("registry tak terjangkau")
  })
  cek("binary opencode", () => {
    if (!ready()) throw new Error("binary belum terpasang")
    const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
    const out = spawnSync(loader, [bin, "--version"], {
      encoding: "utf8",
      env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
    })
    if (out.status !== 0) throw new Error("gagal dieksekusi")
    return `v${out.stdout.trim()}`
  })

  console.log(critical === 0 ? "[opencode-termux] ✅ semua komponen kritis sehat" : `[opencode-termux] ❌ ${critical} masalah kritis`)
  return critical === 0 ? 0 : 1
}

function cmdVersion(): number {
  let binVer = "(belum terpasang)"
  if (ready()) {
    const { LD_PRELOAD, LD_PRELOAD_32BIT, ...cleanEnv } = process.env
    const out = spawnSync(loader, [bin, "--version"], {
      encoding: "utf8",
      env: { ...cleanEnv, LD_LIBRARY_PATH: vendor },
    })
    if (out.status === 0 && out.stdout.trim()) binVer = out.stdout.trim()
  }
  console.log(`opencode-termux v${PKG.version} (upstream opencode ${PKG.opencodeUpstream}, binary ${binVer})`)
  return 0
}

async function main(): Promise<void> {
  const arg = process.argv[2]
  if (arg === "update") process.exit(await cmdUpdate())
  if (arg === "doctor") process.exit(cmdDoctor())
  if (arg === "version") process.exit(cmdVersion())
  if (arg === "help" || arg === "--help" || arg === "-h") {
    console.log(`opencode-termux v${PKG.version}
pakai:
  opencode-termux                 jalankan CLI opencode (argumen diteruskan)
  opencode-termux update          perbarui binary ke upstream terbaru
  opencode-termux doctor          diagnosis lingkungan & bundle
  opencode-termux version         info versi paket + binary`)
    process.exit(0)
  }
  if (!heal()) process.exit(1)
  process.exit(runBinary(process.argv.slice(2)))
}

main().catch(e => {
  console.error("[opencode-termux]", e.message)
  process.exit(1)
})