#!/bin/sh
':' //; runner="$(command -v node || command -v bun || true)"; [ -n "$runner" ] || { echo "slopcode requires bun or node in PATH" >&2; exit 127; }; exec "$runner" "$0" "$@"

const childProcess = require("child_process")
const fs = require("fs")
const path = require("path")
const os = require("os")
const pkg = require("../package.json")

function run(target, options = {}) {
  const result = childProcess.spawnSync(target, options.args || process.argv.slice(2), {
    stdio: "inherit",
    env: options.env ? { ...process.env, ...options.env } : process.env,
  })
  if (result.error) {
    console.error(result.error.message)
    process.exit(1)
  }
  const code = typeof result.status === "number" ? result.status : 0
  process.exit(code)
}

const envPath = process.env.SLOPCODE_BIN_PATH
if (envPath) {
  run(envPath)
}

const scriptPath = fs.realpathSync(__filename)
const scriptDir = path.dirname(scriptPath)
const rootDir = path.dirname(scriptDir)
const cached = path.join(scriptDir, ".slopcode")
const meta = path.join(scriptDir, ".slopcode.json")
const sidecar = path.join(scriptDir, "neovim")
const androidBundle = path.join(rootDir, "bundle", "index.js")
const platformMap = {
  darwin: "darwin",
  linux: "linux",
  win32: "windows",
}
const archMap = {
  x64: "x64",
  arm64: "arm64",
  arm: "arm",
}
const supported = {
  android: ["arm64", "x64"],
  darwin: ["arm64", "x64"],
  linux: ["arm64", "x64"],
  windows: ["x64"],
}

const rawPlatform = process.env.SLOPCODE_TEST_PLATFORM || os.platform()
const rawArch = process.env.SLOPCODE_TEST_ARCH || os.arch()
let platform = platformMap[rawPlatform]
if (!platform) {
  platform = rawPlatform
}
let arch = archMap[rawArch]
if (!arch) {
  arch = rawArch
}
const binary = platform === "windows" ? "slopcode.exe" : "slopcode"

function remove(target) {
  if (!fs.existsSync(target)) return
  fs.rmSync(target, { recursive: true, force: true })
}

function clearCache() {
  remove(cached)
  remove(meta)
  remove(sidecar)
}

function termuxEnv() {
  return Boolean(process.env.TERMUX_VERSION || (process.env.PREFIX || "").includes("/com.termux/"))
}

function libc() {
  if (platform === "android") return "bionic"
  if (platform !== "linux") return
  const report = process.report?.getReport?.()
  if (typeof report?.header?.glibcVersionRuntime === "string" && report.header.glibcVersionRuntime) {
    return "glibc"
  }
  if (fs.existsSync("/etc/alpine-release")) return "musl"
  const getconf = childProcess.spawnSync("getconf", ["GNU_LIBC_VERSION"], {
    encoding: "utf8",
    timeout: 1500,
  })
  const fromGetconf = ((getconf.stdout || "") + (getconf.stderr || "")).toLowerCase()
  if (getconf.status === 0 && fromGetconf.includes("glibc")) return "glibc"
  const result = childProcess.spawnSync("ldd", ["--version"], {
    encoding: "utf8",
    timeout: 1500,
  })
  const text = ((result.stdout || "") + (result.stderr || "")).toLowerCase()
  if (text.includes("musl")) return "musl"
  if (text.includes("glibc") || text.includes("gnu libc")) return "glibc"
  if (text.includes("bionic") || termuxEnv()) return "bionic"
  const loader = arch === "arm64" ? "/lib/ld-musl-aarch64.so.1" : arch === "x64" ? "/lib/ld-musl-x86_64.so.1" : ""
  if (loader && fs.existsSync(loader)) return "musl"
}

function supportsAvx2() {
  if (arch !== "x64") return false

  if (platform === "linux") {
    return /(^|\s)avx2(\s|$)/i.test(fs.readFileSync("/proc/cpuinfo", "utf8"))
  }

  if (platform === "darwin") {
    const result = childProcess.spawnSync("sysctl", ["-n", "hw.optional.avx2_0"], {
      encoding: "utf8",
      timeout: 1500,
    })
    if (result.status !== 0) return false
    return (result.stdout || "").trim() === "1"
  }

  if (platform === "windows") {
    const cmd =
      '(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)'

    for (const exe of ["powershell.exe", "pwsh.exe", "pwsh", "powershell"]) {
      const result = childProcess.spawnSync(exe, ["-NoProfile", "-NonInteractive", "-Command", cmd], {
        encoding: "utf8",
        timeout: 3000,
        windowsHide: true,
      })
      if (result.error || result.status !== 0) continue
      const out = (result.stdout || "").trim().toLowerCase()
      if (out === "true" || out === "1") return true
      if (out === "false" || out === "0") return false
    }
  }

  return false
}

function names(prefix) {
  const kind = libc()
  const base = kind === "bionic" ? `${prefix}-android-${arch}` : `${prefix}-${platform}-${arch}`
  if (kind === "bionic") return [`@slopcode-ai/slopcode-android-${arch}`, base]
  const avx2 = supportsAvx2()
  const baseline = arch === "x64" && !avx2

  if (platform === "linux") {
    if (kind === "musl") {
      if (arch === "x64") {
        if (baseline) return [`${base}-baseline-musl`, `${base}-musl`, `${base}-baseline`, base]
        return [`${base}-musl`, `${base}-baseline-musl`, base, `${base}-baseline`]
      }
      return [`${base}-musl`, base]
    }
    if (kind === "glibc") {
      if (arch === "x64") {
        if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
        return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
      }
      return [base, `${base}-musl`]
    }
    if (arch === "x64") {
      if (baseline) return [`${base}-baseline`, base, `${base}-baseline-musl`, `${base}-musl`]
      return [base, `${base}-baseline`, `${base}-musl`, `${base}-baseline-musl`]
    }
    return [base, `${base}-musl`]
  }

  if (arch === "x64") {
    if (baseline) return [`${base}-baseline`, base]
    return [base, `${base}-baseline`]
  }
  return [base]
}

function readMeta() {
  if (!fs.existsSync(meta)) return
  try {
    return JSON.parse(fs.readFileSync(meta, "utf8"))
  } catch {
    return
  }
}

function cacheReady(names) {
  if (!fs.existsSync(cached)) return
  const info = readMeta()
  if (!info) return
  if (info.version !== pkg.version) return
  if (info.platform !== platform || info.arch !== arch) return
  if (info.binary !== binary) return
  if (platform === "linux" && info.libc !== libc()) return
  if (typeof info.package !== "string" || !names.includes(info.package)) return
  if (info.sidecar === true && !fs.existsSync(sidecar)) return
  return cached
}

function unsupported() {
  const hit = supported[platform]
  return !hit || !hit.includes(arch)
}

function supportedMessage() {
  return Object.entries(supported)
    .map(([platform, archs]) => `${platform}:${archs.join(",")}`)
    .join(" ")
}

function termuxMessage() {
  return [
    "SlopCode native Termux support could not find the Android runtime.",
    "Reinstall from Termux with:",
    "  pkg update",
    "  pkg install nodejs git ripgrep neovim tar",
    "  npm install -g slopcode@latest --include=optional",
    "If postinstall could not download the runtime, install the matching GitHub release asset manually.",
  ].join("\n")
}

function androidDoctor(args) {
  return args[0] === "doctor" && args[1] === "android"
}

function androidInteractive(args) {
  return !androidDoctor(args) && !args.includes("--self-test")
}

function androidBun(arch) {
  const name = arch === "arm64" ? "bun-linux-aarch64-android" : "bun-linux-x64-android"
  return [
    process.env.SLOPCODE_BUN_PATH,
    path.join(rootDir, "node_modules", "@oven", name, "bin", "bun"),
    path.join(rootDir, "node_modules", ".bin", process.platform === "win32" ? "bun.cmd" : "bun"),
  ].find((item) => item && fs.existsSync(item))
}

const useCache = libc() !== "bionic"

const packageNames = names("slopcode-bin")
const resolvedNames = Array.from(new Set([...packageNames, ...names("slopcode")]))
const hit = useCache ? cacheReady(resolvedNames) : undefined
if (hit) {
  run(hit)
}
clearCache()

if (unsupported()) {
  console.error(`Unsupported platform ${platform}/${arch}. Supported targets: ${supportedMessage()}`)
  process.exit(1)
}

function resolveBinary() {
  for (const name of resolvedNames) {
    try {
      const candidate = require.resolve(`${name}/bin/${binary}`, {
        paths: [scriptDir],
      })
      if (fs.existsSync(candidate)) return { path: candidate, package: name }
    } catch {
      continue
    }
  }
}

function findBinary(startDir) {
  let current = startDir
  for (;;) {
    const modules = path.join(current, "node_modules")
    if (fs.existsSync(modules)) {
      for (const name of resolvedNames) {
        const candidate = path.join(modules, name, "bin", binary)
        if (fs.existsSync(candidate)) return { path: candidate, package: name }
      }
    }
    const parent = path.dirname(current)
    if (parent === current) {
      return
    }
    current = parent
  }
}

const resolved = resolveBinary() || findBinary(scriptDir)
if (!resolved) {
  if (libc() === "bionic") {
    console.error(termuxMessage())
    process.exit(1)
  }
  console.error(
    "It seems that your package manager failed to install the right version of the slopcode CLI for your platform. You can try manually installing " +
      packageNames.map((n) => `\"${n}\"`).join(" or ") +
      " package",
  )
  process.exit(1)
}

const args = process.argv.slice(2)
if (platform === "android" && androidInteractive(args)) {
  const bun = androidBun(arch)
  if (bun && fs.existsSync(androidBundle)) {
    const runtimeRoot = path.dirname(path.dirname(resolved.path))
    run(bun, {
      args: [androidBundle, ...args],
      env: {
        SLOPCODE_BIONIC: "1",
        SLOPCODE_VERSION: pkg.version,
        SLOPCODE_ENTRYPOINT: androidBundle,
        SLOPCODE_ANDROID_ROOT: runtimeRoot,
        SLOPCODE_ANDROID_HOST_PATH: path.join(runtimeRoot, "bin", "slopcode-android-host"),
        SLOPCODE_ANDROID_HOST: process.env.SLOPCODE_ANDROID_HOST || "sidecar",
        NODE_PATH: [path.join(rootDir, "android-modules"), process.env.NODE_PATH].filter(Boolean).join(path.delimiter),
      },
    })
  }
  if (fs.existsSync(androidBundle) && !bun) {
    console.error(
      "SlopCode Android CLI bootstrap is missing. Reinstall from Termux with: npm install -g slopcode@latest --include=optional",
    )
    process.exit(1)
  }
}

run(resolved.path)

