import { stripHtmlTags } from './analysis.js';
import { applySiteTitleTemplate } from './title-templates.js';
export const META_TITLE_MAX = 60;
export const META_TITLE_MIN = 30;
export const META_DESC_MAX = 160;
export const META_DESC_MIN = 120;
/** Issue types that support a deterministic one-click fix in Phase 1. */
export const DETERMINISTIC_FIX_TYPES = new Set([
    'meta-title-length',
    'meta-description-length',
    'accidental-noindex',
    'missing-meta-title',
    'missing-meta-description',
    'missing-canonical',
]);
/** Truncate at a word boundary when possible so snippets read naturally. */
export function truncateAtWordBoundary(text, maxLen) {
    const trimmed = text.trim();
    if (trimmed.length <= maxLen)
        return trimmed;
    const slice = trimmed.slice(0, maxLen);
    const lastSpace = slice.lastIndexOf(' ');
    if (lastSpace > Math.floor(maxLen * 0.6)) {
        return slice.slice(0, lastSpace).trim();
    }
    return slice.trim();
}
/** Build a meta description excerpt from page body (plain text). */
export function excerptForMetaDescription(content, maxLen = META_DESC_MAX) {
    const plain = stripHtmlTags(content).replace(/\s+/g, ' ').trim();
    if (!plain)
        return '';
    return truncateAtWordBoundary(plain, maxLen);
}
function buildMetaTitleFromPage(title, siteSeo) {
    const rendered = applySiteTitleTemplate(siteSeo?.titleTemplate, {
        page: title,
        site: siteSeo?.siteName ?? '',
    });
    const candidate = rendered.trim() || title.trim();
    if (candidate.length > META_TITLE_MAX) {
        return truncateAtWordBoundary(candidate, META_TITLE_MAX);
    }
    return candidate;
}
function absoluteCanonicalUrl(entity, siteSeo) {
    const siteUrl = siteSeo?.siteUrl?.replace(/\/+$/, '') ?? '';
    if (!siteUrl)
        return null;
    const path = entity.url?.trim() ?? '';
    if (!path)
        return null;
    if (path.startsWith('http://') || path.startsWith('https://'))
        return path;
    return `${siteUrl}${path.startsWith('/') ? path : `/${path}`}`;
}
export function buildFixFingerprint(entityId, issueType, updatedAt) {
    const ms = updatedAt instanceof Date ? updatedAt.getTime() : new Date(updatedAt).getTime();
    return `${entityId}:${ms}:${issueType}`;
}
function fieldChange(field, label, before, after) {
    return { field, label, before, after };
}
function notFixable(issue, ctx, reason) {
    const entityId = issue.entityId ?? ctx.entity.entityId;
    return {
        autoFixable: false,
        fingerprint: buildFixFingerprint(entityId, issue.type, ctx.updatedAt),
        patch: {},
        changes: [],
        justification: reason,
    };
}
/**
 * Compute a deterministic fix suggestion for an audit issue. Returns
 * `autoFixable: false` when the issue type is unsupported or required context
 * is missing (e.g. no site URL for canonical).
 */
export function suggestDeterministicFix(issue, ctx) {
    const { entity, siteSeo } = ctx;
    const entityId = issue.entityId ?? entity.entityId;
    const fingerprint = buildFixFingerprint(entityId, issue.type, ctx.updatedAt);
    if (!DETERMINISTIC_FIX_TYPES.has(issue.type)) {
        return notFixable(issue, ctx, issue.recommendation ?? 'This issue requires manual review.');
    }
    switch (issue.type) {
        case 'accidental-noindex': {
            if (!entity.noindex) {
                return notFixable(issue, ctx, 'This page is already set to index.');
            }
            return {
                autoFixable: true,
                source: 'deterministic',
                fingerprint,
                patch: { noindex: false },
                changes: [fieldChange('noindex', 'Index in search engines', true, false)],
                justification: 'Removes the noindex flag so search engines can index this public page again.',
            };
        }
        case 'missing-meta-title': {
            const proposed = buildMetaTitleFromPage(entity.title, siteSeo);
            if (!proposed) {
                return notFixable(issue, ctx, 'Add a page title first, then retry the fix.');
            }
            return {
                autoFixable: true,
                source: 'deterministic',
                fingerprint,
                patch: { metaTitle: proposed },
                changes: [fieldChange('metaTitle', 'Meta title', entity.metaTitle ?? null, proposed)],
                justification: `Sets the meta title from the page title using your site title template (${proposed.length} characters).`,
            };
        }
        case 'meta-title-length': {
            const current = entity.metaTitle?.trim() ?? '';
            if (!current) {
                return notFixable(issue, ctx, 'No meta title is set — use the missing-meta-title fix instead.');
            }
            let proposed = current;
            if (current.length > META_TITLE_MAX) {
                proposed = truncateAtWordBoundary(current, META_TITLE_MAX);
            }
            else if (current.length < META_TITLE_MIN) {
                proposed = buildMetaTitleFromPage(entity.title, siteSeo);
                if (proposed.length > META_TITLE_MAX) {
                    proposed = truncateAtWordBoundary(proposed, META_TITLE_MAX);
                }
            }
            if (proposed === current) {
                return notFixable(issue, ctx, 'Could not derive a better meta title automatically.');
            }
            return {
                autoFixable: true,
                source: 'deterministic',
                fingerprint,
                patch: { metaTitle: proposed },
                changes: [fieldChange('metaTitle', 'Meta title', current, proposed)],
                justification: current.length > META_TITLE_MAX
                    ? `Shortened the meta title to ${proposed.length} characters so it fits search result snippets.`
                    : `Expanded the meta title to ${proposed.length} characters using your site title template.`,
            };
        }
        case 'missing-meta-description': {
            const proposed = excerptForMetaDescription(entity.content ?? '');
            if (!proposed) {
                return notFixable(issue, ctx, 'Add page content first so a description can be generated from the body.');
            }
            return {
                autoFixable: true,
                source: 'deterministic',
                fingerprint,
                patch: { metaDescription: proposed },
                changes: [
                    fieldChange('metaDescription', 'Meta description', entity.metaDescription ?? null, proposed),
                ],
                justification: `Drafts a ${proposed.length}-character meta description from the page content.`,
            };
        }
        case 'meta-description-length': {
            const current = entity.metaDescription?.trim() ?? '';
            if (!current) {
                return notFixable(issue, ctx, 'No meta description is set — use the missing-meta-description fix instead.');
            }
            let proposed = current;
            if (current.length > META_DESC_MAX) {
                proposed = truncateAtWordBoundary(current, META_DESC_MAX);
            }
            else if (current.length < META_DESC_MIN) {
                const fromBody = excerptForMetaDescription(entity.content ?? '', META_DESC_MAX);
                proposed = fromBody.length >= current.length ? fromBody : current;
                if (proposed.length < META_DESC_MIN && fromBody) {
                    proposed = fromBody;
                }
            }
            if (proposed === current) {
                return notFixable(issue, ctx, 'Could not improve the meta description automatically — expand the page content or edit manually.');
            }
            return {
                autoFixable: true,
                source: 'deterministic',
                fingerprint,
                patch: { metaDescription: proposed },
                changes: [fieldChange('metaDescription', 'Meta description', current, proposed)],
                justification: current.length > META_DESC_MAX
                    ? `Shortened the meta description to ${proposed.length} characters for SERP display.`
                    : `Extended the meta description to ${proposed.length} characters using page content.`,
            };
        }
        case 'missing-canonical': {
            const proposed = absoluteCanonicalUrl(entity, siteSeo);
            if (!proposed) {
                return notFixable(issue, ctx, 'Configure your site URL in SEO settings, then retry the canonical fix.');
            }
            const stored = entity.canonicalUrl?.trim() || null;
            return {
                autoFixable: true,
                source: 'deterministic',
                fingerprint,
                patch: { canonical: proposed },
                changes: [fieldChange('canonicalUrl', 'Canonical URL', stored, proposed)],
                justification: 'Sets the canonical URL to this page’s preferred public address.',
            };
        }
        default:
            return notFixable(issue, ctx, 'This issue type is not supported for automatic fixes yet.');
    }
}
/** Returns true when a deterministic meta description is too short to be useful. */
function deterministicDescriptionTooShort(suggestion) {
    const desc = suggestion.patch.metaDescription;
    return typeof desc === 'string' && desc.length > 0 && desc.length < META_DESC_MIN;
}
/**
 * Resolve a fix suggestion: deterministic first, then AI when needed.
 */
export async function suggestIssueFix(issue, ctx, aiDeps) {
    const deterministic = suggestDeterministicFix(issue, ctx);
    const { suggestAiFix, AI_FIX_TYPES } = await import('./issue-fix-ai.js');
    const needsAi = AI_FIX_TYPES.has(issue.type) &&
        aiDeps &&
        (!deterministic.autoFixable || deterministicDescriptionTooShort(deterministic));
    if (!needsAi) {
        return { suggestion: deterministic, cacheForApply: false };
    }
    const ai = await suggestAiFix(issue, ctx, aiDeps);
    if (!ai.ok) {
        return {
            suggestion: {
                autoFixable: false,
                source: 'ai',
                fingerprint: deterministic.fingerprint,
                patch: {},
                changes: [],
                justification: ai.message,
            },
            cacheForApply: false,
        };
    }
    return {
        suggestion: ai.suggestion,
        cacheForApply: ai.suggestion.source === 'ai' && ai.suggestion.autoFixable,
    };
}
//# sourceMappingURL=issue-fix.js.map