/**
 * Cross-cutting signals fed into `runSeoAuditAnalysis` — redirect health and
 * static broken-internal-link detection (no outbound HTTP; safe for cron/audit).
 */
import { detectChainsAndLoops, normalizeRedirectPath, resolveChain, buildEdgeMap, } from '../redirects/graph.js';
const HREF_REGEX = /href=["']([^"']+)["']/gi;
function normalizePath(path, siteUrl) {
    const trimmed = path.trim();
    if (!trimmed ||
        trimmed.startsWith('#') ||
        trimmed.startsWith('mailto:') ||
        trimmed.startsWith('tel:'))
        return null;
    if (/^https?:\/\//i.test(trimmed)) {
        const base = siteUrl.replace(/\/+$/, '').toLowerCase();
        if (!base || !trimmed.toLowerCase().startsWith(base))
            return null;
        return normalizeRedirectPath(trimmed.slice(base.length) || '/');
    }
    return normalizeRedirectPath(trimmed);
}
/** Count redirect chains and loops from stored redirect rules. */
export async function gatherRedirectSignals(db) {
    if (!db?.redirect?.findMany)
        return { redirectChains: 0, redirectLoops: 0 };
    const rows = await db.redirect.findMany({}).catch(() => []);
    const rules = rows.map((r) => ({
        id: r.id,
        from: r.source,
        to: r.destination,
        enabled: r.enabled,
    }));
    const chains = detectChainsAndLoops(rules);
    return {
        redirectChains: chains.filter((c) => c.kind === 'chain').length,
        redirectLoops: chains.filter((c) => c.kind === 'loop').length,
    };
}
/**
 * Find internal links in content that don't resolve to a known page URL or
 * redirect terminal. Static analysis only — does not fetch URLs.
 */
export function detectBrokenInternalLinks(entities, options = {}) {
    const siteUrl = (options.siteUrl ?? '').replace(/\/+$/, '');
    const knownPaths = new Set();
    for (const e of entities) {
        if (e.url)
            knownPaths.add(normalizeRedirectPath(e.url));
        if (siteUrl && e.url.startsWith('/')) {
            knownPaths.add(normalizeRedirectPath(`${siteUrl}${e.url}`));
        }
    }
    const edgeMap = buildEdgeMap(options.redirectRules ?? []);
    const broken = [];
    const reported = new Set();
    const pathExists = (path) => {
        if (knownPaths.has(path))
            return true;
        const resolved = resolveChain(edgeMap, path);
        if (resolved.loop)
            return false;
        return knownPaths.has(resolved.terminal);
    };
    for (const e of entities) {
        const content = e.content ?? '';
        if (!content)
            continue;
        let match;
        HREF_REGEX.lastIndex = 0;
        while ((match = HREF_REGEX.exec(content)) !== null) {
            const href = match[1];
            if (!href)
                continue;
            const path = normalizePath(href, siteUrl);
            if (!path || pathExists(path))
                continue;
            const key = `${e.entityId}:${path}`;
            if (reported.has(key))
                continue;
            reported.add(key);
            broken.push({
                url: href,
                fromEntityId: e.entityId,
                fromTitle: e.title,
                fromUrl: e.url,
            });
        }
    }
    return broken;
}
/** Load redirect rules and gather all audit cross-cutting signals. */
export async function gatherAuditSignals(db, entities, siteUrl) {
    const redirectPart = await gatherRedirectSignals(db);
    let redirectRules = [];
    if (db?.redirect?.findMany) {
        const rows = await db.redirect.findMany({}).catch(() => []);
        redirectRules = rows.map((r) => ({
            id: r.id,
            from: r.source,
            to: r.destination,
            enabled: r.enabled,
        }));
    }
    const brokenLinks = detectBrokenInternalLinks(entities, { siteUrl, redirectRules });
    return { ...redirectPart, brokenLinks };
}
//# sourceMappingURL=audit-signals.js.map