import express, { Router } from "express";
import path from "node:path";
import fs from "node:fs";
import { fileURLToPath } from "node:url";
import { httpLogger, errorHandler } from "./middleware/index.js";
import { actorMiddleware } from "./middleware/auth.js";
import { boardMutationGuard } from "./middleware/board-mutation-guard.js";
import { privateHostnameGuard, resolvePrivateHostnameAllowSet } from "./middleware/private-hostname-guard.js";
import { healthRoutes } from "./routes/health.js";
import { companyRoutes } from "./routes/companies.js";
import { companySkillRoutes } from "./routes/company-skills.js";
import { agentRoutes } from "./routes/agents.js";
import { projectRoutes } from "./routes/projects.js";
import { issueRoutes } from "./routes/issues.js";
import { routineRoutes } from "./routes/routines.js";
import { executionWorkspaceRoutes } from "./routes/execution-workspaces.js";
import { goalRoutes } from "./routes/goals.js";
import { approvalRoutes } from "./routes/approvals.js";
import { secretRoutes } from "./routes/secrets.js";
import { costRoutes } from "./routes/costs.js";
import { activityRoutes } from "./routes/activity.js";
import { dashboardRoutes } from "./routes/dashboard.js";
import { sidebarBadgeRoutes } from "./routes/sidebar-badges.js";
import { instanceSettingsRoutes } from "./routes/instance-settings.js";
import { llmRoutes } from "./routes/llms.js";
import { assetRoutes } from "./routes/assets.js";
import { accessRoutes } from "./routes/access.js";
import { pluginRoutes } from "./routes/plugins.js";
import { pluginUiStaticRoutes } from "./routes/plugin-ui-static.js";
import { toolConnectionRoutes } from "./routes/tool-connections.js";
import { roleTemplateRoutes } from "./routes/role-templates.js";
import { employeeFileRoutes } from "./routes/employee-files.js";
import { billingRoutes } from "./routes/billing.js";
import { applyUiBranding } from "./ui-branding.js";
import { logger } from "./middleware/logger.js";
import { DEFAULT_LOCAL_PLUGIN_DIR, pluginLoader } from "./services/plugin-loader.js";
import { createPluginWorkerManager } from "./services/plugin-worker-manager.js";
import { createPluginJobScheduler } from "./services/plugin-job-scheduler.js";
import { pluginJobStore } from "./services/plugin-job-store.js";
import { createPluginToolDispatcher } from "./services/plugin-tool-dispatcher.js";
import { pluginLifecycleManager } from "./services/plugin-lifecycle.js";
import { createPluginJobCoordinator } from "./services/plugin-job-coordinator.js";
import { buildHostServices, flushPluginLogBuffer } from "./services/plugin-host-services.js";
import { createPluginEventBus } from "./services/plugin-event-bus.js";
import { setPluginEventBus } from "./services/activity-log.js";
import { createPluginDevWatcher } from "./services/plugin-dev-watcher.js";
import { createPluginHostServiceCleanup } from "./services/plugin-host-service-cleanup.js";
import { pluginRegistryService } from "./services/plugin-registry.js";
import { createHostClientHandlers } from "@zmeel/plugin-sdk";
const FEEDBACK_EXPORT_FLUSH_INTERVAL_MS = 5_000;
export function resolveViteHmrPort(serverPort) {
    if (serverPort <= 55_535) {
        return serverPort + 10_000;
    }
    return Math.max(1_024, serverPort - 10_000);
}
export async function createApp(db, opts) {
    const app = express();
    app.use(express.json({
        // Company import/export payloads can inline full portable packages.
        limit: "10mb",
        verify: (req, _res, buf) => {
            req.rawBody = buf;
        },
    }));
    app.use(httpLogger);
    const privateHostnameGateEnabled = opts.deploymentMode === "authenticated" && opts.deploymentExposure === "private";
    const privateHostnameAllowSet = resolvePrivateHostnameAllowSet({
        allowedHostnames: opts.allowedHostnames,
        bindHost: opts.bindHost,
    });
    app.use(privateHostnameGuard({
        enabled: privateHostnameGateEnabled,
        allowedHostnames: opts.allowedHostnames,
        bindHost: opts.bindHost,
    }));
    app.use(actorMiddleware(db, {
        deploymentMode: opts.deploymentMode,
        resolveSession: opts.resolveSession,
    }));
    app.get("/api/auth/get-session", (req, res) => {
        if (req.actor.type !== "board" || !req.actor.userId) {
            res.status(401).json({ error: "Unauthorized" });
            return;
        }
        res.json({
            session: {
                id: `zmeel:${req.actor.source}:${req.actor.userId}`,
                userId: req.actor.userId,
            },
            user: {
                id: req.actor.userId,
                email: null,
                name: req.actor.source === "local_implicit" ? "Local Board" : null,
            },
        });
    });
    if (opts.betterAuthHandler) {
        app.all("/api/auth/*authPath", opts.betterAuthHandler);
    }
    app.use(llmRoutes(db));
    // Mount API routes
    const api = Router();
    api.use(boardMutationGuard());
    api.use("/health", healthRoutes(db, {
        deploymentMode: opts.deploymentMode,
        deploymentExposure: opts.deploymentExposure,
        authReady: opts.authReady,
        companyDeletionEnabled: opts.companyDeletionEnabled,
        saasMode: opts.saasMode,
    }));
    api.use("/companies", companyRoutes(db, opts.storageService, { saasMode: opts.saasMode }));
    api.use(companySkillRoutes(db));
    api.use(agentRoutes(db));
    api.use(assetRoutes(db, opts.storageService));
    api.use(projectRoutes(db));
    api.use(issueRoutes(db, opts.storageService));
    api.use(routineRoutes(db));
    api.use(executionWorkspaceRoutes(db));
    api.use(goalRoutes(db));
    api.use(approvalRoutes(db));
    api.use(secretRoutes(db));
    api.use(costRoutes(db));
    api.use(billingRoutes(db));
    api.use(activityRoutes(db));
    api.use(dashboardRoutes(db));
    api.use(sidebarBadgeRoutes(db));
    api.use(instanceSettingsRoutes(db));
    api.use(toolConnectionRoutes(db));
    api.use(roleTemplateRoutes());
    api.use(employeeFileRoutes(db));
    const hostServicesDisposers = new Map();
    const workerManager = createPluginWorkerManager();
    const pluginRegistry = pluginRegistryService(db);
    const eventBus = createPluginEventBus();
    setPluginEventBus(eventBus);
    const jobStore = pluginJobStore(db);
    const lifecycle = pluginLifecycleManager(db, { workerManager });
    const scheduler = createPluginJobScheduler({
        db,
        jobStore,
        workerManager,
    });
    const toolDispatcher = createPluginToolDispatcher({
        workerManager,
        lifecycleManager: lifecycle,
        db,
    });
    const jobCoordinator = createPluginJobCoordinator({
        db,
        lifecycle,
        scheduler,
        jobStore,
    });
    const hostServiceCleanup = createPluginHostServiceCleanup(lifecycle, hostServicesDisposers);
    const loader = pluginLoader(db, { localPluginDir: opts.localPluginDir ?? DEFAULT_LOCAL_PLUGIN_DIR }, {
        workerManager,
        eventBus,
        jobScheduler: scheduler,
        jobStore,
        toolDispatcher,
        lifecycleManager: lifecycle,
        instanceInfo: {
            instanceId: opts.instanceId ?? "default",
            hostVersion: opts.hostVersion ?? "0.0.0",
        },
        buildHostHandlers: (pluginId, manifest) => {
            const notifyWorker = (method, params) => {
                const handle = workerManager.getWorker(pluginId);
                if (handle)
                    handle.notify(method, params);
            };
            const services = buildHostServices(db, pluginId, manifest.id, eventBus, notifyWorker);
            hostServicesDisposers.set(pluginId, () => services.dispose());
            return createHostClientHandlers({
                pluginId,
                capabilities: manifest.capabilities,
                services,
            });
        },
    });
    api.use(pluginRoutes(db, loader, { scheduler, jobStore }, { workerManager }, { toolDispatcher }, { workerManager }));
    api.use(accessRoutes(db, {
        deploymentMode: opts.deploymentMode,
        deploymentExposure: opts.deploymentExposure,
        bindHost: opts.bindHost,
        allowedHostnames: opts.allowedHostnames,
    }));
    app.use("/api", api);
    app.use("/api", (_req, res) => {
        res.status(404).json({ error: "API route not found" });
    });
    app.use(pluginUiStaticRoutes(db, {
        localPluginDir: opts.localPluginDir ?? DEFAULT_LOCAL_PLUGIN_DIR,
    }));
    const __dirname = path.dirname(fileURLToPath(import.meta.url));
    if (opts.uiMode === "static") {
        // Try published location first (server/ui-dist/), then monorepo dev location (../../ui/dist)
        const candidates = [
            path.resolve(__dirname, "../ui-dist"),
            path.resolve(__dirname, "../../ui/dist"),
        ];
        const uiDist = candidates.find((p) => fs.existsSync(path.join(p, "index.html")));
        if (uiDist) {
            const indexHtml = applyUiBranding(fs.readFileSync(path.join(uiDist, "index.html"), "utf-8"));
            app.use(express.static(uiDist));
            app.get(/.*/, (_req, res) => {
                res.status(200).set("Content-Type", "text/html").end(indexHtml);
            });
        }
        else {
            console.warn("[zmeel] UI dist not found; running in API-only mode");
        }
    }
    if (opts.uiMode === "vite-dev") {
        const uiRoot = path.resolve(__dirname, "../../ui");
        const hmrPort = resolveViteHmrPort(opts.serverPort);
        const { createServer: createViteServer } = await import("vite");
        const vite = await createViteServer({
            root: uiRoot,
            appType: "custom",
            server: {
                middlewareMode: true,
                hmr: {
                    host: opts.bindHost,
                    port: hmrPort,
                    clientPort: hmrPort,
                },
                allowedHosts: privateHostnameGateEnabled ? Array.from(privateHostnameAllowSet) : undefined,
            },
        });
        app.use(vite.middlewares);
        app.get(/.*/, async (req, res, next) => {
            try {
                const templatePath = path.resolve(uiRoot, "index.html");
                const template = fs.readFileSync(templatePath, "utf-8");
                const html = applyUiBranding(await vite.transformIndexHtml(req.originalUrl, template));
                res.status(200).set({ "Content-Type": "text/html" }).end(html);
            }
            catch (err) {
                next(err);
            }
        });
    }
    app.use(errorHandler);
    jobCoordinator.start();
    scheduler.start();
    const feedbackExportTimer = opts.feedbackExportService
        ? setInterval(() => {
            void opts.feedbackExportService?.flushPendingFeedbackTraces().catch((err) => {
                logger.error({ err }, "Failed to flush pending feedback exports");
            });
        }, FEEDBACK_EXPORT_FLUSH_INTERVAL_MS)
        : null;
    feedbackExportTimer?.unref?.();
    if (opts.feedbackExportService) {
        void opts.feedbackExportService.flushPendingFeedbackTraces().catch((err) => {
            logger.error({ err }, "Failed to flush pending feedback exports");
        });
    }
    void toolDispatcher.initialize().catch((err) => {
        logger.error({ err }, "Failed to initialize plugin tool dispatcher");
    });
    const devWatcher = opts.uiMode === "vite-dev"
        ? createPluginDevWatcher(lifecycle, async (pluginId) => (await pluginRegistry.getById(pluginId))?.packagePath ?? null)
        : null;
    void loader.loadAll().then((result) => {
        if (!result)
            return;
        for (const loaded of result.results) {
            if (devWatcher && loaded.success && loaded.plugin.packagePath) {
                devWatcher.watch(loaded.plugin.id, loaded.plugin.packagePath);
            }
        }
    }).catch((err) => {
        logger.error({ err }, "Failed to load ready plugins on startup");
    });
    process.once("exit", () => {
        if (feedbackExportTimer)
            clearInterval(feedbackExportTimer);
        devWatcher?.close();
        hostServiceCleanup.disposeAll();
        hostServiceCleanup.teardown();
    });
    process.once("beforeExit", () => {
        void flushPluginLogBuffer();
    });
    return app;
}
//# sourceMappingURL=app.js.map