import { VERSION } from "../config.js";
import { getPiUserAgent } from "../utils/pi-user-agent.js";
const DEFAULT_CATALOG_BASE_URL = "https://pi.dev";
export const REMOTE_CATALOG_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;
function mergeModels(baseline, dynamic) {
    const merged = [...baseline];
    for (const model of dynamic) {
        const index = merged.findIndex((entry) => entry.id === model.id);
        if (index >= 0)
            merged[index] = model;
        else
            merged.push(model);
    }
    return merged;
}
function parseCatalog(providerId, value) {
    const entries = Array.isArray(value)
        ? value
        : "models" in value && Array.isArray(value.models)
            ? value.models
            : Object.values(value);
    return entries
        .filter((entry) => typeof entry === "object" && entry !== null && "id" in entry)
        .map((model) => ({ ...model, provider: providerId }));
}
function settleOnAbort(operation, signal, canSettle) {
    if (!signal)
        return operation;
    if (signal.aborted && canSettle())
        return Promise.resolve();
    return new Promise((resolve, reject) => {
        const abort = () => {
            if (canSettle())
                resolve();
        };
        signal.addEventListener("abort", abort, { once: true });
        operation.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
    });
}
/** Add a persisted pi.dev catalog overlay to a static built-in provider. */
export function withRemoteCatalog(provider, catalogBaseUrl = DEFAULT_CATALOG_BASE_URL) {
    let dynamicModels = [];
    let inflightRefresh;
    let refreshEpoch = 0;
    let persistenceInProgress = false;
    const refreshModels = (context) => {
        if (inflightRefresh) {
            const requiresEscalation = (context.allowNetwork && !inflightRefresh.allowNetwork)
                || (context.force === true && !inflightRefresh.force)
                || (inflightRefresh.signal?.aborted === true && context.signal?.aborted !== true);
            if (requiresEscalation) {
                const startEscalation = () => {
                    if (context.signal?.aborted)
                        return;
                    return refreshModels(context);
                };
                const escalation = inflightRefresh.promise.then(startEscalation, startEscalation);
                return settleOnAbort(escalation, context.signal, () => !persistenceInProgress);
            }
            const owner = inflightRefresh;
            const retryAfterAbortedOwner = () => {
                if (owner.signal?.aborted && !context.signal?.aborted)
                    return refreshModels(context);
            };
            const joined = owner.promise.then(retryAfterAbortedOwner, (error) => {
                const retry = retryAfterAbortedOwner();
                if (retry)
                    return retry;
                throw error;
            });
            return settleOnAbort(joined, context.signal, () => !persistenceInProgress);
        }
        const epoch = ++refreshEpoch;
        const isCurrent = () => epoch === refreshEpoch && !context.signal?.aborted;
        const persist = async (entry) => {
            persistenceInProgress = true;
            try {
                await context.store.write(entry);
            }
            finally {
                persistenceInProgress = false;
            }
        };
        const operation = (async () => {
            const stored = await context.store.read();
            if (!isCurrent())
                return;
            if (stored)
                dynamicModels = stored.models.filter((model) => model.provider === provider.id);
            if (!context.allowNetwork)
                return;
            if (!context.force &&
                stored?.checkedAt !== undefined &&
                Date.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS)
                return;
            const url = new URL(`/api/models/providers/${encodeURIComponent(provider.id)}`, catalogBaseUrl);
            const response = await fetch(url, {
                headers: { accept: "application/json", "User-Agent": getPiUserAgent(VERSION) },
                signal: context.signal,
            });
            if (!isCurrent())
                return;
            const checkedAt = Date.now();
            if (response.status === 404 || response.status === 501) {
                await persist({ models: dynamicModels, checkedAt });
                return;
            }
            if (!response.ok) {
                throw new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);
            }
            const body = await response.json();
            if (!isCurrent())
                return;
            const refreshed = parseCatalog(provider.id, body);
            await persist({ models: refreshed, checkedAt });
            if (isCurrent())
                dynamicModels = refreshed;
        })();
        const refresh = settleOnAbort(operation, context.signal, () => !persistenceInProgress);
        const published = refresh.finally(() => {
            if (inflightRefresh?.promise === published)
                inflightRefresh = undefined;
        });
        inflightRefresh = {
            promise: published,
            allowNetwork: context.allowNetwork,
            force: context.force === true,
            signal: context.signal,
        };
        return published;
    };
    return {
        ...provider,
        getModels: () => mergeModels(provider.getModels(), dynamicModels),
        refreshModels,
    };
}
//# sourceMappingURL=remote-catalog-provider.js.map