#!/usr/bin/env node
import process from "node:process";
import type { GatewayLockHandle } from "../infra/gateway-lock.js";

declare const __OPENCLAW_VERSION__: string;

const BUNDLED_VERSION =
  (typeof __OPENCLAW_VERSION__ === "string" && __OPENCLAW_VERSION__) ||
  process.env.OPENCLAW_BUNDLED_VERSION ||
  "0.0.0";

function argValue(args: string[], flag: string): string | undefined {
  const idx = args.indexOf(flag);
  if (idx < 0) return undefined;
  const value = args[idx + 1];
  return value && !value.startsWith("-") ? value : undefined;
}

function hasFlag(args: string[], flag: string): boolean {
  return args.includes(flag);
}

const args = process.argv.slice(2);

type GatewayWsLogStyle = "auto" | "full" | "compact";

async function main() {
  if (hasFlag(args, "--version") || hasFlag(args, "-v")) {
    // Match `clawdbot --version` behavior for Swift env/version checks.
    // Keep output a single line.
    console.log(BUNDLED_VERSION);
    process.exit(0);
  }

  // Bun runtime ships a global `Long` that protobufjs detects, but it does not
  // implement the long.js API that Baileys/WAProto expects (fromBits, ...).
  // Ensure we use long.js so the embedded gateway doesn't crash at startup.
  if (typeof process.versions.bun === "string") {
    const mod = await import("long");
    const Long = (mod as unknown as { default?: unknown }).default ?? mod;
    (globalThis as unknown as { Long?: unknown }).Long = Long;
  }

  const [
    { loadConfig },
    { startGatewayServer },
    { setGatewayWsLogStyle },
    { setVerbose },
    { acquireGatewayLock, GatewayLockError },
    { consumeGatewaySigusr1RestartAuthorization, isGatewaySigusr1RestartExternallyAllowed },
    { defaultRuntime },
    { enableConsoleCapture, setConsoleTimestampPrefix },
    commandQueueMod,
    { createRestartIterationHook },
  ] = await Promise.all([
    import("../config/config.js"),
    import("../gateway/server.js"),
    import("../gateway/ws-logging.js"),
    import("../globals.js"),
    import("../infra/gateway-lock.js"),
    import("../infra/restart.js"),
    import("../runtime.js"),
    import("../logging.js"),
    import("../process/command-queue.js"),
    import("../process/restart-recovery.js"),
  ] as const);

  enableConsoleCapture();
  setConsoleTimestampPrefix(true);
  setVerbose(hasFlag(args, "--verbose"));

  const wsLogRaw = (hasFlag(args, "--compact") ? "compact" : argValue(args, "--ws-log")) as
    | string
    | undefined;
  const wsLogStyle: GatewayWsLogStyle =
    wsLogRaw === "compact" ? "compact" : wsLogRaw === "full" ? "full" : "auto";
  setGatewayWsLogStyle(wsLogStyle);

  const cfg = loadConfig();
  const portRaw =
    argValue(args, "--port") ??
    process.env.OPENCLAW_GATEWAY_PORT ??
    (typeof cfg.gateway?.port === "number" ? String(cfg.gateway.port) : "") ??
    "18789";
  const port = Number.parseInt(portRaw, 10);
  if (Number.isNaN(port) || port <= 0) {
    defaultRuntime.error(`Invalid --port (${portRaw})`);
    process.exit(1);
  }

  const bindRaw =
    argValue(args, "--bind") ??
    process.env.OPENCLAW_GATEWAY_BIND ??
    cfg.gateway?.bind ??
    "loopback";
  const bind =
    bindRaw === "loopback" ||
    bindRaw === "lan" ||
    bindRaw === "auto" ||
    bindRaw === "custom" ||
    bindRaw === "tailnet"
      ? bindRaw
      : null;
  if (!bind) {
    defaultRuntime.error('Invalid --bind (use "loopback", "lan", "tailnet", "auto", or "custom")');
    process.exit(1);
  }

  const token = argValue(args, "--token");
  if (token) process.env.OPENCLAW_GATEWAY_TOKEN = token;

  let server: Awaited<ReturnType<typeof startGatewayServer>> | null = null;
  let lock: GatewayLockHandle | null = null;
  let shuttingDown = false;
  let forceExitTimer: ReturnType<typeof setTimeout> | null = null;
  let restartResolver: (() => void) | null = null;

  const cleanupSignals = () => {
    process.removeListener("SIGTERM", onSigterm);
    process.removeListener("SIGINT", onSigint);
    process.removeListener("SIGUSR1", onSigusr1);
  };

  const request = (action: "stop" | "restart", signal: string) => {
    if (shuttingDown) {
      defaultRuntime.log(`gateway: received ${signal} during shutdown; ignoring`);
      return;
    }
    shuttingDown = true;
    const isRestart = action === "restart";
    defaultRuntime.log(
      `gateway: received ${signal}; ${isRestart ? "restarting" : "shutting down"}`,
    );

    const DRAIN_TIMEOUT_MS = 30_000;
    const SHUTDOWN_TIMEOUT_MS = 5_000;
    const forceExitMs = isRestart ? DRAIN_TIMEOUT_MS + SHUTDOWN_TIMEOUT_MS : SHUTDOWN_TIMEOUT_MS;
    forceExitTimer = setTimeout(() => {
      defaultRuntime.error("gateway: shutdown timed out; exiting without full cleanup");
      cleanupSignals();
      process.exit(0);
    }, forceExitMs);

    void (async () => {
      try {
        if (isRestart) {
          const activeTasks = commandQueueMod.getActiveTaskCount();
          if (activeTasks > 0) {
            defaultRuntime.log(
              `gateway: draining ${activeTasks} active task(s) before restart (timeout ${DRAIN_TIMEOUT_MS}ms)`,
            );
            const { drained } = await commandQueueMod.waitForActiveTasks(DRAIN_TIMEOUT_MS);
            if (drained) {
              defaultRuntime.log("gateway: all active tasks drained");
            } else {
              defaultRuntime.log("gateway: drain timeout reached; proceeding with restart");
            }
          }
        }

        await server?.close({
          reason: isRestart ? "gateway restarting" : "gateway stopping",
          restartExpectedMs: isRestart ? 1500 : null,
        });
      } catch (err) {
        defaultRuntime.error(`gateway: shutdown error: ${String(err)}`);
      } finally {
        if (forceExitTimer) clearTimeout(forceExitTimer);
        server = null;
        if (isRestart) {
          shuttingDown = false;
          restartResolver?.();
        } else {
          cleanupSignals();
          process.exit(0);
        }
      }
    })();
  };

  const onSigterm = () => {
    defaultRuntime.log("gateway: signal SIGTERM received");
    request("stop", "SIGTERM");
  };
  const onSigint = () => {
    defaultRuntime.log("gateway: signal SIGINT received");
    request("stop", "SIGINT");
  };
  const onSigusr1 = () => {
    defaultRuntime.log("gateway: signal SIGUSR1 received");
    const authorized = consumeGatewaySigusr1RestartAuthorization();
    if (!authorized && !isGatewaySigusr1RestartExternallyAllowed()) {
      defaultRuntime.log(
        "gateway: SIGUSR1 restart ignored (not authorized; enable commands.restart or use gateway tool).",
      );
      return;
    }
    request("restart", "SIGUSR1");
  };

  process.on("SIGTERM", onSigterm);
  process.on("SIGINT", onSigint);
  process.on("SIGUSR1", onSigusr1);

  try {
    try {
      lock = await acquireGatewayLock();
    } catch (err) {
      if (err instanceof GatewayLockError) {
        defaultRuntime.error(`Gateway start blocked: ${err.message}`);
        process.exit(1);
      }
      throw err;
    }
    const onIteration = createRestartIterationHook(() => {
      // After an in-process restart (SIGUSR1), reset command-queue lane state.
      // Interrupted tasks from the previous lifecycle may have left `active`
      // counts elevated (their finally blocks never ran), permanently blocking
      // new work from draining.
      commandQueueMod.resetAllLanes();
    });

    // eslint-disable-next-line no-constant-condition
    while (true) {
      onIteration();
      try {
        server = await startGatewayServer(port, { bind });
      } catch (err) {
        cleanupSignals();
        defaultRuntime.error(`Gateway failed to start: ${String(err)}`);
        process.exit(1);
      }
      await new Promise<void>((resolve) => {
        restartResolver = resolve;
      });
    }
  } finally {
    await lock?.release();
    cleanupSignals();
  }
}

void main().catch((err) => {
  console.error(
    "[clawdbot] Gateway daemon failed:",
    err instanceof Error ? (err.stack ?? err.message) : err,
  );
  process.exit(1);
});
