import yargs from "yargs"
import { hideBin } from "yargs/helpers"

// ╔══════════════════════════════════════════════════════════════════╗
// ║  SAEEOL CLI — 애드온 아키텍처                                     ║
// ║                                                                   ║
// ║  원자 단위 애드온 → 티어별 자동 활성화                              ║
// ║                                                                   ║
// ║  LIGHT  = core + llm + lifecycle                                  ║
// ║  CODE   = LIGHT + server + tools + data + dev                     ║
// ║  MASTER = CODE + web + remote + analytics                         ║
// ╚══════════════════════════════════════════════════════════════════╝

import type { Tier } from "./addons/types"
import { commandsForTier, manifest } from "./addons/registry"

// ── DEV 전용 (항상 포함) ────────────────────────────────────────────
import { DevSetupCommand, DevAliasCommand } from "./overlay/cli/dev-setup"

// ── 공통 ────────────────────────────────────────────────────────────
import * as Log from "@saeeol/core/util/log"
import { UI } from "./cli/ui"
import { Installation } from "./installation"
import { InstallationBuildKind, InstallationVersion } from "@saeeol/core/installation/version" 
import { NamedError } from "@saeeol/core/util/error"
import { FormatError } from "./cli/error"
import { Filesystem } from "@/util/filesystem"
import { EOL } from "os"
import { Telemetry } from "@saeeol/telemetry"
import { InstanceStore } from "./project/instance-store" 
import { migrateLegacySaeeolAuth, ENV_FEATURE, ENV_VERSION } from "@saeeol/gateway"
import { Config } from "./config/config"
import { Auth } from "./auth"
import path from "path"
import { Global } from "@saeeol/core/global"
import { createHelpCommand } from "./overlay/help-command" 
import { JsonMigration } from "@/storage/json-migration"
import { Database } from "@/storage/db"
import { errorMessage } from "./util/error"
import { Heap } from "./cli/heap"
import { drizzle } from "drizzle-orm/bun-sqlite"
import { ensureProcessMetadata } from "@saeeol/core/util/saeeol-process"


if (!process.env[ENV_FEATURE]) {
  const isServe = process.argv.includes("serve")
  process.env[ENV_FEATURE] = isServe ? "unknown" : "cli"
}
if (!process.env[ENV_VERSION]) {
  process.env[ENV_VERSION] = InstallationVersion
}

const processMetadata = ensureProcessMetadata("main")

// Set terminal title to 새얼
if (process.stderr.isTTY) {
  process.stderr.write("\x1b]0;● 새얼\x07")
}

process.on("unhandledRejection", (e) => {
  Log.Default.error("rejection", {
    e: errorMessage(e),
  })
})

process.on("uncaughtException", (e) => {
  Log.Default.error("exception", {
    e: errorMessage(e),
  })
})

const args = hideBin(process.argv)

function show(out: string) {
  const text = out.trimStart()
  const end = out.endsWith(EOL) ? "" : EOL 
  if (!text.startsWith("saeeol ")) {
    process.stderr.write(UI.logo() + EOL + EOL)
    process.stderr.write(text + end) 
    return
  }
  process.stderr.write(out + end) 
}

let cli = yargs(args) 
  .parserConfiguration({ "populate--": true })
  .scriptName("saeeol") 
  .wrap(100)
  .help("help", "show help")
  .alias("help", "h")
  .version("version", "show version number", InstallationVersion)
  .alias("version", "v")
  .option("print-logs", {
    describe: "print logs to stderr",
    type: "boolean",
  })
  .option("log-level", {
    describe: "log level",
    type: "string",
    choices: ["DEBUG", "INFO", "WARN", "ERROR"],
  })
  .option("pure", {
    describe: "run without external plugins",
    type: "boolean",
  })
  .middleware(async (opts) => {
    if (opts.pure) {
      process.env.SAEEOL_PURE = "1"
    }

    await Log.init({
      print: process.argv.includes("--print-logs"),
      dev: Installation.isLocal(),
      level: (() => {
        if (opts.logLevel) return opts.logLevel as Log.Level
        if (Installation.isLocal()) return "DEBUG"
        return "INFO"
      })(),
    })

    Heap.start()

    process.env.AGENT = "1"
    process.env.SAEEOL = "1" 
    process.env.SAEEOL_PID = String(process.pid)

    Log.Default.info("saeeol", {
      version: InstallationVersion,
      args: process.argv.slice(2),
      process_role: processMetadata.processRole,
      run_id: processMetadata.runID,
    })
    const globalCfg = await Config.getGlobal()
    await Telemetry.init({
      dataPath: Global.Path.data,
      version: InstallationVersion,
      enabled: globalCfg.experimental?.openTelemetry !== false,
    })

    // Migrate legacy Saeeol CLI auth if needed
    await migrateLegacySaeeolAuth(
      async () => (await Auth.get("saeeol")) !== undefined,
      async (auth) => Auth.set("saeeol", auth),
    )

    const saeeolAuth = await Auth.get("saeeol")
    if (saeeolAuth) {
      const token = saeeolAuth.type === "oauth" ? saeeolAuth.access : saeeolAuth.key
      const accountId = saeeolAuth.type === "oauth" ? saeeolAuth.accountId : undefined
      await Telemetry.updateIdentity(token, accountId)
    }

    Telemetry.trackCliStart()

    const marker = path.join(Global.Path.data, "saeeol.db")
    if (!(await Filesystem.exists(marker))) {
      const tty = process.stderr.isTTY
      process.stderr.write("Performing one time database migration, may take a few minutes..." + EOL)
      const width = 36
      const orange = "\x1b[38;5;214m"
      const muted = "\x1b[0;2m"
      const reset = "\x1b[0m"
      let last = -1
      if (tty) process.stderr.write("\x1b[?25l")
      try {
        await JsonMigration.run(drizzle({ client: Database.Client().$client }), {
          progress: (event) => {
            const percent = Math.floor((event.current / event.total) * 100)
            if (percent === last && event.current !== event.total) return
            last = percent
            if (tty) {
              const fill = Math.round((percent / 100) * width)
              const bar = `${"■".repeat(fill)}${"･".repeat(width - fill)}`
              process.stderr.write(
                `\r${orange}${bar} ${percent.toString().padStart(3)}%${reset} ${muted}${event.label.padEnd(12)} ${event.current}/${event.total}${reset}`,
              )
              if (event.current === event.total) process.stderr.write("\n")
            } else {
              process.stderr.write(`sqlite-migration:${percent}${EOL}`)
            }
          },
        })
      } finally {
        if (tty) process.stderr.write("\x1b[?25h")
        else {
          process.stderr.write(`sqlite-migration:done${EOL}`)
        }
      }
      process.stderr.write("Database migration complete." + EOL)
    }
  })
  .usage("")
  .completion("completion", "generate shell completion script")

// ════════════════════════════════════════════════════════════════
//   애드온 기반 명령어 등록
//   SAEEOL_TIER define에 따라 활성 애드온이 결정됨
// ════════════════════════════════════════════════════════════════

declare const SAEEOL_TIER: string
const tier = (typeof SAEEOL_TIER === "string" ? SAEEOL_TIER : "master") as Tier
const addonCommands = await commandsForTier(tier)
for (const cmd of addonCommands) {
  cli = cli.command(cmd)
}

if (InstallationBuildKind !== "release") {
  cli = cli.command(DevSetupCommand).command(DevAliasCommand)
}
cli = cli.command(createHelpCommand(() => cli))

cli = cli
  .fail((msg, err) => {
    if (
      msg?.startsWith("Unknown argument") ||
      msg?.startsWith("Not enough non-option arguments") ||
      msg?.startsWith("Invalid values:")
    ) {
      if (err) throw err
      cli.showHelp(show)
    }
    if (err) throw err
    process.exit(1)
  })
  .strict()

try {
  if (args.includes("-h") || args.includes("--help")) {
    await cli.parse(args, (err: Error | undefined, _argv: unknown, out: string) => {
      if (err) throw err
      if (!out) return
      show(out)
    })
  } else {
    await cli.parse()
  }
} catch (e) {
  let data: Record<string, any> = {}
  if (e instanceof NamedError) {
    const obj = e.toObject()
    Object.assign(data, {
      ...obj.data,
    })
  }

  if (e instanceof Error) {
    Object.assign(data, {
      name: e.name,
      message: e.message,
      cause: e.cause?.toString(),
      stack: e.stack,
    })
  }

  if (e instanceof ResolveMessage) {
    Object.assign(data, {
      name: e.name,
      message: e.message,
      code: e.code,
      specifier: e.specifier,
      referrer: e.referrer,
      position: e.position,
      importKind: e.importKind,
    })
  }
  Log.Default.error("fatal", data)
  const formatted = FormatError(e)
  if (formatted) UI.error(formatted)
  if (formatted === undefined) {
    UI.error("Unexpected error, check log file at " + Log.file() + " for more details" + EOL)
    process.stderr.write(errorMessage(e) + EOL)
  }
  process.exitCode = 1
} finally {
  const exitCode = typeof process.exitCode === "number" ? process.exitCode : undefined
  Telemetry.trackCliExit(exitCode)
  await Telemetry.shutdown()

  await InstanceStore.disposeAllInstances() 

  // Some subprocesses don't react properly to SIGTERM and similar signals.
  // Most notably, some docker-container-based MCP servers don't handle such signals unless
  // run using `docker run --init`.
  // Explicitly exit to avoid any hanging subprocesses.
  process.exit()
}
