/**
 * Head of Product UI — backend.
 * Migrated from ~/personal/codebase/head-of-product-ui/server.ts (v1).
 * Each concern lives in its own server/lib/* module.
 *
 * Endpoints are registered in this file; logic lives in the libs.
 */
import { Hono } from "hono";
import { serveStatic } from "hono/bun";
import { existsSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { PORT } from "./lib/paths";
import { INSTALLED_PLUGINS } from "./lib/agent";
import { getWorkspaceRoot, applyAuthToEnv, loadSettings } from "./lib/settings";
import { registerTreeRoutes } from "./lib/tree";
import { registerFilesRoutes } from "./lib/files";
import { registerChatsRoutes } from "./lib/chats";
import { registerNowRoutes } from "./lib/now";
import { registerCheckRoutes } from "./lib/check";
import { registerChatRoutes } from "./lib/chat";
import { registerSkillsRoutes } from "./lib/skills";
import { registerSettingsRoutes } from "./lib/settings";
import { registerSetupRoutes } from "./lib/setup";
import { registerMCPRoutes } from "./lib/mcp";
import { startCheckScheduler } from "./lib/check-scheduler";
import { initScanState } from "./lib/scan-state";
import { posthog } from "./lib/posthog";

// Wire stored apiKey (if any) into process.env BEFORE the SDK is first
// queried. Without this, picking the "API key" path in the setup wizard
// would persist the key but every chat would still fail with 401.
applyAuthToEnv();

const app = new Hono();

// Health check. Moved off "/" so the SPA can own that route in production.
app.get("/api/health", (c) =>
  c.json({
    status: "ok",
    port: PORT,
    workspaceRoot: getWorkspaceRoot(),
    pluginsLoaded: INSTALLED_PLUGINS.length,
  })
);

registerTreeRoutes(app);
registerFilesRoutes(app);
registerChatsRoutes(app);
registerNowRoutes(app);
registerCheckRoutes(app);
registerChatRoutes(app);
registerSkillsRoutes(app);
registerSettingsRoutes(app);
registerSetupRoutes(app);
registerMCPRoutes(app);

// Production: serve the built Vite app from /dist. In dev the Vite
// server handles /, and proxies /api/* here — this static block is a
// no-op then because /dist doesn't exist until `bun run build`.
const SERVER_DIR = dirname(fileURLToPath(import.meta.url));
const DIST_DIR = join(SERVER_DIR, "..", "dist");
if (existsSync(DIST_DIR)) {
  app.use("/assets/*", serveStatic({ root: "./dist" }));
  app.use("/*.svg", serveStatic({ root: "./dist" }));
  app.use("/*.png", serveStatic({ root: "./dist" }));
  app.use("/*.ico", serveStatic({ root: "./dist" }));
  // SPA fallback — any unmatched GET serves index.html.
  app.get("*", serveStatic({ path: "./dist/index.html" }));
  console.log(`  Static: serving built app from ${DIST_DIR}`);
}

// Detect orphaned scan state from a crashed previous process before
// the scheduler starts. Without this, a crash mid-scan could leave
// running:true persisted forever and the dashboard would show a stale
// "Scanning…" forever.
initScanState();

// Start the background "today's focus" scheduler. Skip until the user
// has completed setup — otherwise every tick on a fresh Mac fires SDK
// queries with no auth and clutters logs with 401s. Re-evaluated on
// next server restart; the "Re-run setup" button in Settings does a
// hard reload anyway. Set HOP_DISABLE_SCHEDULER=1 to opt out entirely.
if (loadSettings().setupComplete) {
  startCheckScheduler();
} else {
  console.log(`  Scheduler: skipped (setup not complete)`);
}

console.log(`Head of Product UI v2 backend — http://localhost:${PORT}`);
console.log(`  Workspace root: ${getWorkspaceRoot()}`);
console.log(`  Plugins loaded for SDK: ${INSTALLED_PLUGINS.length}`);

// Flush PostHog events on clean exit.
process.on("SIGTERM", () => posthog.shutdown());
process.on("SIGINT", () => posthog.shutdown());

// idleTimeout: 0 — Claude SSE streams can have long gaps between chunks.
// Bun's default of 10s would kill them mid-stream.
export default { port: PORT, idleTimeout: 0, fetch: app.fetch };
