#!/usr/bin/env bun

import { $ } from "bun"
import fs from "fs"
import path from "path"
import { fileURLToPath } from "url"
import { createSolidTransformPlugin } from "@opentui/solid/bun-plugin"
import { createRequire } from "module" 

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const dir = path.resolve(__dirname, "..")
const require = createRequire(import.meta.url) 

process.chdir(dir)

await import("./generate.ts")

import { Script } from "@saeeol/script"
import pkg from "../package.json"
import { LanceDBRuntime } from "../src/overlay/lancedb" 

// Load migrations from migration directories
const migrationDirs = (
  await fs.promises.readdir(path.join(dir, "migration"), {
    withFileTypes: true,
  })
)
  .filter((entry) => entry.isDirectory() && /^\d{4}\d{2}\d{2}\d{2}\d{2}\d{2}/.test(entry.name))
  .map((entry) => entry.name)
  .sort()

const migrations = await Promise.all(
  migrationDirs.map(async (name) => {
    const file = path.join(dir, "migration", name, "migration.sql")
    const sql = await Bun.file(file).text()
    const match = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/.exec(name)
    const timestamp = match
      ? Date.UTC(
          Number(match[1]),
          Number(match[2]) - 1,
          Number(match[3]),
          Number(match[4]),
          Number(match[5]),
          Number(match[6]),
        )
      : 0
    return { sql, timestamp, name }
  }),
)
console.log(`Loaded ${migrations.length} migrations`)

const singleFlag = process.argv.includes("--single")
const baselineFlag = process.argv.includes("--baseline")
const skipInstall = process.argv.includes("--skip-install")
const sourcemapsFlag = process.argv.includes("--sourcemaps")
const plugin = createSolidTransformPlugin()

// ══════════════════════════════════════════════════════════════════
//  Tier-based tree-sitter WASM selection
//
//  LIGHT  = 5 langs (markdown, json, yaml, toml, tsx)   ~8 MB
//  CODE   = 15 langs (+ python, rust, go, c, cpp...)     ~18 MB
//  MASTER = all 37 langs                                  ~50 MB
// ══════════════════════════════════════════════════════════════════

// ══════════════════════════════════════════════════════════════════
//  Tier-based external packages
//  These packages are excluded from the bundle entirely
// ══════════════════════════════════════════════════════════════════

const allProviderPkgs = [
  "@ai-sdk/amazon-bedrock",
  "@ai-sdk/anthropic",
  "@ai-sdk/azure",
  "@ai-sdk/google",
  "@ai-sdk/google-vertex",
  "@ai-sdk/openai",
  "@ai-sdk/openai-compatible",
  "@openrouter/ai-sdk-provider",
  "@ai-sdk/xai",
  "@ai-sdk/mistral",
  "@ai-sdk/groq",
  "@ai-sdk/deepinfra",
  "@ai-sdk/cerebras",
  "@ai-sdk/cohere",
  "@ai-sdk/gateway",
  "@ai-sdk/togetherai",
  "@ai-sdk/perplexity",
  "@ai-sdk/vercel",
  "@ai-sdk/alibaba",
  "gitlab-ai-provider",
  "venice-ai-sdk-provider",
]

const lightRequired = new Set([
  "@ai-sdk/anthropic",
  "@ai-sdk/openai",
  "@ai-sdk/openai-compatible",
  "@ai-sdk/google",
  "@saeeol/gateway",
])

const codeRequired = new Set([
  ...lightRequired,
  "@ai-sdk/amazon-bedrock",
  "@ai-sdk/azure",
  "@ai-sdk/google-vertex",
  "@openrouter/ai-sdk-provider",
  "@ai-sdk/groq",
  "@ai-sdk/deepinfra",
  "@ai-sdk/gateway",
  "@ai-sdk/alibaba",
  "@ai-sdk/cerebras",
])

function tierExternals(tier: string): string[] {
  if (tier === "master") return []
  const required = tier === "light" ? lightRequired : codeRequired
  return allProviderPkgs.filter((p) => !required.has(p))
}

const tierArg = process.argv.find((a) => a.startsWith("--tier="))?.split("=")[1] ?? "master"

const treeSitterLanguages: Record<string, string[]> = {
  light: [
    "markdown", "markdown_inline",
    "json",
    "yaml",
    "toml",
    "tsx", "typescript",
  ],
  code: [
    "markdown", "markdown_inline",
    "json",
    "yaml",
    "toml",
    "tsx", "typescript",
    "python",
    "rust",
    "go",
    "c", "cpp",
    "java",
    "javascript",
    "bash",
  ],
  master: [], // empty = all
}

async function copyTreeSitterWasms(outputDir: string, tier: string) {
  const runtimeWasmPath = require.resolve("web-tree-sitter/tree-sitter.wasm")
  const languagePackagePath = require.resolve("tree-sitter-wasms/package.json")
  const languageWasmDir = path.join(path.dirname(languagePackagePath), "out")
  const targetDir = path.join(outputDir, "tree-sitter")

  await fs.promises.mkdir(targetDir, { recursive: true })
  await fs.promises.copyFile(runtimeWasmPath, path.join(targetDir, "tree-sitter.wasm"))

  const allWasmFiles = (await fs.promises.readdir(languageWasmDir)).filter((file) => file.endsWith(".wasm"))
  const allowed = treeSitterLanguages[tier]

  const filesToCopy = allowed.length === 0
    ? allWasmFiles
    : allWasmFiles.filter((file) => {
        const langName = file.replace("tree-sitter-", "").replace(".wasm", "")
        return allowed.some((a) => langName === a || langName.startsWith(a + "-"))
      })

  await Promise.all(
    filesToCopy.map((file) => fs.promises.copyFile(path.join(languageWasmDir, file), path.join(targetDir, file))),
  )

  console.log(`copied ${filesToCopy.length + 1} tree-sitter wasm files to ${targetDir} (tier=${tier})`)
}

const allTargets: {
  os: string
  arch: "arm64" | "x64"
  abi?: "musl"
  avx2?: false
}[] = [
  {
    os: "linux",
    arch: "arm64",
  },
  {
    os: "linux",
    arch: "x64",
  },
  {
    os: "linux",
    arch: "x64",
    avx2: false,
  },
  {
    os: "linux",
    arch: "arm64",
    abi: "musl",
  },
  {
    os: "linux",
    arch: "x64",
    abi: "musl",
  },
  {
    os: "linux",
    arch: "x64",
    abi: "musl",
    avx2: false,
  },
  {
    os: "darwin",
    arch: "arm64",
  },
  {
    os: "darwin",
    arch: "x64",
  },
  {
    os: "darwin",
    arch: "x64",
    avx2: false,
  },
  {
    os: "win32",
    arch: "arm64",
  },
  {
    os: "win32",
    arch: "x64",
  },
  {
    os: "win32",
    arch: "x64",
    avx2: false,
  },
]

const targets = singleFlag
  ? allTargets.filter((item) => {
      if (item.os !== process.platform || item.arch !== process.arch) {
        return false
      }

      // When building for the current platform, prefer a single native binary by default.
      // Baseline binaries require additional Bun artifacts and can be flaky to download.
      if (item.avx2 === false) {
        return baselineFlag
      }

      // also skip abi-specific builds for the same reason
      if (item.abi !== undefined) {
        return false
      }

      return true
    })
  : allTargets

await $`rm -rf dist`

const binaries: Record<string, string> = {}
if (!skipInstall) {
  await $`bun install --os="*" --cpu="*" @opentui/core@${pkg.dependencies["@opentui/core"]}`
  await $`bun install --os="*" --cpu="*" @parcel/watcher@${pkg.dependencies["@parcel/watcher"]}`
}
for (const item of targets) {
  const name = [
    pkg.name,
    // changing to win32 flags npm for some reason
    item.os === "win32" ? "windows" : item.os,
    item.arch,
    item.avx2 === false ? "baseline" : undefined,
    item.abi === undefined ? undefined : item.abi,
  ]
    .filter(Boolean)
    .join("-")
  console.log(`building ${name}`)
  await $`mkdir -p dist/${name}/bin`

  const localPath = path.resolve(dir, "node_modules/@opentui/core/parser.worker.js")
  const rootPath = path.resolve(dir, "../../node_modules/@opentui/core/parser.worker.js")
  const parserWorker = fs.realpathSync(fs.existsSync(localPath) ? localPath : rootPath)
  const workerPath = "./src/cli/cmd/tui/worker.ts"

  // Use platform-specific bunfs root path based on target OS
  const bunfsRoot = item.os === "win32" ? "B:/~BUN/root/" : "/$bunfs/root/"
  const workerRelativePath = path.relative(dir, parserWorker).replaceAll("\\", "/")

  await Bun.build({
    conditions: ["browser"],
    tsconfig: "./tsconfig.json",
    plugins: [plugin],
    sourcemap: Script.release ? "none" : "external",
    external: ["node-gyp", ...LanceDBRuntime.external, ...tierExternals(tierArg)],
    format: "esm",
    minify: true,
    splitting: true,
    compile: {
      autoloadBunfig: false,
      autoloadDotenv: false,
      autoloadTsconfig: true,
      autoloadPackageJson: true,
      target: name.replace(pkg.name, "bun") as any,
      outfile: `dist/${name}/bin/saeeol`, 
      execArgv: [`--user-agent=saeeol/${Script.version}`, "--use-system-ca", "--"], 
      windows: {},
    },
    files: {},
    entrypoints: ["./src/index.ts", parserWorker, workerPath],
    define: {
      SAEEOL_VERSION: `'${Script.version}'`,
      SAEEOL_MIGRATIONS: JSON.stringify(migrations),
      OTUI_TREE_SITTER_WORKER_PATH: bunfsRoot + workerRelativePath,
      SAEEOL_WORKER_PATH: workerPath,
      SAEEOL_CHANNEL: `'${Script.channel}'`,
      SAEEOL_LIBC: item.os === "linux" ? `'${item.abi ?? "glibc"}'` : "",
      SAEEOL_BUILD_KIND: Script.release ? `'release'` : `'source'`, 
      SAEEOL_TIER: `'${tierArg}'`,
    },
  })

  await copyTreeSitterWasms(path.resolve(dir, `dist/${name}/bin`), tierArg)
  if (item.os === "linux") {
    const interpreters: Record<string, string> = {
      x64: "/lib64/ld-linux-x86-64.so.2",
      arm64: "/lib/ld-linux-aarch64.so.1",
      "x64-musl": "/lib/ld-musl-x86_64.so.1",
      "arm64-musl": "/lib/ld-musl-aarch64.so.1",
    }
    const key = item.abi === "musl" ? `${item.arch}-musl` : item.arch
    const interpreter = interpreters[key]
    if (interpreter) {
      try {
        await $`patchelf --set-interpreter ${interpreter} dist/${name}/bin/saeeol`
        console.log(`patched interpreter for ${name} -> ${interpreter}`)
      } catch {
        console.warn(`patchelf not available, skipping interpreter fix for ${name}`)
      }
    }
  }

  // Smoke test: only run if binary is for current platform
  if (item.os === process.platform && item.arch === process.arch && !item.abi) {
    const binaryPath = `dist/${name}/bin/saeeol` 
    console.log(`Running smoke test: ${binaryPath} --version`)
    try {
      const versionOutput = await $`${binaryPath} --version`.text()
      console.log(`Smoke test passed: ${versionOutput.trim()}`)
    } catch (e) {
      console.error(`Smoke test failed for ${name}:`, e)
      process.exit(1)
    }
  }

  await $`rm -rf ./dist/${name}/bin/tui`
  await Bun.file(`dist/${name}/package.json`).write(
    JSON.stringify(
      {
        name,
        version: Script.version,
        os: [item.os],
        cpu: [item.arch],
        repository: {
          type: "git",
          url: "https://github.com/SAEEOL/saeeol",
        },
      },
      null,
      2,
    ),
  )
  binaries[name] = Script.version
}

if (Script.release) {
  const archives: string[] = [] 
  for (const key of Object.keys(binaries)) {
    const archive = key.replace(pkg.name, "saeeol") 
    if (key.includes("linux")) {
      const out = path.resolve("dist", `${archive}.tar.gz`) 
      await $`tar -czf ${out} *`.cwd(`dist/${key}/bin`) 
      archives.push(out) 
    } else {
      const out = path.resolve("dist", `${archive}.zip`) 
      await $`zip -r ${out} *`.cwd(`dist/${key}/bin`) 
      archives.push(out) 
    }
  }
  await $`gh release upload v${Script.version} ${archives} --clobber` 
}

export { binaries }
