import { EventEmitter } from "node:events";
import { resolve } from "node:path";
const DEFAULT_CWD_KEY = "__default__";
const DEFAULT_SNAPSHOT_TTL_MS = 300000;
export class ProviderSnapshotManager {
    constructor(providerRegistry, logger, options = {}) {
        this.providerRegistry = providerRegistry;
        this.logger = logger;
        this.snapshots = new Map();
        this.lastCheckedAts = new Map();
        this.warmUps = new Map();
        this.events = new EventEmitter();
        this.destroyed = false;
        this.ttlMs = options.ttlMs ?? DEFAULT_SNAPSHOT_TTL_MS;
        this.now = options.now ?? Date.now;
    }
    getSnapshot(cwd) {
        const cwdKey = normalizeCwdKey(cwd);
        const entries = this.snapshots.get(cwdKey);
        if (!entries) {
            const loadingEntries = this.resetSnapshotToLoading(cwdKey);
            void this.warmUp(cwd);
            return entriesToArray(loadingEntries);
        }
        if (this.shouldRevalidate(cwdKey)) {
            void this.warmUp(cwd);
        }
        return entriesToArray(entries);
    }
    async refresh(options = {}) {
        const { cwd } = options;
        const cwdKey = normalizeCwdKey(cwd);
        const inFlight = this.warmUps.get(cwdKey);
        if (inFlight) {
            await inFlight;
            return;
        }
        const providers = this.resolveRefreshProviders(options.providers);
        this.resetSnapshotToLoading(cwdKey, providers);
        this.emitChange(cwdKey);
        await this.warmUp(cwd, providers);
    }
    on(event, listener) {
        this.events.on(event, listener);
        return this;
    }
    off(event, listener) {
        this.events.off(event, listener);
        return this;
    }
    destroy() {
        this.destroyed = true;
        this.events.removeAllListeners();
        this.snapshots.clear();
        this.lastCheckedAts.clear();
        this.warmUps.clear();
    }
    createLoadingEntries() {
        const entries = new Map();
        for (const provider of this.getProviderIds()) {
            const definition = this.providerRegistry[provider];
            entries.set(provider, {
                provider,
                status: "loading",
                label: definition?.label,
                description: definition?.description,
                defaultModeId: definition?.defaultModeId ?? null,
            });
        }
        return entries;
    }
    async warmUp(cwd, providers) {
        const cwdKey = normalizeCwdKey(cwd);
        const inFlight = this.warmUps.get(cwdKey);
        if (inFlight) {
            return inFlight;
        }
        const providersToRefresh = providers ?? this.getProviderIds();
        const warmUpPromise = Promise.allSettled(providersToRefresh.map((provider) => this.refreshProvider(cwdKey, provider, cwd))).then(() => {
            if (!providers) {
                this.lastCheckedAts.set(cwdKey, this.now());
            }
        });
        this.warmUps.set(cwdKey, warmUpPromise);
        try {
            await warmUpPromise;
        }
        finally {
            if (this.warmUps.get(cwdKey) === warmUpPromise) {
                this.warmUps.delete(cwdKey);
            }
        }
    }
    async refreshProvider(cwdKey, provider, cwd) {
        const definition = this.providerRegistry[provider];
        if (!definition) {
            return;
        }
        const snapshot = this.getOrCreateSnapshot(cwdKey);
        try {
            const client = definition.createClient(this.logger);
            const available = await client.isAvailable();
            if (!available) {
                snapshot.set(provider, {
                    provider,
                    status: "unavailable",
                    label: definition.label,
                    description: definition.description,
                    defaultModeId: definition.defaultModeId,
                });
                this.emitChange(cwdKey);
                return;
            }
            const [models, modes] = await Promise.all([
                definition.fetchModels({ cwd }),
                definition.fetchModes({ cwd }),
            ]);
            snapshot.set(provider, {
                provider,
                status: "ready",
                models,
                modes,
                fetchedAt: new Date().toISOString(),
                label: definition.label,
                description: definition.description,
                defaultModeId: definition.defaultModeId,
            });
            this.emitChange(cwdKey);
        }
        catch (error) {
            snapshot.set(provider, {
                provider,
                status: "error",
                error: toErrorMessage(error),
                label: definition.label,
                description: definition.description,
                defaultModeId: definition.defaultModeId,
            });
            this.logger.warn({ err: error, provider, cwd: cwdKey }, "Failed to refresh provider snapshot");
            this.emitChange(cwdKey);
        }
    }
    emitChange(cwdKey) {
        if (this.destroyed) {
            return;
        }
        const snapshot = this.snapshots.get(cwdKey);
        if (!snapshot) {
            return;
        }
        this.events.emit("change", entriesToArray(snapshot), denormalizeCwdKey(cwdKey));
    }
    shouldRevalidate(cwdKey) {
        if (this.warmUps.has(cwdKey)) {
            return false;
        }
        const lastCheckedAt = this.lastCheckedAts.get(cwdKey);
        if (lastCheckedAt === undefined) {
            return false;
        }
        return this.now() - lastCheckedAt > this.ttlMs;
    }
    getOrCreateSnapshot(cwdKey) {
        const existing = this.snapshots.get(cwdKey);
        if (existing) {
            return existing;
        }
        const created = this.createLoadingEntries();
        this.snapshots.set(cwdKey, created);
        return created;
    }
    resetSnapshotToLoading(cwdKey, providers) {
        const snapshot = this.getOrCreateSnapshot(cwdKey);
        const loadingEntries = this.createLoadingEntries();
        if (!providers) {
            snapshot.clear();
            for (const [provider, entry] of loadingEntries) {
                snapshot.set(provider, entry);
            }
            return snapshot;
        }
        for (const provider of providers) {
            const loadingEntry = loadingEntries.get(provider);
            if (!loadingEntry)
                continue;
            const existing = snapshot.get(provider);
            snapshot.set(provider, {
                ...loadingEntry,
                models: existing?.models,
                modes: existing?.modes,
                fetchedAt: existing?.fetchedAt,
            });
        }
        return snapshot;
    }
    getProviderIds() {
        return Object.keys(this.providerRegistry);
    }
    resolveRefreshProviders(providers) {
        if (!providers || providers.length === 0) {
            return undefined;
        }
        const providerIds = new Set(this.getProviderIds());
        return Array.from(new Set(providers)).filter((provider) => providerIds.has(provider));
    }
}
function normalizeCwdKey(cwd) {
    if (!cwd) {
        return DEFAULT_CWD_KEY;
    }
    const trimmed = cwd.trim();
    if (!trimmed) {
        return DEFAULT_CWD_KEY;
    }
    return resolve(trimmed);
}
function denormalizeCwdKey(cwdKey) {
    return cwdKey === DEFAULT_CWD_KEY ? undefined : cwdKey;
}
function entriesToArray(entries) {
    return Array.from(entries.values(), cloneEntry);
}
function cloneEntry(entry) {
    return {
        ...entry,
        models: entry.models?.map((model) => ({ ...model })),
        modes: entry.modes?.map((mode) => ({ ...mode })),
    };
}
function toErrorMessage(error) {
    if (error instanceof Error && error.message) {
        return error.message;
    }
    if (typeof error === "string" && error) {
        return error;
    }
    return "Unknown error";
}
//# sourceMappingURL=provider-snapshot-manager.js.map