import { hasChildren } from './types.js';
// ─── Helpers ───────────────────────────────────────────────────────────────
export function stripHtml(html) {
    return html.replace(/<[^>]*>/g, '').trim();
}
export function countWords(text) {
    const trimmed = text.trim();
    if (trimmed.length === 0)
        return 0;
    return trimmed.split(/\s+/).length;
}
export function countSentences(text) {
    const matches = text.match(/[.!?]+/g);
    return matches ? matches.length : 1;
}
export function countSyllables(word) {
    const w = word.toLowerCase().replace(/[^a-z]/g, '');
    if (w.length <= 2)
        return 1;
    let count = 0;
    let prevVowel = false;
    for (let i = 0; i < w.length; i++) {
        const char = w[i];
        const isVowel = /[aeiouy]/.test(char);
        if (isVowel && !prevVowel) {
            count++;
        }
        prevVowel = isVowel;
    }
    if (w.endsWith('e') && count > 1) {
        count--;
    }
    return Math.max(count, 1);
}
function isInternalLink(url) {
    return url.startsWith('/') || url.startsWith('#');
}
function extractStringValues(data) {
    const parts = [];
    for (const value of Object.values(data)) {
        if (typeof value === 'string') {
            parts.push(stripHtml(value));
        }
        else if (Array.isArray(value)) {
            for (const item of value) {
                if (typeof item === 'string') {
                    parts.push(stripHtml(item));
                }
                else if (item && typeof item === 'object') {
                    parts.push(extractStringValues(item));
                }
            }
        }
        else if (value && typeof value === 'object') {
            parts.push(extractStringValues(value));
        }
    }
    return parts.filter(Boolean).join(' ');
}
// ─── Content Extractor ─────────────────────────────────────────────────────
function extractBlockContent(block, content) {
    const { blockType } = block.settings;
    const data = block.data;
    const texts = [];
    switch (blockType) {
        case 'hero': {
            const title = typeof data.title === 'string' ? data.title : '';
            const subtitle = typeof data.subtitle === 'string' ? data.subtitle : '';
            const ctaText = typeof data.ctaText === 'string' ? data.ctaText : '';
            if (title)
                texts.push(title);
            if (subtitle)
                texts.push(subtitle);
            if (ctaText)
                texts.push(ctaText);
            if (title) {
                content.headings.push({ level: 1, text: title, blockId: block.id });
            }
            if (typeof data.image === 'string' && data.image) {
                content.images.push({
                    src: data.image,
                    alt: typeof data.alt === 'string' ? data.alt : '',
                    blockId: block.id,
                });
            }
            if (typeof data.ctaLink === 'string' && data.ctaLink) {
                content.links.push({
                    url: data.ctaLink,
                    text: ctaText || 'CTA',
                    internal: isInternalLink(data.ctaLink),
                    blockId: block.id,
                });
            }
            break;
        }
        case 'text': {
            const heading = typeof data.heading === 'string' ? data.heading : '';
            const body = typeof data.body === 'string' ? stripHtml(data.body) : '';
            if (heading)
                texts.push(heading);
            if (body)
                texts.push(body);
            if (heading) {
                const level = typeof data.headingLevel === 'number' ? data.headingLevel : 2;
                content.headings.push({ level, text: heading, blockId: block.id });
            }
            break;
        }
        case 'image': {
            const alt = typeof data.alt === 'string' ? data.alt : '';
            const caption = typeof data.caption === 'string' ? data.caption : '';
            if (alt)
                texts.push(alt);
            if (caption)
                texts.push(caption);
            content.images.push({
                src: typeof data.src === 'string' ? data.src : '',
                alt,
                blockId: block.id,
            });
            if (typeof data.link === 'string' && data.link) {
                content.links.push({
                    url: data.link,
                    text: alt || caption || 'Image link',
                    internal: isInternalLink(data.link),
                    blockId: block.id,
                });
            }
            break;
        }
        case 'cards': {
            const items = Array.isArray(data.items) ? data.items : [];
            for (const item of items) {
                if (item && typeof item === 'object') {
                    const cardItem = item;
                    const title = typeof cardItem.title === 'string' ? cardItem.title : '';
                    const desc = typeof cardItem.description === 'string' ? cardItem.description : '';
                    if (title)
                        texts.push(title);
                    if (desc)
                        texts.push(stripHtml(desc));
                    if (typeof cardItem.link === 'string' && cardItem.link) {
                        content.links.push({
                            url: cardItem.link,
                            text: title || 'Card link',
                            internal: isInternalLink(cardItem.link),
                            blockId: block.id,
                        });
                    }
                }
            }
            break;
        }
        case 'cta': {
            const heading = typeof data.heading === 'string' ? data.heading : '';
            const body = typeof data.body === 'string' ? stripHtml(data.body) : '';
            const buttonText = typeof data.buttonText === 'string' ? data.buttonText : '';
            if (heading)
                texts.push(heading);
            if (body)
                texts.push(body);
            if (buttonText)
                texts.push(buttonText);
            if (typeof data.buttonLink === 'string' && data.buttonLink) {
                content.links.push({
                    url: data.buttonLink,
                    text: buttonText || 'Button',
                    internal: isInternalLink(data.buttonLink),
                    blockId: block.id,
                });
            }
            break;
        }
        case 'video': {
            for (const value of Object.values(data)) {
                if (typeof value === 'string' && value) {
                    texts.push(value);
                }
            }
            break;
        }
        case 'gallery': {
            const images = Array.isArray(data.images) ? data.images : [];
            for (const img of images) {
                if (img && typeof img === 'object') {
                    const imgItem = img;
                    const alt = typeof imgItem.alt === 'string' ? imgItem.alt : '';
                    const caption = typeof imgItem.caption === 'string' ? imgItem.caption : '';
                    if (alt)
                        texts.push(alt);
                    if (caption)
                        texts.push(caption);
                    content.images.push({
                        src: typeof imgItem.src === 'string' ? imgItem.src : '',
                        alt,
                        blockId: block.id,
                    });
                }
            }
            break;
        }
        case 'faq': {
            const items = Array.isArray(data.items) ? data.items : [];
            for (const item of items) {
                if (item && typeof item === 'object') {
                    const faqItem = item;
                    const question = typeof faqItem.question === 'string' ? faqItem.question : '';
                    const answer = typeof faqItem.answer === 'string' ? stripHtml(faqItem.answer) : '';
                    if (question)
                        texts.push(question);
                    if (answer)
                        texts.push(answer);
                }
            }
            break;
        }
        case 'form': {
            const msg = typeof data.successMessage === 'string' ? data.successMessage : '';
            if (msg)
                texts.push(msg);
            break;
        }
        case 'code':
            break;
        default: {
            texts.push(extractStringValues(data));
            break;
        }
    }
    const blockText = texts.filter(Boolean).join(' ');
    if (blockText) {
        content.blockTexts.set(block.id, blockText);
    }
}
function traverseTree(node, content) {
    if (node.type === 'block') {
        extractBlockContent(node, content);
        return;
    }
    if (hasChildren(node)) {
        for (const child of node.children) {
            traverseTree(child, content);
        }
    }
}
export function extractContent(tree) {
    const content = {
        plainText: '',
        wordCount: 0,
        headings: [],
        images: [],
        links: [],
        blockTexts: new Map(),
    };
    traverseTree(tree, content);
    const allTexts = [];
    for (const text of content.blockTexts.values()) {
        allTexts.push(text);
    }
    content.plainText = allTexts.join(' ');
    content.wordCount = countWords(content.plainText);
    return content;
}
// ─── Readability Analyzer ──────────────────────────────────────────────────
export function analyzeReadability(text) {
    const words = text.trim().split(/\s+/).filter(Boolean);
    const wordCount = words.length;
    if (wordCount === 0) {
        return {
            fleschScore: 0,
            avgSentenceLength: 0,
            wordCount: 0,
            readingTime: 0,
            passiveEstimate: 0,
        };
    }
    const sentenceCount = countSentences(text);
    const avgSentenceLength = wordCount / sentenceCount;
    let totalSyllables = 0;
    for (const word of words) {
        totalSyllables += countSyllables(word);
    }
    const fleschScore = Math.max(0, Math.min(100, 206.835 - 1.015 * avgSentenceLength - 84.6 * (totalSyllables / wordCount)));
    const readingTime = Math.ceil(wordCount / 200);
    const passivePattern = /\b(is|are|was|were|been|being|be)\s+\w+ed\b/gi;
    const passiveMatches = text.match(passivePattern);
    const passiveCount = passiveMatches ? passiveMatches.length : 0;
    const passiveEstimate = sentenceCount > 0 ? Math.round((passiveCount / sentenceCount) * 100) : 0;
    return {
        fleschScore: Math.round(fleschScore * 10) / 10,
        avgSentenceLength: Math.round(avgSentenceLength * 10) / 10,
        wordCount,
        readingTime,
        passiveEstimate: Math.min(passiveEstimate, 100),
    };
}
// ─── SEO Checker ───────────────────────────────────────────────────────────
function keyphraseInText(text, keyphrase) {
    return text.toLowerCase().includes(keyphrase.toLowerCase());
}
function keyphraseCount(text, keyphrase) {
    const lower = text.toLowerCase();
    const kp = keyphrase.toLowerCase();
    let count = 0;
    let idx = 0;
    while ((idx = lower.indexOf(kp, idx)) !== -1) {
        count++;
        idx += kp.length;
    }
    return count;
}
function getFirstParagraph(content) {
    for (const text of content.blockTexts.values()) {
        if (text.trim().length > 0)
            return text;
    }
    return '';
}
export function analyzeSEO(tree, pageSettings) {
    const content = extractContent(tree);
    const readability = analyzeReadability(content.plainText);
    const checks = [];
    const perBlockHints = new Map();
    const kp = pageSettings.focusKeyphrase || '';
    const metaTitle = pageSettings.metaTitle || pageSettings.title || '';
    const metaDesc = pageSettings.metaDescription || '';
    // ── Page-level checks ───────────────────────────────────────────────────
    if (metaTitle.length === 0) {
        checks.push({
            id: 'meta-title-exists',
            label: 'Meta title',
            status: 'error',
            detail: 'Meta title is missing',
        });
    }
    else if (metaTitle.length < 30 || metaTitle.length > 60) {
        checks.push({
            id: 'meta-title-length',
            label: 'Meta title length',
            status: 'warning',
            detail: `Meta title is ${metaTitle.length} chars (recommended: 30-60)`,
        });
    }
    else {
        checks.push({
            id: 'meta-title-length',
            label: 'Meta title length',
            status: 'good',
            detail: `Meta title is ${metaTitle.length} chars`,
        });
    }
    if (metaDesc.length === 0) {
        checks.push({
            id: 'meta-desc-exists',
            label: 'Meta description',
            status: 'error',
            detail: 'Meta description is missing',
        });
    }
    else if (metaDesc.length < 120 || metaDesc.length > 160) {
        checks.push({
            id: 'meta-desc-length',
            label: 'Meta description length',
            status: 'warning',
            detail: `Meta description is ${metaDesc.length} chars (recommended: 120-160)`,
        });
    }
    else {
        checks.push({
            id: 'meta-desc-length',
            label: 'Meta description length',
            status: 'good',
            detail: `Meta description is ${metaDesc.length} chars`,
        });
    }
    if (!kp) {
        checks.push({
            id: 'keyphrase-set',
            label: 'Focus keyphrase',
            status: 'warning',
            detail: 'No focus keyphrase set',
        });
    }
    else {
        checks.push({
            id: 'keyphrase-set',
            label: 'Focus keyphrase',
            status: 'good',
            detail: `Focus keyphrase: "${kp}"`,
        });
        if (keyphraseInText(metaTitle, kp)) {
            checks.push({
                id: 'keyphrase-in-title',
                label: 'Keyphrase in title',
                status: 'good',
                detail: 'Focus keyphrase found in meta title',
            });
        }
        else {
            checks.push({
                id: 'keyphrase-in-title',
                label: 'Keyphrase in title',
                status: 'warning',
                detail: 'Focus keyphrase not found in meta title',
            });
        }
        if (keyphraseInText(metaDesc, kp)) {
            checks.push({
                id: 'keyphrase-in-desc',
                label: 'Keyphrase in description',
                status: 'good',
                detail: 'Focus keyphrase found in meta description',
            });
        }
        else {
            checks.push({
                id: 'keyphrase-in-desc',
                label: 'Keyphrase in description',
                status: 'warning',
                detail: 'Focus keyphrase not found in meta description',
            });
        }
        if (keyphraseInText(pageSettings.slug, kp)) {
            checks.push({
                id: 'keyphrase-in-slug',
                label: 'Keyphrase in slug',
                status: 'good',
                detail: 'Focus keyphrase found in URL slug',
            });
        }
        else {
            checks.push({
                id: 'keyphrase-in-slug',
                label: 'Keyphrase in slug',
                status: 'warning',
                detail: 'Focus keyphrase not found in URL slug',
            });
        }
        const firstPara = getFirstParagraph(content);
        if (keyphraseInText(firstPara, kp)) {
            checks.push({
                id: 'keyphrase-in-intro',
                label: 'Keyphrase in introduction',
                status: 'good',
                detail: 'Focus keyphrase found in first paragraph',
            });
        }
        else {
            checks.push({
                id: 'keyphrase-in-intro',
                label: 'Keyphrase in introduction',
                status: 'warning',
                detail: 'Focus keyphrase not found in first paragraph',
            });
        }
        const kpCount = keyphraseCount(content.plainText, kp);
        const density = content.wordCount > 0 ? ((kpCount * countWords(kp)) / content.wordCount) * 100 : 0;
        if (density >= 0.5 && density <= 3) {
            checks.push({
                id: 'keyphrase-density',
                label: 'Keyphrase density',
                status: 'good',
                detail: `Keyphrase density is ${density.toFixed(1)}%`,
            });
        }
        else if (density === 0) {
            checks.push({
                id: 'keyphrase-density',
                label: 'Keyphrase density',
                status: 'error',
                detail: 'Keyphrase not found in content',
            });
        }
        else {
            checks.push({
                id: 'keyphrase-density',
                label: 'Keyphrase density',
                status: 'warning',
                detail: `Keyphrase density is ${density.toFixed(1)}% (recommended: 0.5-3%)`,
            });
        }
    }
    if (content.wordCount >= 300) {
        checks.push({
            id: 'content-length',
            label: 'Content length',
            status: 'good',
            detail: `${content.wordCount} words`,
        });
    }
    else {
        checks.push({
            id: 'content-length',
            label: 'Content length',
            status: 'warning',
            detail: `Only ${content.wordCount} words (recommended: 300+)`,
        });
    }
    if (pageSettings.ogImage) {
        checks.push({
            id: 'og-image',
            label: 'Open Graph image',
            status: 'good',
            detail: 'OG image is set',
        });
    }
    else {
        checks.push({
            id: 'og-image',
            label: 'Open Graph image',
            status: 'warning',
            detail: 'No OG image set',
        });
    }
    if (pageSettings.schemaType) {
        checks.push({
            id: 'schema-type',
            label: 'Schema type',
            status: 'good',
            detail: `Schema type: ${pageSettings.schemaType}`,
        });
    }
    else {
        checks.push({
            id: 'schema-type',
            label: 'Schema type',
            status: 'warning',
            detail: 'No schema type set',
        });
    }
    // ── Heading structure ───────────────────────────────────────────────────
    const h1Count = content.headings.filter((h) => h.level === 1).length;
    if (h1Count === 0) {
        checks.push({
            id: 'h1-exists',
            label: 'H1 heading',
            status: 'error',
            detail: 'No H1 heading found',
        });
    }
    else if (h1Count === 1) {
        checks.push({
            id: 'h1-exists',
            label: 'H1 heading',
            status: 'good',
            detail: 'Single H1 heading found',
        });
    }
    else {
        checks.push({
            id: 'h1-exists',
            label: 'H1 heading',
            status: 'warning',
            detail: `Multiple H1 headings found (${h1Count})`,
        });
    }
    let hierarchyGood = true;
    const sortedHeadings = [...content.headings];
    for (let i = 1; i < sortedHeadings.length; i++) {
        const prev = sortedHeadings[i - 1];
        const curr = sortedHeadings[i];
        if (prev && curr && curr.level > prev.level + 1) {
            hierarchyGood = false;
            break;
        }
    }
    if (hierarchyGood) {
        checks.push({
            id: 'heading-hierarchy',
            label: 'Heading hierarchy',
            status: 'good',
            detail: 'Heading levels follow a logical order',
        });
    }
    else {
        checks.push({
            id: 'heading-hierarchy',
            label: 'Heading hierarchy',
            status: 'warning',
            detail: 'Heading hierarchy skips levels',
        });
    }
    if (kp && content.headings.some((h) => keyphraseInText(h.text, kp))) {
        checks.push({
            id: 'keyphrase-in-heading',
            label: 'Keyphrase in heading',
            status: 'good',
            detail: 'Focus keyphrase found in a heading',
        });
    }
    else if (kp) {
        checks.push({
            id: 'keyphrase-in-heading',
            label: 'Keyphrase in heading',
            status: 'warning',
            detail: 'Focus keyphrase not found in any heading',
        });
    }
    // ── Image checks ────────────────────────────────────────────────────────
    const imagesWithoutAlt = content.images.filter((img) => !img.alt);
    if (imagesWithoutAlt.length === 0 && content.images.length > 0) {
        checks.push({
            id: 'images-alt',
            label: 'Image alt text',
            status: 'good',
            detail: 'All images have alt text',
        });
    }
    else if (imagesWithoutAlt.length > 0) {
        checks.push({
            id: 'images-alt',
            label: 'Image alt text',
            status: 'error',
            detail: `${imagesWithoutAlt.length} image(s) missing alt text`,
        });
    }
    if (kp && content.images.some((img) => keyphraseInText(img.alt, kp))) {
        checks.push({
            id: 'keyphrase-in-alt',
            label: 'Keyphrase in image alt',
            status: 'good',
            detail: 'Focus keyphrase found in at least one image alt text',
        });
    }
    else if (kp && content.images.length > 0) {
        checks.push({
            id: 'keyphrase-in-alt',
            label: 'Keyphrase in image alt',
            status: 'warning',
            detail: 'Focus keyphrase not found in any image alt text',
        });
    }
    // ── Link checks ─────────────────────────────────────────────────────────
    const internalLinks = content.links.filter((l) => l.internal);
    const externalLinks = content.links.filter((l) => !l.internal);
    if (internalLinks.length > 0) {
        checks.push({
            id: 'internal-links',
            label: 'Internal links',
            status: 'good',
            detail: `${internalLinks.length} internal link(s)`,
        });
    }
    else {
        checks.push({
            id: 'internal-links',
            label: 'Internal links',
            status: 'warning',
            detail: 'No internal links found',
        });
    }
    if (externalLinks.length > 0) {
        checks.push({
            id: 'external-links',
            label: 'External links',
            status: 'good',
            detail: `${externalLinks.length} external link(s)`,
        });
    }
    else {
        checks.push({
            id: 'external-links',
            label: 'External links',
            status: 'warning',
            detail: 'No external links found',
        });
    }
    // ── Per-block hints ─────────────────────────────────────────────────────
    for (const img of content.images) {
        if (!img.alt) {
            addBlockHint(perBlockHints, img.blockId, {
                id: 'block-missing-alt',
                label: 'Missing alt text',
                status: 'error',
                detail: 'Missing alt text',
                blockId: img.blockId,
            });
        }
        else if (kp && !keyphraseInText(img.alt, kp)) {
            addBlockHint(perBlockHints, img.blockId, {
                id: 'block-alt-no-keyphrase',
                label: 'Alt text keyphrase',
                status: 'warning',
                detail: "Alt text doesn't contain focus keyphrase",
                blockId: img.blockId,
            });
        }
    }
    for (const [blockId, blockText] of content.blockTexts) {
        if (kp) {
            const heading = content.headings.find((h) => h.blockId === blockId && h.level === 1);
            if (heading && !keyphraseInText(heading.text, kp)) {
                addBlockHint(perBlockHints, blockId, {
                    id: 'block-hero-no-keyphrase',
                    label: 'Title keyphrase',
                    status: 'warning',
                    detail: "Title doesn't contain focus keyphrase",
                    blockId,
                });
            }
        }
        if (countWords(blockText) > 300) {
            addBlockHint(perBlockHints, blockId, {
                id: 'block-paragraph-long',
                label: 'Paragraph length',
                status: 'warning',
                detail: 'Paragraph too long',
                blockId,
            });
        }
    }
    // Check for text blocks without headings
    traverseForTextBlockHints(tree, content, perBlockHints);
    // Check for video blocks without poster
    traverseForVideoHints(tree, perBlockHints);
    // ── Score calculation ───────────────────────────────────────────────────
    let totalScore = 0;
    for (const check of checks) {
        if (check.status === 'good')
            totalScore += 100;
        else if (check.status === 'warning')
            totalScore += 50;
    }
    const score = checks.length > 0 ? Math.round(totalScore / checks.length) : 0;
    return { score, checks, readability, content, perBlockHints };
}
// ─── Internal Utilities ────────────────────────────────────────────────────
function addBlockHint(map, blockId, hint) {
    const existing = map.get(blockId);
    if (existing) {
        existing.push(hint);
    }
    else {
        map.set(blockId, [hint]);
    }
}
function traverseForTextBlockHints(node, content, perBlockHints) {
    if (node.type === 'block') {
        if (node.settings.blockType === 'text') {
            const hasHeading = content.headings.some((h) => h.blockId === node.id);
            if (!hasHeading) {
                addBlockHint(perBlockHints, node.id, {
                    id: 'block-no-heading',
                    label: 'Section heading',
                    status: 'warning',
                    detail: 'No heading in this section',
                    blockId: node.id,
                });
            }
        }
        return;
    }
    if (hasChildren(node)) {
        for (const child of node.children) {
            traverseForTextBlockHints(child, content, perBlockHints);
        }
    }
}
function traverseForVideoHints(node, perBlockHints) {
    if (node.type === 'block') {
        if (node.settings.blockType === 'video' && !node.data.poster) {
            addBlockHint(perBlockHints, node.id, {
                id: 'block-video-no-poster',
                label: 'Video poster',
                status: 'warning',
                detail: 'No poster image set',
                blockId: node.id,
            });
        }
        return;
    }
    if (hasChildren(node)) {
        for (const child of node.children) {
            traverseForVideoHints(child, perBlockHints);
        }
    }
}
//# sourceMappingURL=seo-analyzer.js.map