/**
 * HTTP sync server extracted from WarpRuntime.serve().
 *
 * Handles request routing, JSON parsing, validation, and error responses
 * for the sync protocol. All HTTP I/O flows through an HttpServerPort
 * so the domain never touches node:http directly.
 *
 * @module domain/services/sync/HttpSyncServer
 */
import SyncError from "../../errors/SyncError.js";
import nullLogger from "../../utils/nullLogger.js";
import { parseOptions, errorResponse, internalSyncErrorResponse, jsonResponse, checkContentType, validateRoute, checkBodySize, parseBody, initAuth, waitForListen, buildListenResult, extractFrontierWriters, } from "./HttpSyncServerHelpers.js";
export default class HttpSyncServer {
    _httpPort;
    _graph;
    _logger;
    _path;
    _host;
    _maxRequestBytes;
    _auth;
    _authMode;
    constructor(options) {
        const parsed = parseOptions(options);
        this._httpPort = parsed.httpPort;
        this._graph = parsed.graph;
        this._logger = parsed.logger ?? nullLogger;
        this._path = parsed.path;
        this._host = parsed.host;
        this._maxRequestBytes = parsed.maxRequestBytes;
        const authInit = initAuth(parsed.auth, parsed.allowedWriters);
        this._auth = authInit.auth;
        this._authMode = authInit.authMode;
    }
    /**
     * Runs auth verification and writer whitelist checks. Returns an error
     * response when enforcement blocks the request, or null to proceed.
     *
     * In log-only mode both checks record metrics/logs but always return
     * null so the request proceeds.
     */
    async _authorize(request, parsed) {
        if (this._auth === null) {
            return null;
        }
        const sigError = await this._verifySignature(request);
        if (sigError !== null) {
            return sigError;
        }
        return this._checkWriterWhitelist(parsed);
    }
    /**
     * Verifies the request signature via SyncAuthService.
     */
    async _verifySignature(request) {
        const auth = this._auth;
        const authResult = await auth.verify(request);
        if (!authResult.ok) {
            if (this._authMode === 'enforce') {
                return errorResponse(authResult.status, authResult.reason);
            }
            auth.recordLogOnlyPassthrough();
        }
        return null;
    }
    /**
     * Checks writer IDs from the request frontier against the whitelist.
     */
    _checkWriterWhitelist(parsed) {
        const auth = this._auth;
        const writerIds = extractFrontierWriters(parsed);
        if (writerIds.length === 0) {
            return null;
        }
        const writerResult = auth.enforceWriters(writerIds);
        if (!writerResult.ok) {
            return errorResponse(writerResult.status, writerResult.reason);
        }
        return null;
    }
    /**
     * Handles an incoming HTTP request through the sync pipeline.
     */
    async _handleRequest(request) {
        const req = {
            method: request.method,
            url: request.url,
            headers: request.headers,
            ...(request.body !== undefined ? { body: request.body } : {}),
        };
        const preflightError = this._preflight(req);
        if (preflightError !== null) {
            return preflightError;
        }
        const { error, parsed } = parseBody(req.body);
        if (error !== null) {
            return error;
        }
        const authError = await this._authorize(req, parsed); // nosemgrep: ts-no-double-cast -- 0025A; nosemgrep: ts-no-record-string-unknown-outside-adapters -- 0025B; nosemgrep: ts-no-unknown-outside-adapters -- 0025B
        if (authError !== null) {
            return authError;
        }
        return await this._executeSyncRequest(parsed);
    }
    /**
     * Runs content-type, route, and body-size validation.
     */
    _preflight(req) {
        const contentTypeError = checkContentType(req.headers);
        if (contentTypeError !== null) {
            return contentTypeError;
        }
        const routeError = validateRoute(req, this._path, this._host);
        if (routeError !== null) {
            return routeError;
        }
        return checkBodySize(req.body, this._maxRequestBytes);
    }
    /**
     * Forwards the parsed sync request to the graph and wraps errors.
     */
    async _executeSyncRequest(parsed) {
        try {
            const response = await this._graph.processSyncRequest(parsed);
            return jsonResponse(response);
        }
        catch (err) {
            this._logger.error('sync server: processSyncRequest failed', syncFailureLogFields(err instanceof Error ? err : null));
            return internalSyncErrorResponse();
        }
    }
    /**
     * Starts the HTTP sync server.
     *
     * @param port - Port to listen on (0 for ephemeral)
     * @returns Server handle with `url` and `close()` method
     * @throws SyncError If port is not a number
     */
    async listen(port) {
        if (typeof port !== 'number') {
            throw new SyncError('listen() requires a numeric port', { code: 'E_SYNC_PROTOCOL' });
        }
        const server = this._httpPort.createServer((request) => this._handleRequest(request));
        await waitForListen(server, port, this._host);
        return buildListenResult({ server, port, host: this._host, path: this._path });
    }
}
function syncFailureLogFields(err) {
    if (err !== null) {
        return { error: err };
    }
    return { thrownType: 'non-error' };
}
