/** Device-scoped routine activation.
 *
 * Definitions say what a routine does. This module owns the independent answer
 * to whether THIS device runs it: membership in the top-level `routines:` list
 * of `~/.agents/devices/<machine>/agents.yaml`.
 */
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'yaml';
import { machineId } from './machine-id.js';
import { getUserAgentsDir, readMeta, updateMeta } from './state.js';
export function normalizeRoutineNames(names) {
    return [...new Set([...names].map((name) => name.trim()).filter(Boolean))].sort();
}
/** null means this device has not materialized activation state yet. */
export function enabledRoutineNames() {
    const names = readMeta().deviceRoutines;
    return Array.isArray(names) ? normalizeRoutineNames(names) : null;
}
export function routineEnabledOnThisDevice(name) {
    const names = enabledRoutineNames();
    return names === null ? null : names.includes(name);
}
export function replaceEnabledRoutines(names) {
    const normalized = normalizeRoutineNames(names);
    updateMeta((meta) => ({ ...meta, deviceRoutines: normalized }));
    return normalized;
}
/**
 * Add or remove one routine on this machine. `legacyEnabledNames` seeds the
 * manifest the first time an upgraded host changes activation, preserving every
 * other routine that was effectively enabled under the old definition fields.
 */
export function setRoutineEnabledOnThisDevice(name, enabled, legacyEnabledNames = []) {
    const current = enabledRoutineNames() ?? normalizeRoutineNames(legacyEnabledNames);
    const next = new Set(current);
    if (enabled)
        next.add(name);
    else
        next.delete(name);
    return replaceEnabledRoutines(next);
}
/** Read-only fleet view from synced device documents. Never writes a peer file. */
export function devicesWithRoutineEnabled(name) {
    const devicesDir = path.join(getUserAgentsDir(), 'devices');
    if (!fs.existsSync(devicesDir))
        return [];
    const devices = [];
    for (const entry of fs.readdirSync(devicesDir, { withFileTypes: true })) {
        if (!entry.isDirectory())
            continue;
        const file = path.join(devicesDir, entry.name, 'agents.yaml');
        if (!fs.existsSync(file))
            continue;
        let parsed;
        try {
            parsed = yaml.parse(fs.readFileSync(file, 'utf-8'));
        }
        catch (err) {
            throw new Error(`Device config corrupted at ${file}: ${err.message}`);
        }
        if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
            throw new Error(`Device config corrupted at ${file}: expected a YAML map.`);
        }
        const routines = parsed.routines;
        if (routines === undefined)
            continue;
        if (!Array.isArray(routines) || routines.some((routine) => typeof routine !== 'string')) {
            throw new Error(`Device config corrupted at ${file}: routines must be a string list.`);
        }
        if (routines.includes(name))
            devices.push(entry.name);
    }
    return devices.sort();
}
/**
 * Build the whole fleet's activation map in one pass over `devices/`.
 *
 * `devicesWithRoutineEnabled` answers this for a single routine and throws on a
 * corrupt peer file — the right contract for a command acting on one routine,
 * the wrong one for a listing, where a single unreadable device document would
 * blank out every row (and re-walking `devices/` per routine is quadratic).
 */
export function routineDeviceIndex() {
    const byRoutine = new Map();
    const errors = [];
    let materialized = false;
    const devicesDir = path.join(getUserAgentsDir(), 'devices');
    if (!fs.existsSync(devicesDir))
        return { byRoutine, materialized, errors };
    let entries;
    try {
        entries = fs.readdirSync(devicesDir, { withFileTypes: true });
    }
    catch (err) {
        return { byRoutine, materialized, errors: [`${devicesDir}: ${err.message}`] };
    }
    for (const entry of entries) {
        if (!entry.isDirectory())
            continue;
        const file = path.join(devicesDir, entry.name, 'agents.yaml');
        if (!fs.existsSync(file))
            continue;
        let parsed;
        try {
            parsed = yaml.parse(fs.readFileSync(file, 'utf-8'));
        }
        catch (err) {
            errors.push(`${file}: ${err.message.split('\n')[0]}`);
            continue;
        }
        if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
            errors.push(`${file}: expected a YAML map`);
            continue;
        }
        const routines = parsed.routines;
        if (routines === undefined)
            continue;
        if (!Array.isArray(routines) || routines.some((routine) => typeof routine !== 'string')) {
            errors.push(`${file}: routines must be a string list`);
            continue;
        }
        materialized = true;
        // Same normalization the writers use (`replaceEnabledRoutines`), so a device
        // that lists a routine twice, or with stray whitespace, cannot make the index
        // disagree with `enabledRoutineNames` about what that device enables.
        for (const name of normalizeRoutineNames(routines)) {
            const devices = byRoutine.get(name);
            if (devices)
                devices.push(entry.name);
            else
                byRoutine.set(name, [entry.name]);
        }
    }
    for (const devices of byRoutine.values())
        devices.sort();
    return { byRoutine, materialized, errors };
}
export function currentRoutineDevice() {
    return machineId();
}
