// lib/parsers/plan-schema.mjs
// Plan v1.7 schema parser — heading shape detection.
//
// The canonical step heading is `### Step N: <title>` (literal colon-space).
// Forbidden narrative drift formats (introduced in v1.8.0 to defend against
// Opus 4.7 schema-drift): `## Fase N`, `### Phase N`, `### Stage N`, `### Steg N`.
//
// This module extracts step boundaries; per-step body parsing lives elsewhere.

import { ok, fail, issue } from '../util/result.mjs';

export const STEP_HEADING_REGEX = /^### Step (\d+):\s+(.+?)\s*$/m;
export const STEP_HEADING_GLOBAL = /^### Step (\d+):\s+(.+?)\s*$/gm;
export const FORBIDDEN_HEADING_REGEX = /^(?:##|###) (?:Fase|Phase|Stage|Steg) \d+/m;
export const FORBIDDEN_HEADING_GLOBAL = /^(?:##|###) (?:Fase|Phase|Stage|Steg) \d+/gm;
export const PLAN_VERSION_REGEX = /^plan_version:\s*['"]?([\d.]+)['"]?/m;

/**
 * Find all step heading positions in plan text.
 * @returns {Array<{n: number, title: string, line: number, offset: number}>}
 */
export function findSteps(text) {
  if (typeof text !== 'string') return [];
  const out = [];
  STEP_HEADING_GLOBAL.lastIndex = 0;
  let m;
  while ((m = STEP_HEADING_GLOBAL.exec(text)) !== null) {
    const offset = m.index;
    const line = text.slice(0, offset).split(/\r?\n/).length;
    out.push({ n: Number.parseInt(m[1], 10), title: m[2].trim(), line, offset });
  }
  return out;
}

/**
 * Find forbidden narrative-drift heading occurrences (Fase/Phase/Stage/Steg N).
 * @returns {Array<{form: string, line: number, offset: number, raw: string}>}
 */
export function findForbiddenHeadings(text) {
  if (typeof text !== 'string') return [];
  const out = [];
  FORBIDDEN_HEADING_GLOBAL.lastIndex = 0;
  let m;
  while ((m = FORBIDDEN_HEADING_GLOBAL.exec(text)) !== null) {
    const offset = m.index;
    const line = text.slice(0, offset).split(/\r?\n/).length;
    const raw = m[0];
    out.push({ form: raw, line, offset, raw });
  }
  return out;
}

/**
 * Slice plan text into per-step sections.
 * @returns {Array<{n: number, title: string, body: string, line: number}>}
 */
export function sliceSteps(text) {
  const heads = findSteps(text);
  const sections = [];
  for (let i = 0; i < heads.length; i++) {
    const start = heads[i].offset;
    const end = i + 1 < heads.length ? heads[i + 1].offset : text.length;
    const block = text.slice(start, end);
    sections.push({
      n: heads[i].n,
      title: heads[i].title,
      body: block,
      line: heads[i].line,
    });
  }
  return sections;
}

/**
 * Extract `plan_version: X.Y` from frontmatter or doc body.
 */
export function extractPlanVersion(text) {
  const m = typeof text === 'string' ? text.match(PLAN_VERSION_REGEX) : null;
  return m ? m[1] : null;
}

/**
 * Validate plan structure at the heading level.
 * Strict mode: forbidden-heading count > 0 → error. Step numbers must be 1..N contiguous.
 * @returns {import('../util/result.mjs').Result}
 */
export function validatePlanHeadings(text, opts = {}) {
  const strict = opts.strict !== false;
  const errors = [];
  const warnings = [];

  if (typeof text !== 'string') {
    return fail(issue('PLAN_INPUT', 'Plan text is not a string'));
  }

  const forbidden = findForbiddenHeadings(text);
  if (forbidden.length > 0) {
    const list = forbidden.map(f => `line ${f.line}: ${f.raw}`).join('; ');
    const errorIssue = issue(
      'PLAN_FORBIDDEN_HEADING',
      `Found ${forbidden.length} forbidden narrative-drift heading(s): ${list}`,
      'Use canonical "### Step N: <title>". Forbidden forms: Fase/Phase/Stage/Steg.',
    );
    if (strict) errors.push(errorIssue);
    else warnings.push(errorIssue);
  }

  const steps = findSteps(text);
  if (steps.length === 0) {
    errors.push(issue('PLAN_NO_STEPS', 'No step headings found', 'Expected at least one "### Step 1: <title>".'));
  } else {
    const numbers = steps.map(s => s.n);
    for (let i = 0; i < numbers.length; i++) {
      if (numbers[i] !== i + 1) {
        errors.push(issue(
          'PLAN_STEP_NUMBERING',
          `Step numbering breaks at position ${i + 1} (got Step ${numbers[i]})`,
          'Steps must be 1..N contiguous and ordered.',
        ));
        break;
      }
    }
  }

  return { valid: errors.length === 0, errors, warnings, parsed: { steps, forbidden } };
}
