import { ServerAuth } from "@opencode-ai/server/auth"
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Effect, Schema, Stream } from "effect"
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
import { randomBytes } from "node:crypto"
import path from "node:path"

const Ready = Schema.Struct({ url: Schema.String })
const decodeReady = Schema.decodeUnknownPromise(Schema.fromJsonString(Ready))

type Options = {
  readonly command?: ReadonlyArray<string>
}

function command(password: string, options: Options) {
  const compiled = path.basename(process.execPath).replace(/\.exe$/, "") !== "bun"
  const entrypoint = compiled ? [] : process.argv[1] ? [process.argv[1]] : []
  if (!compiled && entrypoint.length === 0) throw new Error("Failed to resolve CLI entrypoint")
  const [executable, ...args] = options.command ?? [process.execPath, ...entrypoint, "serve"]
  if (!executable) throw new Error("Failed to resolve standalone server command")
  return ChildProcess.make(executable, [...args, "--stdio", "--port", "0"], {
    cwd: process.cwd(),
    // Explicit entry wins over anything inherited, so a user-exported
    // OPENCODE_PASSWORD cannot shadow the child's lease credential.
    env: { OPENCODE_PASSWORD: password },
    extendEnv: true,
    // The server treats EOF on this pipe as the end of its ownership lease.
    // The OS closes it even when the TUI is killed before Effect finalizers run.
    stdin: "pipe",
    stderr: "ignore",
    killSignal: "SIGTERM",
    forceKillAfter: "3 seconds",
  })
}

const makeTransport = Effect.fn("cli.standalone.transport")(
  function* (options: Options) {
    const password = randomBytes(32).toString("base64url")
    const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
    const proc = yield* spawner.spawn(command(password, options))
    const output = yield* proc.stdout.pipe(Stream.decodeText(), Stream.splitLines, Stream.take(1), Stream.mkString)
    if (!output) return yield* Effect.fail(new Error("Standalone server exited before reporting readiness"))
    const ready = yield* Effect.tryPromise(() => decodeReady(output))
    return { url: ready.url, headers: ServerAuth.headers({ password, username: "opencode" }), pid: proc.pid }
  },
  Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node)),
)

export function transport(options: Options = {}) {
  return makeTransport(options)
}

export * as Standalone from "./standalone"
