// Planu — SPEC-386: Docs Registry Auto-Discovery
// Discovers docs URLs from npm/pypi/crates.io package metadata
const RATE_LIMIT_DELAY_MS = 1000;
const VERIFY_TIMEOUT_MS = 3000;
const HEALTH_TIMEOUT_MS = 5000;
const SLOW_THRESHOLD_MS = 2000;
/** Sleep for a given number of milliseconds. */
function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}
/** Perform a HEAD request with a timeout, returning the HTTP status or null on failure. */
async function headWithTimeout(url, timeoutMs) {
    const controller = new AbortController();
    const timer = setTimeout(() => {
        controller.abort();
    }, timeoutMs);
    try {
        const res = await fetch(url, { method: 'HEAD', signal: controller.signal, redirect: 'follow' });
        return res.status;
    }
    catch {
        return null;
    }
    finally {
        clearTimeout(timer);
    }
}
/** Try to fetch JSON from a URL with a timeout. Returns null on any failure. */
async function fetchJson(url, timeoutMs = HEALTH_TIMEOUT_MS) {
    const controller = new AbortController();
    const timer = setTimeout(() => {
        controller.abort();
    }, timeoutMs);
    try {
        const res = await fetch(url, { signal: controller.signal });
        if (!res.ok) {
            return null;
        }
        return (await res.json());
    }
    catch {
        return null;
    }
    finally {
        clearTimeout(timer);
    }
}
/** Extract a clean HTTPS docs URL from npm package metadata. */
function extractNpmDocsUrl(pkg) {
    if (pkg.homepage?.startsWith('http')) {
        return pkg.homepage;
    }
    if (pkg.docs?.startsWith('http')) {
        return pkg.docs;
    }
    const repo = typeof pkg.repository === 'string' ? pkg.repository : pkg.repository?.url;
    if (repo) {
        const clean = repo
            .replace(/^git\+/, '')
            .replace(/\.git$/, '')
            .replace(/^git:\/\//, 'https://');
        if (clean.startsWith('https://')) {
            return clean;
        }
    }
    return null;
}
/** Try npm registry to find docs URL for a package. */
async function tryNpm(tech) {
    const pkg = await fetchJson(`https://registry.npmjs.org/${encodeURIComponent(tech)}`);
    if (!pkg) {
        return null;
    }
    return extractNpmDocsUrl(pkg);
}
/** Try PyPI registry to find docs URL for a package. */
async function tryPypi(tech) {
    const data = await fetchJson(`https://pypi.org/pypi/${encodeURIComponent(tech)}/json`);
    if (!data?.info) {
        return null;
    }
    const urls = data.info.project_urls ?? {};
    const docsUrl = urls.Documentation ?? urls.documentation ?? urls.Docs ?? urls.Homepage ?? data.info.home_page;
    if (docsUrl?.startsWith('http')) {
        return docsUrl;
    }
    return null;
}
/** Try crates.io registry to find docs URL for a crate. */
async function tryCrates(tech) {
    const data = await fetchJson(`https://crates.io/api/v1/crates/${encodeURIComponent(tech)}`);
    if (!data?.crate) {
        return null;
    }
    const url = data.crate.documentation ?? data.crate.homepage;
    if (url?.startsWith('http')) {
        return url;
    }
    return null;
}
/**
 * Verify that a URL is alive with a HEAD request (timeout 3s).
 * Returns true if status is 200, 301, or 302.
 */
export async function validateRegistryUrl(url) {
    const status = await headWithTimeout(url, VERIFY_TIMEOUT_MS);
    return status !== null && (status === 200 || status === 301 || status === 302);
}
/**
 * Discover docs URL for a tech package by querying npm, PyPI, and crates.io.
 * Respects a 1 req/s rate limit per registry via simple delay.
 * Returns null if not found anywhere.
 */
export async function discoverDocsUrl(tech) {
    const tryWithDelay = async (fn) => {
        const result = await fn();
        await sleep(RATE_LIMIT_DELAY_MS);
        return result;
    };
    const registries = [
        { source: 'npm', fn: () => tryNpm(tech) },
        { source: 'pypi', fn: () => tryPypi(tech) },
        { source: 'crates', fn: () => tryCrates(tech) },
    ];
    for (const { source, fn } of registries) {
        const url = await tryWithDelay(fn);
        if (url) {
            const verified = await validateRegistryUrl(url);
            return {
                tech,
                docsUrl: url,
                source,
                discoveredAt: new Date().toISOString(),
                verified,
            };
        }
    }
    return null;
}
/**
 * Batch validate all entries in the docs registry.
 * Categorizes each entry as healthy (<=2s), slow (2-5s), or broken (timeout/error).
 */
export async function checkRegistryHealth(entries) {
    const healthy = [];
    const broken = [];
    const slow = [];
    const checks = Object.entries(entries).map(async ([key, entry]) => {
        const start = Date.now();
        const controller = new AbortController();
        const timer = setTimeout(() => {
            controller.abort();
        }, HEALTH_TIMEOUT_MS);
        try {
            const res = await fetch(entry.base, {
                method: 'HEAD',
                signal: controller.signal,
                redirect: 'follow',
            });
            const elapsed = Date.now() - start;
            const ok = res.status === 200 || res.status === 301 || res.status === 302;
            if (!ok) {
                broken.push(key);
            }
            else if (elapsed > SLOW_THRESHOLD_MS) {
                slow.push(key);
            }
            else {
                healthy.push(key);
            }
        }
        catch {
            broken.push(key);
        }
        finally {
            clearTimeout(timer);
        }
    });
    await Promise.all(checks);
    return { healthy, broken, slow };
}
//# sourceMappingURL=registry-auto-discovery.js.map