/**
 * Structured brand voice rules — parsed from profile frontmatter and used for
 * deterministic alignment scoring and the Phase 3 admin editors.
 */
import { splitBrandVoiceDocument } from './brand-voice-document.js';
export const BRAND_VOICE_CHANNEL_PRESETS = [
    { id: 'meta_description', label: 'Meta description' },
    { id: 'blog', label: 'Blog post' },
    { id: 'google_ads', label: 'Google Ads' },
];
const FRONTMATTER_SECTION_END = '(?=^[^\\s#].|^#|\\Z)';
const MAX_WORKED_EXAMPLES = 10;
const MAX_BANNED_TERMS = 40;
const MAX_PREFERRED_TERMS = 40;
const MAX_AVOID_PATTERNS = 20;
function extractSection(frontmatter, section) {
    const re = new RegExp(`^${section}:\\s*\\n([\\s\\S]*?)${FRONTMATTER_SECTION_END}`, 'm');
    return re.exec(frontmatter)?.[1]?.trim() ?? '';
}
function readScale(section, key, fallback) {
    const match = new RegExp(`^\\s*${key}:\\s*(\\d+)`, 'm').exec(section);
    if (!match)
        return fallback;
    const n = Number.parseInt(match[1], 10);
    return Number.isFinite(n) ? Math.min(100, Math.max(0, n)) : fallback;
}
function readBool(section, key, fallback) {
    const match = new RegExp(`^\\s*${key}:\\s*(true|false)`, 'm').exec(section);
    if (!match)
        return fallback;
    return match[1] === 'true';
}
function readEnum(section, key, allowed, fallback) {
    const match = new RegExp(`^\\s*${key}:\\s*(\\S+)`, 'm').exec(section);
    if (!match)
        return fallback;
    const value = match[1];
    return allowed.includes(value) ? value : fallback;
}
function parseBannedTerms(vocabularySection) {
    const terms = [];
    const blocks = vocabularySection.split(/\n\s*-\s*term:/).slice(1);
    for (const block of blocks) {
        const termMatch = /^\s*"?([^"\n]+)"?/.exec(block);
        const useMatch = /use_instead:\s*"?([^"\n#]*)"?/.exec(block);
        if (!termMatch)
            continue;
        terms.push({
            term: termMatch[1].trim(),
            useInstead: (useMatch?.[1] ?? '').trim(),
        });
    }
    return terms.slice(0, MAX_BANNED_TERMS);
}
function parseSimpleList(section, key) {
    const block = new RegExp(`^\\s*${key}:\\s*\\n((?:\\s+-\\s+.+\n?)*)`, 'm').exec(section)?.[1] ?? '';
    return [...block.matchAll(/^\s+-\s+(.+)\s*$/gm)]
        .map((m) => m[1].replace(/^["']|["']$/g, '').trim())
        .filter(Boolean)
        .slice(0, key === 'preferred' ? MAX_PREFERRED_TERMS : MAX_AVOID_PATTERNS);
}
function parseChannels(channelsSection) {
    const rules = [];
    const chunks = channelsSection.split(/\n(?=[a-z][a-z0-9_]*:)/);
    for (const chunk of chunks) {
        const idMatch = /^([a-z][a-z0-9_]*):/m.exec(chunk);
        if (!idMatch)
            continue;
        const id = idMatch[1];
        const preset = BRAND_VOICE_CHANNEL_PRESETS.find((p) => p.id === id);
        const readNum = (key) => {
            const m = new RegExp(`^\\s*${key}:\\s*(\\d+)`, 'm').exec(chunk);
            return m ? Number.parseInt(m[1], 10) : undefined;
        };
        const noteMatch = /^\s*note:\s*"?([^"\n]+)"?/m.exec(chunk);
        rules.push({
            id,
            label: preset?.label ?? id.replace(/_/g, ' '),
            maxChars: readNum('max_chars'),
            maxHeadlineChars: readNum('max_headline_chars'),
            maxDescriptionChars: readNum('max_description_chars'),
            note: noteMatch?.[1]?.trim(),
        });
    }
    return rules;
}
function parseWorkedExamples(body) {
    const sectionRe = /^##\s+Worked examples\s*$[\s\S]*?(?=^##\s+|\Z)/im;
    const section = sectionRe.exec(body)?.[0] ?? '';
    const rows = section
        .split('\n')
        .map((line) => line.trim())
        .filter((line) => line.startsWith('|') && !line.includes('Off-brand') && !/^\|[-| ]+\|$/.test(line));
    const examples = [];
    for (const row of rows) {
        const cells = row
            .split('|')
            .map((c) => c.trim())
            .filter(Boolean);
        if (cells.length < 3)
            continue;
        examples.push({
            offBrand: cells[0].replace(/^["']|["']$/g, ''),
            onBrand: cells[1].replace(/^["']|["']$/g, ''),
            why: cells[2].replace(/^["']|["']$/g, ''),
        });
    }
    return examples.slice(0, MAX_WORKED_EXAMPLES);
}
export function defaultStructuredProfile() {
    return {
        tone: {
            formalCasual: 50,
            seriousPlayful: 50,
            conciseDetailed: 50,
            reservedBold: 50,
            readingLevel: 8,
        },
        mechanics: {
            oxfordComma: true,
            contractions: 'allow',
            exclamationPoints: 'avoid',
            emoji: 'forbid',
            caseHeadlines: 'sentence',
            caseButtons: 'sentence',
            numbers: 'numerals_for_10_plus',
            serialSpelling: 'us',
        },
        bannedTerms: [],
        preferredTerms: [],
        avoidPatterns: [],
        channels: BRAND_VOICE_CHANNEL_PRESETS.map((preset) => ({
            id: preset.id,
            label: preset.label,
            ...(preset.id === 'meta_description' ? { maxChars: 160 } : {}),
            ...(preset.id === 'google_ads' ? { maxHeadlineChars: 30, maxDescriptionChars: 90 } : {}),
            ...(preset.id === 'blog' ? { note: 'authoritative; still no hype words' } : {}),
            ...(preset.id === 'meta_description'
                ? { note: 'lead with the benefit; one clear action' }
                : {}),
        })),
        workedExamples: [],
    };
}
/** Parse machine-readable rules from a profile markdown document. */
export function parseStructuredProfile(document) {
    const defaults = defaultStructuredProfile();
    const split = splitBrandVoiceDocument(document.trim());
    if (!split)
        return defaults;
    const toneSection = extractSection(split.frontmatter, 'tone');
    const mechanicsSection = extractSection(split.frontmatter, 'mechanics');
    const vocabularySection = extractSection(split.frontmatter, 'vocabulary');
    const channelsSection = extractSection(split.frontmatter, 'channels');
    const parsedChannels = parseChannels(channelsSection);
    const channels = parsedChannels.length > 0 ? parsedChannels : defaults.channels;
    return {
        tone: {
            formalCasual: readScale(toneSection, 'formal_casual', defaults.tone.formalCasual),
            seriousPlayful: readScale(toneSection, 'serious_playful', defaults.tone.seriousPlayful),
            conciseDetailed: readScale(toneSection, 'concise_detailed', defaults.tone.conciseDetailed),
            reservedBold: readScale(toneSection, 'reserved_bold', defaults.tone.reservedBold),
            readingLevel: readScale(toneSection, 'reading_level', defaults.tone.readingLevel),
        },
        mechanics: {
            oxfordComma: readBool(mechanicsSection, 'oxford_comma', defaults.mechanics.oxfordComma),
            contractions: readEnum(mechanicsSection, 'contractions', ['allow', 'avoid', 'require'], defaults.mechanics.contractions),
            exclamationPoints: readEnum(mechanicsSection, 'exclamation_points', ['allow', 'avoid', 'forbid'], defaults.mechanics.exclamationPoints),
            emoji: readEnum(mechanicsSection, 'emoji', ['allow', 'sparingly', 'forbid'], defaults.mechanics.emoji),
            caseHeadlines: readEnum(mechanicsSection, 'case_headlines', ['sentence', 'title'], defaults.mechanics.caseHeadlines),
            caseButtons: readEnum(mechanicsSection, 'case_buttons', ['sentence', 'title'], defaults.mechanics.caseButtons),
            numbers: readEnum(mechanicsSection, 'numbers', ['spell_all', 'numerals_all', 'numerals_for_10_plus'], defaults.mechanics.numbers),
            serialSpelling: readEnum(mechanicsSection, 'serial_spelling', ['us', 'uk'], defaults.mechanics.serialSpelling),
        },
        bannedTerms: parseBannedTerms(vocabularySection),
        preferredTerms: parseSimpleList(vocabularySection, 'preferred'),
        avoidPatterns: parseSimpleList(vocabularySection, 'avoid_patterns'),
        channels,
        workedExamples: parseWorkedExamples(split.body),
    };
}
function escapeYaml(value) {
    if (/[:#{}[\],&*?|>-]/.test(value) || value.includes('"')) {
        return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
    }
    return value.includes('\n') ? `"${value.replace(/"/g, '\\"')}"` : value;
}
function renderToneSection(tone) {
    return `tone:
  formal_casual: ${tone.formalCasual}
  serious_playful: ${tone.seriousPlayful}
  concise_detailed: ${tone.conciseDetailed}
  reserved_bold: ${tone.reservedBold}
  reading_level: ${tone.readingLevel}`;
}
function renderMechanicsSection(mechanics) {
    return `mechanics:
  oxford_comma: ${mechanics.oxfordComma}
  contractions: ${mechanics.contractions}
  exclamation_points: ${mechanics.exclamationPoints}
  emoji: ${mechanics.emoji}
  case_headlines: ${mechanics.caseHeadlines}
  case_buttons: ${mechanics.caseButtons}
  numbers: ${mechanics.numbers}
  serial_spelling: ${mechanics.serialSpelling}`;
}
function renderVocabularySection(structured) {
    const banned = structured.bannedTerms.length === 0
        ? '  banned: []'
        : `  banned:\n${structured.bannedTerms
            .map((row) => `    - term: ${escapeYaml(row.term)}\n      use_instead: ${escapeYaml(row.useInstead)}`)
            .join('\n')}`;
    const preferred = structured.preferredTerms.length === 0
        ? '  preferred: []'
        : `  preferred:\n${structured.preferredTerms.map((t) => `    - ${escapeYaml(t)}`).join('\n')}`;
    const avoid = structured.avoidPatterns.length === 0
        ? '  avoid_patterns: []'
        : `  avoid_patterns:\n${structured.avoidPatterns.map((t) => `    - ${escapeYaml(t)}`).join('\n')}`;
    return `vocabulary:\n${banned}\n${preferred}\n${avoid}`;
}
function renderChannelsSection(channels) {
    if (channels.length === 0)
        return 'channels: {}';
    const lines = channels.map((channel) => {
        const props = [`  ${channel.id}:`];
        if (typeof channel.maxChars === 'number')
            props.push(`    max_chars: ${channel.maxChars}`);
        if (typeof channel.maxHeadlineChars === 'number') {
            props.push(`    max_headline_chars: ${channel.maxHeadlineChars}`);
        }
        if (typeof channel.maxDescriptionChars === 'number') {
            props.push(`    max_description_chars: ${channel.maxDescriptionChars}`);
        }
        if (channel.note?.trim())
            props.push(`    note: ${escapeYaml(channel.note.trim())}`);
        return props.join('\n');
    });
    return `channels:\n${lines.join('\n')}`;
}
function renderWorkedExamplesSection(examples) {
    const rows = examples.length === 0
        ? '| _Off-brand example_ | _On-brand rewrite_ | _Why the change helps._ |'
        : examples.map((ex) => `| ${ex.offBrand} | ${ex.onBrand} | ${ex.why} |`).join('\n');
    return `## Worked examples
| Off-brand | On-brand | Why |
|---|---|---|
${rows}
`;
}
function replaceFrontmatterSection(frontmatter, section, content) {
    const re = new RegExp(`^${section}:[\\s\\S]*?(?=${FRONTMATTER_SECTION_END})`, 'm');
    if (re.test(frontmatter))
        return frontmatter.replace(re, content);
    return `${frontmatter.trim()}\n\n${content}`;
}
function replaceBodySection(body, heading, content) {
    const re = new RegExp(`^##\\s+${heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\s\\S]*?(?=^##\\s+|\\Z)`, 'im');
    if (re.test(body))
        return body.replace(re, `${content.trim()}\n`);
    return `${body.trim()}\n\n${content.trim()}`;
}
/** Apply structured rule edits back into the profile markdown document. */
export function applyStructuredProfileToDocument(document, structured) {
    const split = splitBrandVoiceDocument(document.trim());
    if (!split)
        return document;
    let frontmatter = split.frontmatter;
    frontmatter = replaceFrontmatterSection(frontmatter, 'tone', renderToneSection(structured.tone));
    frontmatter = replaceFrontmatterSection(frontmatter, 'mechanics', renderMechanicsSection(structured.mechanics));
    frontmatter = replaceFrontmatterSection(frontmatter, 'vocabulary', renderVocabularySection(structured));
    frontmatter = replaceFrontmatterSection(frontmatter, 'channels', renderChannelsSection(structured.channels));
    let body = replaceBodySection(split.body, 'Worked examples', renderWorkedExamplesSection(structured.workedExamples));
    return `---\n${frontmatter.trim()}\n---\n\n${body.trim()}`;
}
const EMOJI_RE = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}]/u;
const CONTRACTION_RE = /\b(?:can't|won't|don't|it's|we're|you're|they're|i'm|you'll|we'll|isn't|aren't|wasn't|weren't|haven't|hasn't|didn't|doesn't|wouldn't|couldn't|shouldn't)\b/i;
const SUPERLATIVE_RE = /\b(best|worst|greatest|revolutionary|amazing|incredible|unbelievable|world[- ]class|leading)\b/gi;
function clampScore(score) {
    return Math.max(0, Math.min(100, Math.round(score)));
}
/** Deterministic alignment score using frontmatter rules. */
export function scoreBrandAlignment(content, structured, channelId) {
    const text = content.trim();
    const lower = text.toLowerCase();
    const issues = [];
    const dimensions = [];
    let score = 100;
    for (const banned of structured.bannedTerms) {
        if (!banned.term.trim())
            continue;
        const term = banned.term.trim();
        if (!lower.includes(term.toLowerCase()))
            continue;
        score -= 15;
        issues.push({
            id: `banned-${term}`,
            severity: 'error',
            message: `Banned term "${term}" found.`,
            suggestion: banned.useInstead
                ? `Use "${banned.useInstead}" instead.`
                : 'Remove this term entirely.',
        });
    }
    const exclamations = (text.match(/!/g) ?? []).length;
    if (exclamations > 0) {
        if (structured.mechanics.exclamationPoints === 'forbid') {
            score -= exclamations * 12;
            issues.push({
                id: 'exclamation-forbid',
                severity: 'error',
                message: `Exclamation point${exclamations === 1 ? '' : 's'} not allowed (${exclamations}).`,
                suggestion: 'Remove exclamation points for this brand voice.',
            });
        }
        else if (structured.mechanics.exclamationPoints === 'avoid') {
            score -= exclamations * 6;
            issues.push({
                id: 'exclamation-avoid',
                severity: 'warning',
                message: `Exclamation point${exclamations === 1 ? '' : 's'} discouraged (${exclamations}).`,
            });
        }
    }
    if (structured.mechanics.emoji === 'forbid' && EMOJI_RE.test(text)) {
        score -= 15;
        issues.push({
            id: 'emoji-forbid',
            severity: 'error',
            message: 'Emoji are not allowed.',
            suggestion: 'Remove emoji from the copy.',
        });
    }
    if (structured.mechanics.contractions === 'avoid' && CONTRACTION_RE.test(text)) {
        score -= 8;
        issues.push({
            id: 'contractions-avoid',
            severity: 'warning',
            message: 'Contractions are discouraged for this brand voice.',
            suggestion: 'Expand contractions (e.g. "do not" instead of "don\'t").',
        });
    }
    for (const pattern of structured.avoidPatterns) {
        if (!pattern.trim())
            continue;
        const normalized = pattern.toLowerCase();
        if (normalized.includes('hype stacking') && (text.match(SUPERLATIVE_RE) ?? []).length >= 2) {
            score -= 10;
            issues.push({
                id: 'avoid-hype-stacking',
                severity: 'warning',
                message: 'Multiple hype words detected in one piece.',
            });
            continue;
        }
        if (normalized.includes("in today's") && lower.includes("in today's")) {
            score -= 10;
            issues.push({
                id: 'avoid-filler-opener',
                severity: 'warning',
                message: 'Filler opener detected ("In today\'s…").',
                suggestion: 'Start with the point or benefit instead.',
            });
            continue;
        }
        if (normalized.includes('vague claims') && SUPERLATIVE_RE.test(text) && !/\d/.test(text)) {
            score -= 8;
            issues.push({
                id: 'avoid-vague-claim',
                severity: 'warning',
                message: 'Strong claim without a supporting number or proof point.',
            });
        }
    }
    const preferredHits = structured.preferredTerms.filter((term) => lower.includes(term.toLowerCase()));
    const preferredScore = structured.preferredTerms.length > 0
        ? clampScore((preferredHits.length / structured.preferredTerms.length) * 100)
        : 100;
    dimensions.push({
        name: 'Preferred vocabulary',
        score: preferredScore,
        feedback: preferredHits.length > 0
            ? `Uses ${preferredHits.length} preferred term(s).`
            : structured.preferredTerms.length > 0
                ? 'Consider using more preferred brand terms.'
                : 'No preferred terms configured.',
    });
    if (structured.preferredTerms.length > 0 && preferredHits.length === 0) {
        issues.push({
            id: 'preferred-missing',
            severity: 'info',
            message: 'No preferred brand terms detected.',
        });
    }
    const channel = channelId ? structured.channels.find((c) => c.id === channelId) : undefined;
    if (channel) {
        const len = text.length;
        const limit = channel.maxChars ?? channel.maxDescriptionChars;
        if (typeof limit === 'number' && len > limit) {
            score -= 20;
            issues.push({
                id: `channel-length-${channel.id}`,
                severity: 'error',
                message: `${channel.label} exceeds ${limit} characters (${len}).`,
                suggestion: `Trim to ${limit} characters or fewer.`,
            });
        }
        dimensions.push({
            name: `${channel.label} length`,
            score: typeof limit === 'number' ? clampScore(100 - Math.max(0, len - limit) * 2) : 100,
            feedback: typeof limit === 'number'
                ? `${len}/${limit} characters`
                : 'No length limit configured for this channel.',
        });
    }
    const bannedScore = structured.bannedTerms.length > 0
        ? clampScore(100 - issues.filter((i) => i.id.startsWith('banned-')).length * 25)
        : 100;
    dimensions.push({
        name: 'Banned vocabulary',
        score: bannedScore,
        feedback: bannedScore >= 100 ? 'No banned terms detected.' : 'Off-brand terms found in the copy.',
    });
    dimensions.push({
        name: 'Mechanics',
        score: clampScore(100 -
            issues.filter((i) => ['exclamation-forbid', 'emoji-forbid'].includes(i.id)).length * 20 -
            issues.filter((i) => i.severity === 'warning' && i.id.startsWith('exclamation')).length * 8),
        feedback: 'Style mechanics (punctuation, emoji, contractions).',
    });
    return {
        score: clampScore(score),
        channel: channel?.id,
        issues,
        dimensions,
    };
}
export { MAX_BANNED_TERMS, MAX_PREFERRED_TERMS, MAX_AVOID_PATTERNS, MAX_WORKED_EXAMPLES };
//# sourceMappingURL=brand-voice-structured.js.map