/**
 * Page-level AEO (Answer Engine Optimisation) heuristics. Eight checks
 * run against raw HTML; each returns a status and, when the check fails,
 * one concrete rewrite suggestion the operator can apply.
 *
 * The HTML parser is a deliberate single-pass regex scanner: AEO audits
 * run on pages the platform itself renders, so we know they are
 * server-side-generated, well-formed enough for regex inspection, and
 * not gated on script-injected content. Bringing in a full DOM parser
 * (jsdom, cheerio) would dwarf the rest of the plugin and pull in
 * Node-version-coupled native deps. If we later need to audit
 * third-party arbitrary HTML, swap the parser then.
 */
const H1_RX = /<h1\b[^>]*>([\s\S]*?)<\/h1>/gi;
const JSONLD_RX = /<script[^>]*type=["']application\/ld\+json["'][^>]*>([\s\S]*?)<\/script>/gi;
const META_DESC_RX = /<meta\b[^>]*name=["']description["'][^>]*content=["']([^"']*)["']/i;
const META_DESC_RX_REV = /<meta\b[^>]*content=["']([^"']*)["'][^>]*name=["']description["']/i;
const CANONICAL_RX = /<link\b[^>]*rel=["']canonical["'][^>]*href=["']([^"']*)["']/i;
const OG_TITLE_RX = /<meta\b[^>]*property=["']og:title["']/i;
const OG_DESC_RX = /<meta\b[^>]*property=["']og:description["']/i;
const HEADING_RX = /<(h[1-6])\b[^>]*>([\s\S]*?)<\/\1>/gi;
const TAG_STRIP = /<[^>]+>/g;
function stripTags(html) {
    return html.replace(TAG_STRIP, "").replace(/\s+/g, " ").trim();
}
function readMetaDescription(html) {
    const m = html.match(META_DESC_RX) ?? html.match(META_DESC_RX_REV);
    return m ? m[1] : null;
}
function checkH1(html) {
    const matches = [...html.matchAll(H1_RX)];
    if (matches.length === 0) {
        return {
            name: "h1-present",
            status: "fail",
            detail: "no <h1> found",
            suggestion: "Add one <h1> at the top of the page naming the entity or question this page answers.",
        };
    }
    if (matches.length > 1) {
        return {
            name: "h1-present",
            status: "warn",
            detail: `${matches.length} <h1> elements (expected 1)`,
        };
    }
    return {
        name: "h1-present",
        status: "pass",
        detail: `1 <h1>: "${stripTags(matches[0][1]).slice(0, 80)}"`,
    };
}
function checkJsonLd(html) {
    const matches = [...html.matchAll(JSONLD_RX)];
    if (matches.length === 0) {
        return {
            name: "jsonld-present",
            status: "fail",
            detail: "no JSON-LD <script> block",
            suggestion: "Emit a schema.org JSON-LD block via aeo-emit-jsonld and inline it in <head>.",
        };
    }
    let valid = 0;
    for (const m of matches) {
        try {
            JSON.parse(m[1]);
            valid += 1;
        }
        catch {
            // counted below
        }
    }
    if (valid === 0) {
        return {
            name: "jsonld-present",
            status: "fail",
            detail: `${matches.length} JSON-LD block(s) but none parse`,
            suggestion: "Fix the JSON-LD payload — JSON.parse failed on every block.",
        };
    }
    if (valid < matches.length) {
        return {
            name: "jsonld-present",
            status: "warn",
            detail: `${valid}/${matches.length} JSON-LD blocks parse`,
        };
    }
    return {
        name: "jsonld-present",
        status: "pass",
        detail: `${valid} JSON-LD block(s)`,
    };
}
function checkStructuredAnswer(html) {
    const h1Matches = [...html.matchAll(H1_RX)];
    if (h1Matches.length === 0) {
        return {
            name: "structured-answer",
            status: "fail",
            detail: "no <h1> — cannot locate structured answer position",
            suggestion: "Add an <h1> followed by a single ≤280-character paragraph that answers the page question directly.",
        };
    }
    const h1 = h1Matches[0];
    const after = html.slice((h1.index ?? 0) + h1[0].length);
    const pMatch = after.match(/<p\b[^>]*>([\s\S]*?)<\/p>/i);
    if (!pMatch) {
        return {
            name: "structured-answer",
            status: "fail",
            detail: "no <p> after <h1>",
            suggestion: "Add one ≤280-character <p> immediately after the <h1> that states the answer in one sentence.",
        };
    }
    const text = stripTags(pMatch[1]);
    if (text.length === 0) {
        return {
            name: "structured-answer",
            status: "fail",
            detail: "first <p> after <h1> is empty",
            suggestion: "Fill the first <p> after <h1> with a ≤280-character direct answer.",
        };
    }
    if (text.length > 280) {
        return {
            name: "structured-answer",
            status: "warn",
            detail: `first <p> after <h1> is ${text.length} chars (target ≤280)`,
        };
    }
    return {
        name: "structured-answer",
        status: "pass",
        detail: `first <p> after <h1> is ${text.length} chars`,
    };
}
function checkFaqSection(html) {
    const lower = html.toLowerCase();
    const hasFaqJsonLd = /["']@type["']\s*:\s*["']faqpage["']/i.test(html);
    const hasFaqText = /\bfrequently asked questions\b/i.test(lower) ||
        /\bfaqs?\b/i.test(lower);
    if (hasFaqJsonLd) {
        return {
            name: "faq-section",
            status: "pass",
            detail: "FAQPage JSON-LD present",
        };
    }
    if (hasFaqText) {
        return {
            name: "faq-section",
            status: "warn",
            detail: "FAQ text but no FAQPage JSON-LD",
        };
    }
    return {
        name: "faq-section",
        status: "fail",
        detail: "no FAQ section",
        suggestion: "Add a FAQ section with FAQPage JSON-LD listing the 3–5 questions customers most often ask.",
    };
}
function checkMetaDescription(html) {
    const desc = readMetaDescription(html);
    if (!desc) {
        return {
            name: "meta-description",
            status: "fail",
            detail: "no <meta name=\"description\">",
            suggestion: "Add <meta name=\"description\"> with an 80–160 character summary of the page's answer.",
        };
    }
    const len = desc.trim().length;
    if (len < 80 || len > 160) {
        return {
            name: "meta-description",
            status: "warn",
            detail: `description is ${len} chars (target 80–160)`,
        };
    }
    return {
        name: "meta-description",
        status: "pass",
        detail: `description is ${len} chars`,
    };
}
function checkCanonical(html) {
    const m = html.match(CANONICAL_RX);
    if (!m) {
        return {
            name: "canonical-url",
            status: "fail",
            detail: "no <link rel=\"canonical\">",
            suggestion: "Add <link rel=\"canonical\" href=\"<page-url>\"> in <head>.",
        };
    }
    return {
        name: "canonical-url",
        status: "pass",
        detail: `canonical=${m[1]}`,
    };
}
function checkOgTags(html) {
    const hasTitle = OG_TITLE_RX.test(html);
    const hasDesc = OG_DESC_RX.test(html);
    if (hasTitle && hasDesc) {
        return {
            name: "og-tags",
            status: "pass",
            detail: "og:title + og:description present",
        };
    }
    const missing = [];
    if (!hasTitle)
        missing.push("og:title");
    if (!hasDesc)
        missing.push("og:description");
    return {
        name: "og-tags",
        status: "fail",
        detail: `missing: ${missing.join(", ")}`,
        suggestion: `Add <meta property="${missing.join('"> and <meta property="')}"> in <head>.`,
    };
}
function checkHeadingHierarchy(html) {
    const levels = [];
    for (const m of html.matchAll(HEADING_RX)) {
        levels.push(parseInt(m[1].slice(1), 10));
    }
    if (levels.length === 0) {
        return {
            name: "heading-hierarchy",
            status: "fail",
            detail: "no headings on the page",
            suggestion: "Add a semantic heading structure starting with <h1> and stepping down by one level.",
        };
    }
    for (let i = 1; i < levels.length; i += 1) {
        const prev = levels[i - 1];
        const cur = levels[i];
        if (cur > prev + 1) {
            return {
                name: "heading-hierarchy",
                status: "fail",
                detail: `heading skip: h${prev} → h${cur}`,
                suggestion: `Replace the h${cur} after the h${prev} with an h${prev + 1} (or insert an intermediate heading).`,
            };
        }
    }
    return {
        name: "heading-hierarchy",
        status: "pass",
        detail: `${levels.length} headings, no skips`,
    };
}
const ALL_CHECKS = [
    checkH1,
    checkJsonLd,
    checkStructuredAnswer,
    checkFaqSection,
    checkMetaDescription,
    checkCanonical,
    checkOgTags,
    checkHeadingHierarchy,
];
export function auditHtml(html) {
    const heuristics = ALL_CHECKS.map((fn) => fn(html));
    const passes = heuristics.filter((r) => r.status === "pass").length;
    const warns = heuristics.filter((r) => r.status === "warn").length;
    const total = heuristics.length;
    const score = Math.round((100 * (passes + 0.5 * warns)) / total);
    return { score, heuristics };
}
//# sourceMappingURL=audit-heuristics.js.map