import { describe, expect, it } from 'vitest';
import { buildFrontmatterFromLegacy, extractFrontmatter } from './lesson-frontmatter.js';
// ─── extractFrontmatter ──────────────────────────────
describe('extractFrontmatter', () => {
    it('parses valid YAML frontmatter', () => {
        const content = `---
category: security
severity: warning
tags: ["auth", "jwt"]
lifecycle: nursery
---
## Lesson — Test heading

Body text.`;
        const result = extractFrontmatter(content);
        expect(result.hadYaml).toBe(true);
        expect(result.validYaml).toBe(true);
        expect(result.frontmatter.category).toBe('security');
        expect(result.frontmatter.severity).toBe('warning');
        expect(result.frontmatter.tags).toEqual(['auth', 'jwt']);
        expect(result.frontmatter.lifecycle).toBe('nursery');
        expect(result.body).toContain('## Lesson — Test heading');
        expect(result.body).not.toContain('---');
    });
    it('returns defaults when no YAML present', () => {
        const content = `## Lesson — No frontmatter

**Tags:** foo, bar

Body.`;
        const result = extractFrontmatter(content);
        expect(result.hadYaml).toBe(false);
        expect(result.validYaml).toBe(false);
        expect(result.frontmatter.type).toBe('trap');
        expect(result.frontmatter.severity).toBe('error');
        expect(result.frontmatter.tags).toEqual([]);
        expect(result.frontmatter.lifecycle).toBe('stable');
        expect(result.body).toBe(content);
    });
    it('handles malformed YAML gracefully (fail-open)', () => {
        const warnings = [];
        const content = `---
category: [invalid
---
## Lesson — Bad yaml

Body.`;
        const result = extractFrontmatter(content, (msg) => warnings.push(msg));
        expect(result.hadYaml).toBe(true);
        expect(result.validYaml).toBe(false);
        expect(result.frontmatter.type).toBe('trap');
        expect(result.body).toContain('## Lesson — Bad yaml');
        expect(warnings.length).toBeGreaterThan(0);
    });
    it('handles invalid field values gracefully (fail-open)', () => {
        const warnings = [];
        const content = `---
severity: critical
category: nonexistent
---
## Lesson — Bad values

Body.`;
        const result = extractFrontmatter(content, (msg) => warnings.push(msg));
        expect(result.hadYaml).toBe(true);
        expect(result.validYaml).toBe(false);
        expect(result.frontmatter.severity).toBe('error'); // default
        expect(warnings.length).toBeGreaterThan(0);
    });
    it('ignores ## inside YAML block (heading safety)', () => {
        const content = `---
category: architecture
tags: ["## not a heading"]
---
## Lesson — Real heading

Body.`;
        const result = extractFrontmatter(content);
        expect(result.hadYaml).toBe(true);
        expect(result.frontmatter.tags).toEqual(['## not a heading']);
        expect(result.body).toContain('## Lesson — Real heading');
    });
    it('handles CRLF line endings', () => {
        const content = '---\r\ncategory: security\r\nseverity: warning\r\n---\r\n## Lesson — CRLF\r\n\r\nBody.\r\n';
        const result = extractFrontmatter(content);
        expect(result.hadYaml).toBe(true);
        expect(result.frontmatter.category).toBe('security');
        expect(result.frontmatter.severity).toBe('warning');
        expect(result.body).toContain('## Lesson — CRLF');
    });
    it('parses compilation block', () => {
        const content = `---
compilation:
  engine: regex
  pattern: "\\\\beval\\\\("
severity: error
---
## Lesson — Eval trap

Body.`;
        const result = extractFrontmatter(content);
        expect(result.hadYaml).toBe(true);
        expect(result.frontmatter.compilation?.engine).toBe('regex');
        expect(result.frontmatter.compilation?.pattern).toBe('\\beval\\(');
    });
    it('parses ecosystem with multiple frameworks', () => {
        const content = `---
ecosystem:
  frameworks: ["nextjs", "react"]
---
## Lesson — Multi-framework

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.ecosystem?.frameworks).toEqual(['nextjs', 'react']);
    });
    it('parses scope with globs', () => {
        const content = `---
scope:
  globs: ["app/**/*.tsx", "!**/*.test.tsx"]
---
## Lesson — Scoped

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.scope?.globs).toEqual(['app/**/*.tsx', '!**/*.test.tsx']);
    });
    it('applies defaults for empty YAML block', () => {
        const content = `---

---
## Lesson — Empty frontmatter

Body.`;
        const result = extractFrontmatter(content);
        expect(result.hadYaml).toBe(true);
        expect(result.validYaml).toBe(true);
        expect(result.frontmatter.type).toBe('trap');
        expect(result.frontmatter.severity).toBe('error');
        expect(result.frontmatter.lifecycle).toBe('stable');
        expect(result.frontmatter.tags).toEqual([]);
    });
    it('validates rpn bounds', () => {
        const warnings = [];
        const content = `---
rpn: 15
---
## Lesson — Bad rpn

Body.`;
        const result = extractFrontmatter(content, (msg) => warnings.push(msg));
        expect(result.hadYaml).toBe(true);
        expect(result.validYaml).toBe(false);
        expect(warnings.length).toBeGreaterThan(0);
        expect(result.frontmatter.rpn).toBeUndefined(); // default, rpn out of range
    });
    it('does not match --- inside YAML values', () => {
        const content = `---
compilation:
  pattern: "^---$"
tags: ["a---b"]
---
## Lesson — Tricky dashes

Body.`;
        const result = extractFrontmatter(content);
        expect(result.hadYaml).toBe(true);
        expect(result.validYaml).toBe(true);
        expect(result.frontmatter.compilation?.pattern).toBe('^---$');
        expect(result.frontmatter.tags).toEqual(['a---b']);
    });
    it('accepts valid rpn', () => {
        const content = `---
rpn: 8
---
## Lesson — Valid rpn

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.rpn).toBe(8);
    });
});
// ─── buildFrontmatterFromLegacy ──────────────────────
describe('buildFrontmatterFromLegacy', () => {
    it('maps tags into frontmatter', () => {
        const fm = buildFrontmatterFromLegacy(['nextjs', 'auth'], '');
        expect(fm.tags).toEqual(['nextjs', 'auth']);
        expect(fm.type).toBe('trap');
    });
    it('maps legacy Pipeline 1 fields into compilation block', () => {
        const body = `Some text.

**Pattern:** \\beval\\(
**Engine:** regex
**Scope:** **/*.ts, **/*.js
**Severity:** warning`;
        const fm = buildFrontmatterFromLegacy([], body);
        expect(fm.compilation?.engine).toBe('regex');
        expect(fm.compilation?.pattern).toBe('\\beval\\(');
        expect(fm.scope?.globs).toEqual(['**/*.ts', '**/*.js']);
        expect(fm.severity).toBe('warning');
    });
    it('handles missing optional fields', () => {
        const fm = buildFrontmatterFromLegacy([], 'Just a body.');
        expect(fm.compilation).toBeUndefined();
        expect(fm.scope).toBeUndefined();
        expect(fm.severity).toBe('error'); // default
    });
    it('maps ast-grep engine', () => {
        const body = `**Pattern:** pattern: try { $A } catch { }
**Engine:** ast-grep
**Severity:** error`;
        const fm = buildFrontmatterFromLegacy([], body);
        expect(fm.compilation?.engine).toBe('ast-grep');
    });
    it('preserves tags alongside Pipeline 1 fields', () => {
        const body = `**Pattern:** \\bfoo\\b
**Engine:** regex
**Severity:** warning`;
        const fm = buildFrontmatterFromLegacy(['tag1', 'tag2'], body);
        expect(fm.tags).toEqual(['tag1', 'tag2']);
        expect(fm.compilation?.engine).toBe('regex');
    });
});
// ─── appliesTo (strategy item 020) ───────────────────
describe('extractFrontmatter — appliesTo', () => {
    it("defaults to ['any'] when applies-to is omitted", () => {
        const content = `---
category: architecture
---
## Lesson — No applies-to

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.appliesTo).toEqual(['any']);
    });
    it('parses YAML list of roles', () => {
        const content = `---
applies-to: [mutator, boundary]
---
## Lesson — List form

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.appliesTo).toEqual(['mutator', 'boundary']);
    });
    it('parses YAML scalar role into a single-element list', () => {
        const content = `---
applies-to: mutator
---
## Lesson — Scalar form

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.appliesTo).toEqual(['mutator']);
    });
    it("normalizes empty YAML list to ['any']", () => {
        const content = `---
applies-to: []
---
## Lesson — Empty list

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.appliesTo).toEqual(['any']);
    });
    it('lowercases mixed-case input', () => {
        const content = `---
applies-to: [MUTATOR, Any]
---
## Lesson — Mixed case

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.appliesTo).toEqual(['mutator', 'any']);
    });
    it('falls back to defaults and warns on unknown role', () => {
        const warnings = [];
        const content = `---
applies-to: [mutator, database]
---
## Lesson — Bad role

Body.`;
        const result = extractFrontmatter(content, (msg) => warnings.push(msg));
        expect(result.validYaml).toBe(false);
        expect(result.frontmatter.appliesTo).toEqual(['any']); // default
        expect(warnings.length).toBeGreaterThan(0);
    });
    it('preserves authored element order', () => {
        const a = extractFrontmatter(`---
applies-to: [boundary, mutator]
---
## Lesson — Order A

Body.`);
        const b = extractFrontmatter(`---
applies-to: [mutator, boundary]
---
## Lesson — Order B

Body.`);
        expect(a.frontmatter.appliesTo).toEqual(['boundary', 'mutator']);
        expect(b.frontmatter.appliesTo).toEqual(['mutator', 'boundary']);
    });
    it('accepts hot-path and boundary-test roles', () => {
        const content = `---
applies-to: [hot-path, boundary-test]
---
## Lesson — Hyphenated roles

Body.`;
        const result = extractFrontmatter(content);
        expect(result.frontmatter.appliesTo).toEqual(['hot-path', 'boundary-test']);
    });
});
describe('buildFrontmatterFromLegacy — appliesTo', () => {
    it("defaults to ['any'] when **Applies-to:** is missing", () => {
        const fm = buildFrontmatterFromLegacy([], 'Just a body.');
        expect(fm.appliesTo).toEqual(['any']);
    });
    it('parses comma-separated prose form', () => {
        const fm = buildFrontmatterFromLegacy([], '**Applies-to:** mutator, boundary');
        expect(fm.appliesTo).toEqual(['mutator', 'boundary']);
    });
    it('parses single-role prose form', () => {
        const fm = buildFrontmatterFromLegacy([], '**Applies-to:** mutator');
        expect(fm.appliesTo).toEqual(['mutator']);
    });
    it("normalizes whitespace-only prose to ['any']", () => {
        const fm = buildFrontmatterFromLegacy([], '**Applies-to:**   ');
        expect(fm.appliesTo).toEqual(['any']);
    });
    it('lowercases mixed-case prose', () => {
        const fm = buildFrontmatterFromLegacy([], '**Applies-to:** Mutator, ANY');
        expect(fm.appliesTo).toEqual(['mutator', 'any']);
    });
    it('falls back to defaults on unknown prose role', () => {
        const fm = buildFrontmatterFromLegacy([], '**Applies-to:** mutator, database');
        expect(fm.appliesTo).toEqual(['any']); // whole-frontmatter fallback
    });
});
//# sourceMappingURL=lesson-frontmatter.test.js.map