/**
 * Local host provider: the v1 directory.
 *
 * `list()` is the union of ssh-config `Host` stanzas (read-only, connection
 * details owned by ssh) and inline entries the user registered. The host
 * overlay (caps/os, keyed by name) is merged onto both. We never copy or
 * rewrite ssh config.
 *
 * PHNX-3315: registrations are DEVICE-SCOPED — each box writes only its own
 * `hosts:` block in `devices/<machine>/agents.yaml` (via `Meta.deviceHosts`),
 * so N boxes no longer rewrite one shared `hosts:` map (the pull conflict).
 * Reads are the cross-box UNION of every device doc, plus any lingering central
 * legacy entries drained by the migration.
 */
import { readMeta, updateMeta } from '../../state.js';
import { unionDeviceHosts } from '../../devices/device-docs.js';
import { listSshConfigHosts, isSshConfigHost } from '../ssh-config.js';
/** The EFFECTIVE host overlay: the cross-box union of every device doc's
 * `hosts:` block, with any lingering central-legacy `hosts:` entries as a base
 * (a device doc wins on a name collision). Newest `addedAt` wins across boxes. */
function entries() {
    return { ...readMeta().hosts, ...unionDeviceHosts() };
}
/** THIS box's OWN host registrations (the writable slice in the device doc). */
function ownEntries(meta = readMeta()) {
    return meta.deviceHosts ?? {};
}
function toHost(name, entry, enrolled) {
    return {
        name,
        provider: 'local',
        enrolled,
        source: entry.source,
        address: entry.address,
        user: entry.user,
        os: entry.os,
        caps: entry.caps,
        addedAt: entry.addedAt,
    };
}
export class LocalHostProvider {
    id = 'local';
    capabilities() {
        return { directory: true, mutate: true, presence: false, relay: false, lease: false };
    }
    async list() {
        const overlay = entries();
        const out = [];
        const seen = new Set();
        // Inline + overlaid hosts from the registry.
        for (const [name, entry] of Object.entries(overlay)) {
            out.push(toHost(name, entry, true));
            seen.add(name);
        }
        // ssh-config hosts not already carrying an overlay → available, not enrolled.
        for (const name of listSshConfigHosts()) {
            if (seen.has(name))
                continue;
            out.push(toHost(name, { source: 'ssh-config' }, false));
            seen.add(name);
        }
        return out.sort((a, b) => a.name.localeCompare(b.name));
    }
    async resolve(name) {
        const entry = entries()[name];
        if (entry)
            return toHost(name, entry, true);
        if (isSshConfigHost(name))
            return toHost(name, { source: 'ssh-config' }, false);
        return null;
    }
    async register(spec) {
        const entry = {
            source: spec.source,
            ...(spec.source === 'inline' ? { address: spec.address, user: spec.user } : {}),
            ...(spec.os ? { os: spec.os } : {}),
            ...(spec.caps && spec.caps.length ? { caps: spec.caps } : {}),
            addedAt: spec.addedAt ?? new Date().toISOString(),
        };
        // Device-scoped: land in THIS box's device doc, never the shared central map.
        updateMeta((meta) => ({ ...meta, deviceHosts: { ...ownEntries(meta), [spec.name]: entry } }));
        return toHost(spec.name, entry, true);
    }
    async remove(name) {
        // A box only owns the registrations in its own device doc; drop from there.
        updateMeta((meta) => {
            const hosts = { ...ownEntries(meta) };
            delete hosts[name];
            return { ...meta, deviceHosts: hosts };
        });
    }
}
