import "zod-openapi/extend"
import { App } from "./app/app"
import { Server } from "./server/server"
import fs from "fs/promises"
import path from "path"
import { Share } from "./share/share"
import { Global } from "./global"
import yargs from "yargs"
import { hideBin } from "yargs/helpers"
import { RunCommand } from "./cli/cmd/run"
import { GenerateCommand } from "./cli/cmd/generate"
import { VERSION } from "./cli/version"
import { ScrapCommand } from "./cli/cmd/scrap"
import { Log } from "./util/log"
import { AuthCommand, AuthLoginCommand } from "./cli/cmd/auth"
import { UpgradeCommand } from "./cli/cmd/upgrade"
import { Provider } from "./provider/provider"
import { UI } from "./cli/ui"

const cli = yargs(hideBin(process.argv))
  .scriptName("opencode")
  .version(VERSION)
  .option("print-logs", {
    describe: "Print logs to stderr",
    type: "boolean",
  })
  .middleware(async () => {
    await Log.init({ print: process.argv.includes("--print-logs") })
    Log.Default.info("opencode", {
      version: VERSION,
      args: process.argv.slice(2),
    })
  })
  .usage("\n" + UI.logo())
  .command({
    command: "$0 [project]",
    describe: "Start opencode TUI",
    builder: (yargs) =>
      yargs.positional("project", {
        type: "string",
        describe: "path to start opencode in",
      }),
    handler: async (args) => {
      while (true) {
        const cwd = args.project ? path.resolve(args.project) : process.cwd()
        process.chdir(cwd)
        const result = await App.provide(
          { cwd, version: VERSION },
          async (app) => {
            const providers = await Provider.list()
            if (Object.keys(providers).length === 0) {
              return "needs_provider"
            }

            await Share.init()
            const server = Server.listen()

            let cmd = ["go", "run", "./main.go"]
            let cwd = new URL("../../tui/cmd/opencode", import.meta.url)
              .pathname
            if (Bun.embeddedFiles.length > 0) {
              const blob = Bun.embeddedFiles[0] as File
              const binary = path.join(Global.Path.cache, "tui", blob.name)
              const file = Bun.file(binary)
              if (!(await file.exists())) {
                await Bun.write(file, blob, { mode: 0o755 })
                await fs.chmod(binary, 0o755)
              }
              cwd = process.cwd()
              cmd = [binary]
            }
            const proc = Bun.spawn({
              cmd: [...cmd, ...process.argv.slice(2)],
              cwd,
              stdout: "inherit",
              stderr: "inherit",
              stdin: "inherit",
              env: {
                ...process.env,
                OPENCODE_SERVER: server.url.toString(),
                OPENCODE_APP_INFO: JSON.stringify(app),
              },
              onExit: () => {
                server.stop()
              },
            })
            await proc.exited
            await server.stop()

            return "done"
          },
        )
        if (result === "done") break
        if (result === "needs_provider") {
          UI.empty()
          UI.println(UI.logo("   "))
          UI.empty()
          await AuthLoginCommand.handler(args)
        }
      }
    },
  })
  .command(RunCommand)
  .command(GenerateCommand)
  .command(ScrapCommand)
  .command(AuthCommand)
  .command(UpgradeCommand)
  .fail((msg, err) => {
    if (
      msg.startsWith("Unknown argument") ||
      msg.startsWith("Not enough non-option arguments")
    ) {
      cli.showHelp("log")
    }
    Log.Default.error(msg, {
      err,
    })
  })
  .strict()

try {
  await cli.parse()
} catch (e) {
  Log.Default.error(e)
}
