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
        : typeof value === "object" && value !== null && "models" in value && Array.isArray(value.models)
            ? value.models
            : typeof value === "object" && value !== null
                ? Object.values(value)
                : undefined;
    if (!entries)
        throw new Error(`Invalid model catalog for provider "${providerId}"`);
    return entries
        .filter((entry) => typeof entry === "object" && entry !== null && "id" in entry)
        .map((model) => ({ ...model, provider: providerId }));
}
function remoteModels(entry, localGeneratedAt) {
    if (!entry)
        return [];
    if (localGeneratedAt !== undefined && (entry.lastModified === undefined || entry.lastModified <= localGeneratedAt)) {
        return [];
    }
    return entry.models;
}
/** Add a persisted pi.dev catalog overlay to a static built-in provider. */
export function withRemoteCatalog(provider, catalogBaseUrl = DEFAULT_CATALOG_BASE_URL, localGeneratedAt) {
    let dynamicModels = [];
    let inflightRefresh;
    return {
        ...provider,
        getModels: () => mergeModels(provider.getModels(), dynamicModels),
        refreshModels: (context) => {
            inflightRefresh ??= (async () => {
                try {
                    const stored = await context.store.read();
                    dynamicModels = remoteModels(stored, localGeneratedAt).filter((model) => model.provider === provider.id);
                    if (!context.allowNetwork || context.signal?.aborted)
                        return;
                    if (!context.force &&
                        stored?.checkedAt !== undefined &&
                        stored.lastModified !== undefined &&
                        Date.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS) {
                        return;
                    }
                    // Only revalidate when a cached body backs the validator, so a 304 can never
                    // leave the overlay empty.
                    const validator = stored?.models.length ? stored.etag : undefined;
                    const url = new URL(`/api/models/providers/${encodeURIComponent(provider.id)}`, catalogBaseUrl);
                    const response = await fetch(url, {
                        headers: {
                            accept: "application/json",
                            "User-Agent": getPiUserAgent(VERSION),
                            ...(validator ? { "if-none-match": validator } : {}),
                        },
                        signal: context.signal,
                    });
                    if (context.signal?.aborted)
                        return;
                    const checkedAt = Date.now();
                    // Unchanged: dynamicModels already holds the stored overlay, so only the
                    // freshness window moves.
                    if (response.status === 304 && stored) {
                        await context.store.write({ ...stored, checkedAt });
                        return;
                    }
                    if (response.status === 404 || response.status === 501) {
                        await context.store.write({
                            ...(stored ?? { models: [] }),
                            checkedAt,
                            lastModified: 0,
                            etag: undefined,
                        });
                        return;
                    }
                    if (!response.ok) {
                        // Transient failure: the cached body and its validator stay valid, so keep the
                        // etag and let the next refresh revalidate instead of downloading the catalog.
                        await context.store.write({ ...(stored ?? { models: [] }), checkedAt });
                        throw new Error(`Model catalog request failed for ${provider.id}: ${response.status}`);
                    }
                    const refreshed = parseCatalog(provider.id, await response.json());
                    const lastModified = Date.parse(response.headers.get("last-modified") ?? "");
                    if (context.signal?.aborted)
                        return;
                    const entry = {
                        models: refreshed,
                        checkedAt,
                        lastModified: Number.isNaN(lastModified) ? 0 : lastModified,
                        etag: response.headers.get("etag") ?? undefined,
                    };
                    dynamicModels = remoteModels(entry, localGeneratedAt);
                    await context.store.write(entry);
                }
                finally {
                    inflightRefresh = undefined;
                }
            })();
            return inflightRefresh;
        },
    };
}
//# sourceMappingURL=remote-catalog-provider.js.map