"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.APPS_CONFIGURATION_REL_PATH = exports.DEFAULT_APPS_RESOLUTION_STRATEGY = void 0;
exports.parseResolutionStrategy = parseResolutionStrategy;
exports.normalizeAppsResolutionStrategy = normalizeAppsResolutionStrategy;
exports.loadAppsConfigurationFile = loadAppsConfigurationFile;
exports.writeAppsConfigurationFile = writeAppsConfigurationFile;
exports.writeAppsConfigurationFileIfChanged = writeAppsConfigurationFileIfChanged;
exports.isAppDisabledInConfiguration = isAppDisabledInConfiguration;
exports.setAppDisabledInConfiguration = setAppDisabledInConfiguration;
const promises_1 = __importDefault(require("node:fs/promises"));
const node_path_1 = __importDefault(require("node:path"));
const yaml_1 = __importDefault(require("yaml"));
exports.DEFAULT_APPS_RESOLUTION_STRATEGY = {
    order: ['local'],
    localRoots: ['dominds-apps'],
};
exports.APPS_CONFIGURATION_REL_PATH = node_path_1.default.join('.apps', 'configuration.yaml');
function isRecord(v) {
    return typeof v === 'object' && v !== null && !Array.isArray(v);
}
function asString(v) {
    return typeof v === 'string' ? v : null;
}
function parseResolutionStrategy(v, at) {
    if (!isRecord(v))
        return { ok: false, errorText: `Invalid ${at}: expected object` };
    const keys = Object.keys(v);
    for (const k of keys) {
        if (k !== 'order' && k !== 'localRoots') {
            return { ok: false, errorText: `Invalid ${at}: unknown key '${k}'` };
        }
    }
    const orderRaw = v['order'];
    const order = (() => {
        if (orderRaw === undefined)
            return undefined;
        if (!Array.isArray(orderRaw)) {
            return { ok: false, errorText: `Invalid ${at}.order: expected array` };
        }
        const out = [];
        for (let i = 0; i < orderRaw.length; i += 1) {
            const item = orderRaw[i];
            if (item !== 'local' && item !== 'npx') {
                return {
                    ok: false,
                    errorText: `Invalid ${at}.order[${i}]: expected 'local'|'npx'`,
                };
            }
            out.push(item);
        }
        if (out.length === 0) {
            return { ok: false, errorText: `Invalid ${at}.order: must not be empty` };
        }
        return { ok: true, value: out };
    })();
    if (order && !order.ok)
        return order;
    const localRootsRaw = v['localRoots'];
    const localRoots = (() => {
        if (localRootsRaw === undefined)
            return undefined;
        if (!Array.isArray(localRootsRaw)) {
            return { ok: false, errorText: `Invalid ${at}.localRoots: expected array` };
        }
        const out = [];
        for (let i = 0; i < localRootsRaw.length; i += 1) {
            const item = localRootsRaw[i];
            if (typeof item !== 'string' || item.trim() === '') {
                return {
                    ok: false,
                    errorText: `Invalid ${at}.localRoots[${i}]: expected non-empty string`,
                };
            }
            out.push(item.trim());
        }
        if (out.length === 0) {
            return { ok: false, errorText: `Invalid ${at}.localRoots: must not be empty` };
        }
        return { ok: true, value: out };
    })();
    if (localRoots && !localRoots.ok)
        return localRoots;
    return {
        ok: true,
        strategy: {
            order: order ? order.value : undefined,
            localRoots: localRoots ? localRoots.value : undefined,
        },
    };
}
function normalizeAppsResolutionStrategy(raw) {
    const order = raw?.order ??
        exports.DEFAULT_APPS_RESOLUTION_STRATEGY.order ??
        ['local'];
    const localRoots = raw?.localRoots ??
        exports.DEFAULT_APPS_RESOLUTION_STRATEGY.localRoots ?? ['dominds-apps'];
    if (order.length === 0) {
        throw new Error(`Invalid ${exports.APPS_CONFIGURATION_REL_PATH}: resolutionStrategy.order must not be empty`);
    }
    if (localRoots.length === 0) {
        throw new Error(`Invalid ${exports.APPS_CONFIGURATION_REL_PATH}: resolutionStrategy.localRoots must not be empty`);
    }
    if (new Set(order).size !== order.length) {
        throw new Error(`Invalid ${exports.APPS_CONFIGURATION_REL_PATH}: resolutionStrategy.order has duplicates`);
    }
    if (new Set(localRoots).size !== localRoots.length) {
        throw new Error(`Invalid ${exports.APPS_CONFIGURATION_REL_PATH}: resolutionStrategy.localRoots has duplicates`);
    }
    return { order, localRoots };
}
function canonicalizeConfigurationFile(file) {
    const disabledApps = file.disabledApps
        ? [
            ...new Set(file.disabledApps.map((appId) => appId.trim()).filter((appId) => appId !== '')),
        ].sort()
        : [];
    return {
        schemaVersion: 1,
        resolutionStrategy: file.resolutionStrategy,
        disabledApps: disabledApps.length > 0 ? disabledApps : undefined,
    };
}
function parseConfigurationFile(parsed, filePathAbs) {
    if (!isRecord(parsed)) {
        return { ok: false, errorText: `Invalid configuration.yaml: expected object (${filePathAbs})` };
    }
    const keys = Object.keys(parsed);
    for (const k of keys) {
        if (k !== 'schemaVersion' && k !== 'resolutionStrategy' && k !== 'disabledApps') {
            return {
                ok: false,
                errorText: `Invalid configuration.yaml: unknown key '${k}' (${filePathAbs})`,
            };
        }
    }
    const schemaVersion = parsed['schemaVersion'];
    if (schemaVersion !== 1) {
        return {
            ok: false,
            errorText: `Unsupported configuration.yaml schemaVersion: ${String(schemaVersion)} (${filePathAbs})`,
        };
    }
    const strategyRaw = parsed['resolutionStrategy'];
    const resolutionStrategy = (() => {
        if (strategyRaw === undefined)
            return undefined;
        const strategy = parseResolutionStrategy(strategyRaw, 'resolutionStrategy');
        if (!strategy.ok)
            return strategy;
        return { ok: true, value: strategy.strategy };
    })();
    if (resolutionStrategy && !resolutionStrategy.ok) {
        return { ok: false, errorText: `${resolutionStrategy.errorText} (${filePathAbs})` };
    }
    const disabledRaw = parsed['disabledApps'];
    const disabledApps = (() => {
        if (disabledRaw === undefined)
            return undefined;
        if (!Array.isArray(disabledRaw)) {
            return {
                ok: false,
                errorText: `Invalid disabledApps: expected array (${filePathAbs})`,
            };
        }
        const out = [];
        for (let i = 0; i < disabledRaw.length; i += 1) {
            const item = asString(disabledRaw[i]);
            if (!item || item.trim() === '') {
                return {
                    ok: false,
                    errorText: `Invalid disabledApps[${i}]: expected non-empty string (${filePathAbs})`,
                };
            }
            out.push(item.trim());
        }
        return { ok: true, value: out };
    })();
    if (disabledApps && !disabledApps.ok)
        return disabledApps;
    return {
        ok: true,
        file: canonicalizeConfigurationFile({
            schemaVersion: 1,
            resolutionStrategy: resolutionStrategy ? resolutionStrategy.value : undefined,
            disabledApps: disabledApps ? disabledApps.value : undefined,
        }),
    };
}
async function loadAppsConfigurationFile(params) {
    const filePathAbs = node_path_1.default.resolve(params.rtwsRootAbs, exports.APPS_CONFIGURATION_REL_PATH);
    let raw;
    try {
        raw = await promises_1.default.readFile(filePathAbs, 'utf-8');
    }
    catch (err) {
        const isEnoent = typeof err === 'object' &&
            err !== null &&
            'code' in err &&
            err.code === 'ENOENT';
        if (isEnoent) {
            return {
                kind: 'ok',
                filePathAbs,
                exists: false,
                file: { schemaVersion: 1 },
            };
        }
        return {
            kind: 'error',
            filePathAbs,
            errorText: err instanceof Error ? err.message : String(err),
        };
    }
    let parsed;
    try {
        parsed = yaml_1.default.parse(raw);
    }
    catch (err) {
        return {
            kind: 'error',
            filePathAbs,
            errorText: `Failed to parse YAML: ${err instanceof Error ? err.message : String(err)}`,
        };
    }
    const cfg = parseConfigurationFile(parsed, filePathAbs);
    if (!cfg.ok) {
        return { kind: 'error', filePathAbs, errorText: cfg.errorText };
    }
    return {
        kind: 'ok',
        filePathAbs,
        exists: true,
        file: cfg.file,
    };
}
async function writeAppsConfigurationFile(params) {
    const dirAbs = node_path_1.default.resolve(params.rtwsRootAbs, '.apps');
    await promises_1.default.mkdir(dirAbs, { recursive: true });
    const filePathAbs = node_path_1.default.resolve(params.rtwsRootAbs, exports.APPS_CONFIGURATION_REL_PATH);
    const yamlText = yaml_1.default.stringify(canonicalizeConfigurationFile(params.file));
    await promises_1.default.writeFile(filePathAbs, yamlText, 'utf-8');
}
async function writeAppsConfigurationFileIfChanged(params) {
    const dirAbs = node_path_1.default.resolve(params.rtwsRootAbs, '.apps');
    await promises_1.default.mkdir(dirAbs, { recursive: true });
    const filePathAbs = node_path_1.default.resolve(params.rtwsRootAbs, exports.APPS_CONFIGURATION_REL_PATH);
    const yamlText = yaml_1.default.stringify(canonicalizeConfigurationFile(params.file));
    try {
        const prev = await promises_1.default.readFile(filePathAbs, 'utf-8');
        if (prev === yamlText)
            return;
    }
    catch (err) {
        const isEnoent = typeof err === 'object' &&
            err !== null &&
            'code' in err &&
            err.code === 'ENOENT';
        if (!isEnoent)
            throw err;
    }
    await promises_1.default.writeFile(filePathAbs, yamlText, 'utf-8');
}
function isAppDisabledInConfiguration(file, appId) {
    return (file.disabledApps ?? []).includes(appId);
}
function setAppDisabledInConfiguration(params) {
    const appId = params.appId.trim();
    if (appId === '')
        return params.existing;
    const disabledSet = new Set(params.existing.disabledApps ?? []);
    if (params.disabled)
        disabledSet.add(appId);
    else
        disabledSet.delete(appId);
    const next = canonicalizeConfigurationFile({
        schemaVersion: 1,
        resolutionStrategy: params.existing.resolutionStrategy,
        disabledApps: [...disabledSet],
    });
    const prev = canonicalizeConfigurationFile(params.existing);
    if (yaml_1.default.stringify(prev) === yaml_1.default.stringify(next))
        return params.existing;
    return next;
}
