#!/usr/bin/env node
// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * WU-2642 (INIT-057 Phase 4): Long-running entry for the control-plane
 * event-sync sidecar.
 *
 * wu:claim (via {@link spawnControlPlaneSidecar}) spawns this script as a
 * detached Node.js child. wu:done (via {@link stopControlPlaneSidecar})
 * signals it with SIGTERM. SIGTERM triggers a clean dispose() of the
 * underlying {@link startControlPlaneEventSync} handle.
 *
 * Environment inputs (set by the spawner):
 *   LUMENFLOW_SIDECAR_WORKSPACE_ROOT  — absolute path to the workspace root
 *   LUMENFLOW_SIDECAR_INTERVAL_MS     — poll cadence in milliseconds
 *
 * Reads workspace.yaml + `LUMENFLOW_CONTROL_PLANE_TOKEN_ENV` to resolve the
 * control-plane sync port. Exits silently when config is absent (we don't
 * want to spam logs of users who haven't configured a control plane).
 */
import path from 'node:path';
import { readFile } from 'node:fs/promises';
import YAML from 'yaml';
import { CONFIG_FILES, ENV_VARS } from '@lumenflow/core/wu-constants';
import { getErrorMessage } from '@lumenflow/core/error-handler';
import { runCLI } from './cli-entry-point.js';
// NOTE: The runner deliberately does **not** import from `@lumenflow/surfaces`
// because that package has no package.json (ADR-058 open question — license
// ambiguity) and is not resolvable by Node's module resolver from the CLI
// package. The in-process entry point `startControlPlaneEventSync` lives in
// surfaces/http/sidecar-entry.ts and is imported by server.ts directly; this
// long-running runner re-implements the minimal polling loop against the
// same `ControlPlaneSyncPortLike` contract so the two paths can't drift.
const SIDECAR_ENV = {
    WORKSPACE_ROOT: ENV_VARS.SIDECAR_WORKSPACE_ROOT,
    INTERVAL_MS: ENV_VARS.SIDECAR_INTERVAL_MS,
};
const LOG_PREFIX = '[control-plane-sidecar]';
const DEFAULT_INTERVAL_MS = 30_000;
const EXIT_CODE_CONFIG_MISSING = 0;
const EXIT_CODE_SDK_UNAVAILABLE = 0;
const TEXT_ENCODING_UTF8 = 'utf-8';
function isRecord(value) {
    return typeof value === 'object' && value !== null;
}
function asNonEmptyString(value) {
    if (typeof value !== 'string')
        return undefined;
    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : undefined;
}
function resolveIntervalMs() {
    const raw = process.env[SIDECAR_ENV.INTERVAL_MS];
    const parsed = raw ? Number.parseInt(raw, 10) : NaN;
    return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_INTERVAL_MS;
}
async function loadWorkspaceDoc(workspaceRoot) {
    try {
        const content = await readFile(path.join(workspaceRoot, CONFIG_FILES.WORKSPACE_CONFIG), TEXT_ENCODING_UTF8);
        const parsed = YAML.parse(content);
        return isRecord(parsed) ? parsed : null;
    }
    catch {
        return null;
    }
}
async function main() {
    const workspaceRoot = process.env[SIDECAR_ENV.WORKSPACE_ROOT] ?? process.cwd();
    const intervalMs = resolveIntervalMs();
    const doc = await loadWorkspaceDoc(workspaceRoot);
    if (!doc) {
        // Nothing to sync without workspace.yaml. Silent exit is intentional —
        // wu:claim shouldn't be noisy for users who haven't enabled the control
        // plane.
        process.exit(EXIT_CODE_CONFIG_MISSING);
    }
    const workspaceId = asNonEmptyString(Reflect.get(doc, 'id'));
    const controlPlaneRaw = Reflect.get(doc, 'control_plane');
    if (!workspaceId || !isRecord(controlPlaneRaw)) {
        process.exit(EXIT_CODE_CONFIG_MISSING);
    }
    let sdk;
    try {
        sdk = await import('@lumenflow/control-plane-sdk');
    }
    catch (error) {
        console.warn(`${LOG_PREFIX} control-plane SDK unavailable: ${getErrorMessage(error)}`);
        process.exit(EXIT_CODE_SDK_UNAVAILABLE);
    }
    let runtimeConfig;
    try {
        runtimeConfig = sdk.parseWorkspaceControlPlaneConfig({
            id: workspaceId,
            control_plane: controlPlaneRaw,
        });
    }
    catch (error) {
        console.warn(`${LOG_PREFIX} workspace.yaml control_plane invalid: ${getErrorMessage(error)}`);
        process.exit(EXIT_CODE_CONFIG_MISSING);
    }
    const token = asNonEmptyString(process.env[runtimeConfig.control_plane.auth.token_env]);
    if (!token) {
        // No token — skip rather than warning on every claim.
        process.exit(EXIT_CODE_CONFIG_MISSING);
    }
    const syncPort = sdk.createHttpControlPlaneSyncPort(runtimeConfig.control_plane, {
        warn: (message) => console.warn(`${LOG_PREFIX} ${message}`),
    });
    // Minimal polling loop — mirrors startControlPlaneEventSync's contract.
    // Stops on SIGTERM/SIGINT so wu:done / session-end can shut it down.
    let inFlight = false;
    const tick = async () => {
        if (inFlight)
            return;
        inFlight = true;
        try {
            await syncPort.pullPolicies({ workspace_id: workspaceId });
            await syncPort.heartbeat({
                workspace_id: workspaceId,
                session_id: `${LOG_PREFIX}:${new Date().toISOString()}`,
            });
        }
        catch (error) {
            console.warn(`${LOG_PREFIX} sync cycle failed: ${getErrorMessage(error)}`);
        }
        finally {
            inFlight = false;
        }
    };
    const timer = setInterval(() => {
        void tick();
    }, intervalMs);
    const shutdown = () => {
        clearInterval(timer);
        process.exit(0);
    };
    process.on('SIGTERM', shutdown);
    process.on('SIGINT', shutdown);
}
// WU-1537/WU-2653: Use import.meta.main + runCLI for consistent entry-point
// handling across all CLI files (see cli-entry-point.test.ts).
if (import.meta.main) {
    void runCLI(main);
}
//# sourceMappingURL=control-plane-sidecar-runner.js.map