"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyCliproxySessionAffinitySettings = exports.applyCliproxyRoutingStrategy = exports.readCliproxySessionAffinityState = exports.readCliproxyRoutingState = exports.fetchLiveCliproxyRoutingStrategy = exports.getConfiguredCliproxySessionAffinitySettings = exports.getConfiguredCliproxyRoutingStrategy = exports.normalizeCliproxySessionAffinityTtl = exports.normalizeCliproxySessionAffinityEnabled = exports.normalizeCliproxyRoutingStrategy = exports.DEFAULT_CLIPROXY_SESSION_AFFINITY_TTL = exports.DEFAULT_CLIPROXY_SESSION_AFFINITY_ENABLED = exports.DEFAULT_CLIPROXY_ROUTING_STRATEGY = void 0;
const generator_1 = require("../config/generator");
const path_resolver_1 = require("../config/path-resolver");
const routing_strategy_http_1 = require("./routing-strategy-http");
const config_loader_facade_1 = require("../../config/config-loader-facade");
exports.DEFAULT_CLIPROXY_ROUTING_STRATEGY = 'round-robin';
exports.DEFAULT_CLIPROXY_SESSION_AFFINITY_ENABLED = false;
exports.DEFAULT_CLIPROXY_SESSION_AFFINITY_TTL = '1h';
const GO_DURATION_SEGMENT = String.raw `(?:\d+(?:\.\d+)?(?:ns|us|µs|μs|ms|s|m|h))`;
const GO_DURATION_PATTERN = new RegExp(`^${GO_DURATION_SEGMENT}+$`);
function normalizeCliproxyRoutingStrategy(value) {
    if (typeof value !== 'string') {
        return null;
    }
    switch (value.trim().toLowerCase()) {
        case 'round-robin':
        case 'roundrobin':
        case 'rr':
            return 'round-robin';
        case 'fill-first':
        case 'fillfirst':
        case 'ff':
            return 'fill-first';
        default:
            return null;
    }
}
exports.normalizeCliproxyRoutingStrategy = normalizeCliproxyRoutingStrategy;
function normalizeCliproxySessionAffinityEnabled(value) {
    if (typeof value === 'boolean') {
        return value;
    }
    if (typeof value !== 'string') {
        return null;
    }
    switch (value.trim().toLowerCase()) {
        case 'true':
        case '1':
        case 'yes':
        case 'on':
        case 'enable':
        case 'enabled':
            return true;
        case 'false':
        case '0':
        case 'no':
        case 'off':
        case 'disable':
        case 'disabled':
            return false;
        default:
            return null;
    }
}
exports.normalizeCliproxySessionAffinityEnabled = normalizeCliproxySessionAffinityEnabled;
function normalizeCliproxySessionAffinityTtl(value) {
    if (typeof value !== 'string') {
        return null;
    }
    const trimmed = value.trim();
    if (!trimmed || !GO_DURATION_PATTERN.test(trimmed) || !hasPositiveDuration(trimmed)) {
        return null;
    }
    return trimmed;
}
exports.normalizeCliproxySessionAffinityTtl = normalizeCliproxySessionAffinityTtl;
function getConfiguredCliproxyRoutingStrategy() {
    return (normalizeCliproxyRoutingStrategy((0, config_loader_facade_1.loadOrCreateUnifiedConfig)().cliproxy?.routing?.strategy) ??
        exports.DEFAULT_CLIPROXY_ROUTING_STRATEGY);
}
exports.getConfiguredCliproxyRoutingStrategy = getConfiguredCliproxyRoutingStrategy;
function getConfiguredCliproxySessionAffinitySettings() {
    const routing = (0, config_loader_facade_1.loadOrCreateUnifiedConfig)().cliproxy?.routing;
    return {
        enabled: normalizeCliproxySessionAffinityEnabled(routing?.session_affinity) ??
            exports.DEFAULT_CLIPROXY_SESSION_AFFINITY_ENABLED,
        ttl: normalizeCliproxySessionAffinityTtl(routing?.session_affinity_ttl) ??
            exports.DEFAULT_CLIPROXY_SESSION_AFFINITY_TTL,
    };
}
exports.getConfiguredCliproxySessionAffinitySettings = getConfiguredCliproxySessionAffinitySettings;
async function fetchLiveCliproxyRoutingStrategy() {
    const response = await (0, routing_strategy_http_1.fetchCliproxyRoutingResponse)((0, routing_strategy_http_1.getCliproxyRoutingTarget)(), 'GET');
    if (!response.ok) {
        throw new Error(await (0, routing_strategy_http_1.getRoutingErrorMessage)(response, `Failed to read routing strategy (${response.status})`));
    }
    const data = (await response.json());
    const strategy = normalizeCliproxyRoutingStrategy(data?.strategy);
    if (!strategy) {
        throw new Error('CLIProxy returned an invalid routing strategy');
    }
    return strategy;
}
exports.fetchLiveCliproxyRoutingStrategy = fetchLiveCliproxyRoutingStrategy;
async function readCliproxyRoutingState() {
    const target = (0, routing_strategy_http_1.getCliproxyRoutingTarget)();
    if (target.isRemote) {
        return {
            strategy: await fetchLiveCliproxyRoutingStrategy(),
            source: 'live',
            target: 'remote',
            reachable: true,
        };
    }
    try {
        return {
            strategy: await fetchLiveCliproxyRoutingStrategy(),
            source: 'live',
            target: 'local',
            reachable: true,
        };
    }
    catch {
        return {
            strategy: getConfiguredCliproxyRoutingStrategy(),
            source: 'config',
            target: 'local',
            reachable: false,
            message: 'Local CLIProxy is not reachable. Showing the saved startup default.',
        };
    }
}
exports.readCliproxyRoutingState = readCliproxyRoutingState;
async function readCliproxySessionAffinityState() {
    const target = (0, routing_strategy_http_1.getCliproxyRoutingTarget)();
    if (target.isRemote) {
        const reachable = await isLiveCliproxyRoutingReachable();
        return {
            source: 'unsupported',
            target: 'remote',
            reachable,
            manageable: false,
            message: reachable
                ? 'Remote session-affinity management is not supported from CCS yet because upstream management APIs only expose routing.strategy.'
                : 'Remote session-affinity management is not supported from CCS yet, and the remote CLIProxy routing endpoint is not reachable.',
        };
    }
    const settings = getConfiguredCliproxySessionAffinitySettings();
    const reachable = await isLiveCliproxyRoutingReachable();
    return {
        enabled: settings.enabled,
        ttl: settings.ttl,
        source: 'config',
        target: 'local',
        reachable,
        manageable: true,
        message: reachable
            ? 'Showing the saved local session-affinity setting. Running local CLIProxy may hot-reload config changes, but CCS does not verify live selector state.'
            : 'Local CLIProxy is not reachable. Showing the saved local startup default.',
    };
}
exports.readCliproxySessionAffinityState = readCliproxySessionAffinityState;
async function applyCliproxyRoutingStrategy(strategy) {
    const target = (0, routing_strategy_http_1.getCliproxyRoutingTarget)();
    const configPath = (0, path_resolver_1.getConfigPathForPort)(target.port);
    const authDir = (0, path_resolver_1.getAuthDir)();
    if (target.isRemote) {
        await updateLiveCliproxyRoutingStrategy(strategy);
        return {
            strategy,
            source: 'live',
            target: 'remote',
            reachable: true,
            applied: 'live',
            message: 'Updated remote CLIProxy routing strategy.',
        };
    }
    (0, config_loader_facade_1.mutateConfig)((config) => {
        if (config.cliproxy) {
            config.cliproxy.routing = { ...config.cliproxy.routing, strategy };
        }
    });
    (0, generator_1.regenerateConfig)(target.port, { configPath, authDir });
    try {
        await updateLiveCliproxyRoutingStrategy(strategy);
        return {
            strategy,
            source: 'live',
            target: 'local',
            reachable: true,
            applied: 'live-and-config',
            message: 'Updated the running proxy and saved the local startup default.',
        };
    }
    catch {
        return {
            strategy,
            source: 'config',
            target: 'local',
            reachable: false,
            applied: 'config-only',
            message: 'Saved the local startup default. It will apply the next time CLIProxy starts.',
        };
    }
}
exports.applyCliproxyRoutingStrategy = applyCliproxyRoutingStrategy;
async function applyCliproxySessionAffinitySettings(settings) {
    const target = (0, routing_strategy_http_1.getCliproxyRoutingTarget)();
    if (target.isRemote) {
        const reachable = await isLiveCliproxyRoutingReachable();
        return {
            source: 'unsupported',
            target: 'remote',
            reachable,
            manageable: false,
            applied: 'unsupported',
            message: reachable
                ? 'Remote session-affinity management is not supported from CCS yet because upstream management APIs only expose routing.strategy.'
                : 'Remote session-affinity management is not supported from CCS yet, and the remote CLIProxy routing endpoint is not reachable.',
        };
    }
    const configPath = (0, path_resolver_1.getConfigPathForPort)(target.port);
    const authDir = (0, path_resolver_1.getAuthDir)();
    const current = getConfiguredCliproxySessionAffinitySettings();
    const ttl = normalizeCliproxySessionAffinityTtl(settings.ttl) ??
        current.ttl ??
        exports.DEFAULT_CLIPROXY_SESSION_AFFINITY_TTL;
    (0, config_loader_facade_1.mutateConfig)((config) => {
        if (config.cliproxy) {
            config.cliproxy.routing = {
                ...config.cliproxy.routing,
                session_affinity: settings.enabled,
                session_affinity_ttl: ttl,
            };
        }
    });
    (0, generator_1.regenerateConfig)(target.port, { configPath, authDir });
    const reachable = await isLiveCliproxyRoutingReachable();
    return {
        enabled: settings.enabled,
        ttl,
        source: 'config',
        target: 'local',
        reachable,
        manageable: true,
        applied: 'config-only',
        message: reachable
            ? 'Saved the local startup default. Running local CLIProxy may hot-reload the session-affinity setting, but CCS does not verify live selector state yet.'
            : 'Saved the local startup default. It will apply the next time local CLIProxy starts.',
    };
}
exports.applyCliproxySessionAffinitySettings = applyCliproxySessionAffinitySettings;
async function updateLiveCliproxyRoutingStrategy(strategy) {
    const response = await (0, routing_strategy_http_1.fetchCliproxyRoutingResponse)((0, routing_strategy_http_1.getCliproxyRoutingTarget)(), 'PUT', {
        value: strategy,
    });
    if (!response.ok) {
        throw new Error(await (0, routing_strategy_http_1.getRoutingErrorMessage)(response, `Failed to update routing strategy (${response.status})`));
    }
}
function hasPositiveDuration(value) {
    const segments = value.match(new RegExp(GO_DURATION_SEGMENT, 'g'));
    if (!segments) {
        return false;
    }
    return segments.some((segment) => {
        const numeric = parseFloat(segment);
        return Number.isFinite(numeric) && numeric > 0;
    });
}
async function isLiveCliproxyRoutingReachable() {
    try {
        await fetchLiveCliproxyRoutingStrategy();
        return true;
    }
    catch {
        return false;
    }
}
//# sourceMappingURL=routing-strategy.js.map