/**
 * Brand voice profile document — portable markdown + YAML frontmatter format.
 *
 * Pure utilities (no Node APIs) shared by the admin UI and server normalization.
 * The markdown file is the interchange format; flat form fields are derived from
 * it for prompt injection and the quick-edit UI.
 */
import { applyStructuredProfileToDocument } from './brand-voice-structured.js';
export const BRAND_VOICE_DOCUMENT_MAX = 50_000;
function escapeYamlString(value) {
    if (/[:#{}[\],&*?|>-]/.test(value) || value.includes('"')) {
        return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
    }
    return value.includes('\n') ? `"${value.replace(/"/g, '\\"')}"` : value;
}
function yamlList(items) {
    if (items.length === 0)
        return '    []';
    return items.map((item) => `    - ${escapeYamlString(item)}`).join('\n');
}
/** Split a markdown file into YAML frontmatter and body. */
export function splitBrandVoiceDocument(markdown) {
    const trimmed = markdown.trim();
    const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/.exec(trimmed);
    if (!match)
        return null;
    return { frontmatter: match[1].trim(), body: match[2].trim() };
}
function readScalar(frontmatter, key) {
    const re = new RegExp(`^\\s*${key}:\\s*(.+?)\\s*(?:#.*)?$`, 'm');
    const match = re.exec(frontmatter);
    if (!match)
        return '';
    return match[1].replace(/^["']|["']$/g, '').trim();
}
/** Next top-level frontmatter line (unindented key, comment, or EOF). */
const FRONTMATTER_SECTION_END = '(?=^[^\\s#].|^#|\\Z)';
function readNestedScalar(frontmatter, section, key) {
    const sectionRe = new RegExp(`^${section}:\\s*\\n([\\s\\S]*?)${FRONTMATTER_SECTION_END}`, 'm');
    const sectionMatch = sectionRe.exec(frontmatter);
    if (!sectionMatch)
        return '';
    return readScalar(sectionMatch[1], key);
}
function readYamlList(frontmatter, key) {
    const re = new RegExp(`^\\s*${key}:\\s*\\n((?:\\s+-\\s+.+\n?)*)`, 'm');
    const match = re.exec(frontmatter);
    if (!match)
        return [];
    return [...match[1].matchAll(/^\s+-\s+(.+)\s*$/gm)]
        .map((m) => m[1].replace(/^["']|["']$/g, '').trim())
        .filter(Boolean);
}
function readMarkdownSection(body, heading) {
    const re = new RegExp(`^##\\s+${heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$\\n([\\s\\S]*?)(?=^##\\s+|\\Z)`, 'im');
    const match = re.exec(body);
    if (!match)
        return '';
    return match[1]
        .replace(/^>\s.*$/gm, '')
        .replace(/^\*.*\*$/gm, '')
        .trim();
}
function extractWorkedExampleSample(body) {
    const section = readMarkdownSection(body, 'Worked examples');
    if (!section)
        return '';
    const row = section
        .split('\n')
        .map((line) => line.trim())
        .find((line) => line.startsWith('|') && !line.includes('Off-brand') && !line.match(/^\|[-| ]+\|$/));
    if (!row)
        return '';
    const cells = row
        .split('|')
        .map((c) => c.trim())
        .filter(Boolean);
    return cells[1]?.replace(/^["']|["']$/g, '') ?? '';
}
/** Derive quick-edit form fields from a profile document body + frontmatter. */
export function deriveBrandVoiceFields(body, frontmatter) {
    const name = readNestedScalar(frontmatter, 'profile', 'brand_name');
    const voiceLine = readMarkdownSection(body, 'Voice in one line');
    const persona = readMarkdownSection(body, 'Persona');
    const toneParts = [voiceLine, persona].filter(Boolean);
    const audience = readMarkdownSection(body, 'Audience');
    const doSection = readMarkdownSection(body, 'Do');
    const dontSection = readMarkdownSection(body, "Don't");
    const guidelinesParts = [];
    if (doSection)
        guidelinesParts.push(`Do:\n${doSection}`);
    if (dontSection)
        guidelinesParts.push(`Don't:\n${dontSection}`);
    const sampleFromTable = extractWorkedExampleSample(body);
    return {
        name,
        tone: toneParts.join('\n\n'),
        audience,
        guidelines: guidelinesParts.join('\n\n'),
        sampleText: sampleFromTable,
    };
}
function parseMeta(frontmatter) {
    const versionRaw = readNestedScalar(frontmatter, 'profile', 'version');
    const version = Number.parseInt(versionRaw, 10);
    const statusRaw = readNestedScalar(frontmatter, 'profile', 'status');
    const status = statusRaw === 'reviewed' || statusRaw === 'approved' ? statusRaw : 'draft';
    const profileSection = new RegExp(`^profile:\\s*\\n([\\s\\S]*?)${FRONTMATTER_SECTION_END}`, 'm').exec(frontmatter)?.[1] ?? '';
    const generatedFrom = readYamlList(profileSection, 'generated_from');
    const lastUpdated = readNestedScalar(frontmatter, 'profile', 'last_updated') ||
        new Date().toISOString().slice(0, 10);
    return {
        version: Number.isFinite(version) && version > 0 ? version : 1,
        status,
        generatedFrom,
        lastUpdated,
    };
}
export function parseBrandVoiceDocument(markdown) {
    const raw = markdown.trim();
    if (!raw)
        return { ok: false, error: 'Profile document is empty.' };
    if (raw.length > BRAND_VOICE_DOCUMENT_MAX) {
        return { ok: false, error: `Profile document exceeds ${BRAND_VOICE_DOCUMENT_MAX} characters.` };
    }
    const split = splitBrandVoiceDocument(raw);
    if (!split) {
        return { ok: false, error: 'Profile must start with YAML frontmatter (--- ... ---).' };
    }
    const meta = parseMeta(split.frontmatter);
    const fields = deriveBrandVoiceFields(split.body, split.frontmatter);
    if (!fields.name.trim() && !fields.tone.trim() && !(fields.guidelines ?? '').trim()) {
        return {
            ok: false,
            error: "Profile document must include a brand name, tone, or Do/Don't guidance.",
        };
    }
    return {
        ok: true,
        value: {
            document: raw,
            frontmatter: split.frontmatter,
            body: split.body,
            meta,
            fields,
        },
    };
}
function defaultFrontmatter(meta, name) {
    return `# ─────────────────────────────────────────────────────────────────────
# MACHINE-READABLE RULES
# The analyze-and-fix engine parses this frontmatter directly.
# Everything here is deterministic — testable, enforceable, explainable.
# ─────────────────────────────────────────────────────────────────────

profile:
  brand_name: ${escapeYamlString(name || 'Brand voice')}
  version: ${meta.version}
  generated_from:
${yamlList(meta.generatedFrom)}
  last_updated: ${escapeYamlString(meta.lastUpdated)}
  status: ${meta.status}

tone:
  formal_casual: 50
  serious_playful: 50
  concise_detailed: 50
  reserved_bold: 50
  reading_level: 8

mechanics:
  oxford_comma: true
  contractions: allow
  exclamation_points: avoid
  emoji: forbid
  case_headlines: sentence
  case_buttons: sentence
  numbers: numerals_for_10_plus
  serial_spelling: us

vocabulary:
  banned: []
  preferred: []
  avoid_patterns: []

channels:
  meta_description:
    max_chars: 160
    note: "lead with the benefit; one clear action"
  blog:
    note: "authoritative; still no hype words"`;
}
function defaultBody(fields) {
    const voiceLine = fields.tone.split('\n\n')[0]?.trim() ?? fields.tone.trim();
    const persona = fields.tone.includes('\n\n')
        ? fields.tone.split('\n\n').slice(1).join('\n\n').trim()
        : '';
    const doLines = (fields.guidelines ?? '')
        .split(/\n?Don't:\n/i)[0]
        ?.replace(/^Do:\n?/i, '')
        .trim();
    const dontLines = (fields.guidelines ?? '').split(/\n?Don't:\n/i)[1]?.trim() ?? '';
    const sample = (fields.sampleText ?? '').trim();
    return `# Brand Voice — Human Guidance

> This half of the file is read by the engine as context and by your team as
> the source of truth. Edit freely. The frontmatter above is what gets enforced;
> this prose is what makes the enforcement *make sense*.

## Voice in one line
${voiceLine || '_Describe how the brand sounds at its best._'}

## Persona
${persona || '_Who the brand sounds like — and who it does not._'}

## Audience
${(fields.audience ?? '').trim() || '_Who is being spoken to, and what they care about._'}

## Do
${doLines || '- Lead with the benefit or the point; front-load the value.'}

## Don't
${dontLines || '- No hype words or stacked superlatives.'}

## Worked examples
| Off-brand | On-brand | Why |
|---|---|---|
${sample ? `| _Before_ | "${sample.replace(/"/g, '\\"')}" | Representative on-brand sample. |` : '| _Off-brand example_ | _On-brand rewrite_ | _Why the change helps._ |'}

## Notes / change log
- v1 — profile created in Actuate CMS.`;
}
/** Serialize form fields (and optional metadata) into the portable markdown file. */
export function serializeBrandVoiceDocument(input) {
    const existing = input.existingDocument?.trim();
    if (existing) {
        const split = splitBrandVoiceDocument(existing);
        if (split) {
            const meta = {
                ...parseMeta(split.frontmatter),
                ...input.meta,
                lastUpdated: new Date().toISOString().slice(0, 10),
            };
            if (input.meta?.version == null)
                meta.version += 1;
            let document = `---\n${mergeFrontmatterProfile(split.frontmatter, input.fields, meta)}\n---\n\n${defaultBody(input.fields)}`;
            if (input.structured) {
                document = applyStructuredProfileToDocument(document, input.structured);
            }
            return document.slice(0, BRAND_VOICE_DOCUMENT_MAX);
        }
    }
    const meta = {
        version: input.meta?.version ?? 1,
        status: input.meta?.status ?? 'draft',
        generatedFrom: input.meta?.generatedFrom ?? [],
        lastUpdated: input.meta?.lastUpdated ?? new Date().toISOString().slice(0, 10),
    };
    let document = `---\n${defaultFrontmatter(meta, input.fields.name)}\n---\n\n${defaultBody(input.fields)}`;
    if (input.structured) {
        document = applyStructuredProfileToDocument(document, input.structured);
    }
    return document.slice(0, BRAND_VOICE_DOCUMENT_MAX);
}
function mergeFrontmatterProfile(frontmatter, fields, meta) {
    let next = frontmatter;
    const setNested = (section, key, value) => {
        const sectionRe = new RegExp(`(^${section}:\\s*\\n)([\\s\\S]*?)${FRONTMATTER_SECTION_END}`, 'm');
        const match = sectionRe.exec(next);
        if (!match)
            return;
        const block = match[2];
        const keyRe = new RegExp(`^(\\s*${key}:\\s*).*$`, 'm');
        if (keyRe.test(block)) {
            next = next.replace(sectionRe, (_all, head, body) => {
                const updatedBody = body.replace(keyRe, `$1${value}`);
                return `${head}${updatedBody}`;
            });
        }
    };
    setNested('profile', 'brand_name', escapeYamlString(fields.name || 'Brand voice'));
    setNested('profile', 'version', meta.version);
    setNested('profile', 'status', meta.status);
    setNested('profile', 'last_updated', escapeYamlString(meta.lastUpdated));
    return next;
}
/** Merge parsed document fields into a profile, preserving enabled flag. */
export function applyParsedDocumentToProfile(profile, parsed) {
    return {
        ...profile,
        ...parsed.fields,
        document: parsed.document,
        version: parsed.meta.version,
        status: parsed.meta.status,
        generatedFrom: parsed.meta.generatedFrom.length
            ? parsed.meta.generatedFrom
            : profile.generatedFrom,
    };
}
/** Build a profile document from quick-edit fields when none is stored yet. */
export function ensureBrandVoiceDocument(profile) {
    if (profile.document?.trim())
        return profile.document.trim();
    return serializeBrandVoiceDocument({
        fields: {
            name: profile.name ?? '',
            tone: profile.tone ?? '',
            audience: profile.audience ?? '',
            guidelines: profile.guidelines ?? '',
            sampleText: profile.sampleText ?? '',
        },
        meta: {
            version: profile.version ?? 1,
            status: profile.status ?? 'draft',
            generatedFrom: profile.generatedFrom ?? [],
        },
    });
}
/** Sync flat fields from the stored document (when present). */
export function syncFieldsFromDocument(profile) {
    if (!profile.document?.trim())
        return profile;
    const parsed = parseBrandVoiceDocument(profile.document);
    if (!parsed.ok)
        return profile;
    return {
        ...profile,
        ...parsed.value.fields,
        version: parsed.value.meta.version,
        status: parsed.value.meta.status,
        generatedFrom: parsed.value.meta.generatedFrom,
    };
}
export const BRAND_VOICE_EXPORT_FILENAME = 'brand-voice-profile.md';
//# sourceMappingURL=brand-voice-document.js.map