/**
 * Canonical image alt-text analysis, shared by every surface that reports on
 * alt coverage (SEO analysis, the audit engine, the content-health scanner, the
 * cron scan). Previously each had its own regex and they disagreed on the most
 * important edge case — an intentionally *decorative* image — which produced
 * conflicting "missing alt" counts.
 *
 * Rules (chosen to match the long-standing scanner/cron contract: an explicit
 * empty `alt=""` is an intentional, valid decorative choice — only a *missing*
 * `alt` attribute is an error):
 *   - DECORATIVE: an empty `alt=""` (the author opted the image out of the
 *     accessibility tree), or any image explicitly marked decorative for
 *     assistive tech (`role="presentation"`, `role="none"`, `aria-hidden="true"`).
 *     Decorative images are correct and never flagged as missing.
 *   - MISSING: a non-decorative image with no `alt` attribute at all.
 *   - HAS ALT: a non-empty `alt`.
 */
const IMG_TAG_RE = /<img\b[^>]*>/gi;
function getAttr(tag, name) {
    // The negative lookbehind prevents matching a hyphenated/namespaced attribute
    // that merely *ends* with `name` (e.g. `data-default-alt="…"` must not be read
    // as the `alt` attribute).
    const m = tag.match(new RegExp(`(?<![-\\w:])${name}\\s*=\\s*["']([^"']*)["']`, 'i'));
    return m ? (m[1] ?? '') : null;
}
/**
 * True when an `<img>` tag is decorative: either an explicit empty `alt=""`, or
 * marked decorative for assistive tech (`role="presentation"`/`"none"` or
 * `aria-hidden="true"`). A descriptive (non-empty) alt is never decorative.
 */
export function isDecorativeImgTag(tag) {
    const alt = getAttr(tag, 'alt');
    if (alt !== null && alt.trim().length > 0)
        return false;
    if (alt !== null && alt.trim().length === 0)
        return true;
    // No alt attribute at all: decorative only if explicitly marked so.
    const role = getAttr(tag, 'role')?.toLowerCase();
    const ariaHidden = getAttr(tag, 'aria-hidden')?.toLowerCase();
    return role === 'presentation' || role === 'none' || ariaHidden === 'true';
}
export function analyzeImageAlts(html) {
    const result = { total: 0, withAlt: 0, decorative: 0, missing: 0, altTexts: [] };
    if (!html)
        return result;
    const tags = html.match(IMG_TAG_RE) ?? [];
    result.total = tags.length;
    for (const tag of tags) {
        const alt = getAttr(tag, 'alt');
        if (alt !== null && alt.trim().length > 0) {
            result.withAlt++;
            result.altTexts.push(alt.trim());
        }
        else if (isDecorativeImgTag(tag)) {
            result.decorative++;
        }
        else {
            result.missing++;
        }
    }
    return result;
}
/** Count images missing alt text (decorative images excluded). */
export function countMissingAltImages(html) {
    return analyzeImageAlts(html).missing;
}
//# sourceMappingURL=alt-text.js.map