// lib/util/frontmatter.mjs
// Hand-rolled YAML-frontmatter parser.
//
// Supported subset:
//   - String scalars (quoted or unquoted)
//   - Numbers (integer + float)
//   - Booleans (true / false)
//   - null
//   - Single-level dicts
//   - Lists of scalars (- value)
//
// Deliberately rejects: nested dicts in lists, multi-line strings,
// anchors/aliases, tags, flow style ({...} / [...]).
//
// Why no js-yaml: zero-deps invariant. Templates emit only this subset.

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

const FRONTMATTER_RE = /^﻿?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/;

/**
 * Split raw markdown into { frontmatter, body }.
 * Returns { hasFrontmatter: false } when no leading --- block exists.
 */
export function splitFrontmatter(text) {
  if (typeof text !== 'string') return { hasFrontmatter: false, body: '' };
  const stripped = text.replace(/^﻿/, '');
  const m = stripped.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/);
  if (!m) return { hasFrontmatter: false, body: stripped };
  return {
    hasFrontmatter: true,
    frontmatter: m[1],
    body: m[2] || '',
  };
}

/**
 * Parse a YAML-frontmatter string into a JS object.
 * @returns {import('./result.mjs').Result}
 */
export function parseFrontmatter(yamlText) {
  if (typeof yamlText !== 'string') {
    return fail(issue('FM_INPUT', 'Frontmatter input is not a string'));
  }
  const lines = yamlText.split(/\r?\n/);
  const out = {};
  const errors = [];

  let i = 0;
  while (i < lines.length) {
    const line = lines[i];

    if (line.trim() === '' || line.trimStart().startsWith('#')) {
      i++;
      continue;
    }

    const indentMatch = line.match(/^(\s*)/);
    const indent = indentMatch ? indentMatch[0].length : 0;
    if (indent > 0) {
      errors.push(issue('FM_INDENT', `Unexpected indentation at line ${i + 1}`, 'Top-level keys only; nested dicts unsupported.'));
      i++;
      continue;
    }

    const kv = line.match(/^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/);
    if (!kv) {
      errors.push(issue('FM_SYNTAX', `Cannot parse line ${i + 1}: ${line}`));
      i++;
      continue;
    }

    const key = kv[1];
    const rest = kv[2];

    if (rest === '' || rest === undefined) {
      const list = [];
      let j = i + 1;
      while (j < lines.length) {
        const next = lines[j];
        if (next.trim() === '') { j++; continue; }
        const itemMatch = next.match(/^(\s+)-\s+(.*)$/);
        if (!itemMatch) break;
        const itemIndent = itemMatch[1].length;
        const firstContent = itemMatch[2];
        const dictKeyMatch = firstContent.match(/^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/);
        if (dictKeyMatch) {
          const item = {};
          item[dictKeyMatch[1]] = parseScalar(dictKeyMatch[2]);
          let k = j + 1;
          while (k < lines.length) {
            const cont = lines[k];
            if (cont.trim() === '') { k++; continue; }
            const contMatch = cont.match(/^(\s+)([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/);
            if (!contMatch) break;
            if (contMatch[1].length <= itemIndent + 1) break;
            item[contMatch[2]] = parseScalar(contMatch[3]);
            k++;
          }
          list.push(item);
          j = k;
        } else {
          list.push(parseScalar(firstContent));
          j++;
        }
      }
      if (list.length > 0) {
        out[key] = list;
        i = j;
      } else {
        out[key] = null;
        i++;
      }
      continue;
    }

    out[key] = parseScalar(rest);
    i++;
  }

  if (errors.length > 0) return { valid: false, errors, warnings: [], parsed: out };
  return ok(out);
}

function parseScalar(raw) {
  const s = raw.trim();
  if (s === '') return '';
  if (s === 'null' || s === '~') return null;
  if (s === 'true') return true;
  if (s === 'false') return false;
  if (s === '[]') return [];
  if (s === '{}') return {};
  if (/^-?\d+$/.test(s)) return Number.parseInt(s, 10);
  if (/^-?\d+\.\d+$/.test(s)) return Number.parseFloat(s);
  if (s.startsWith('"') && s.endsWith('"')) {
    return s.slice(1, -1).replace(/\\(.)/g, (_, ch) => {
      if (ch === 'n') return '\n';
      if (ch === 't') return '\t';
      if (ch === 'r') return '\r';
      return ch;
    });
  }
  if (s.startsWith("'") && s.endsWith("'")) return s.slice(1, -1);
  return s;
}

/**
 * Parse a markdown file's frontmatter directly from its full text.
 * @returns {import('./result.mjs').Result}
 */
export function parseDocument(text) {
  const split = splitFrontmatter(text);
  if (!split.hasFrontmatter) {
    return fail(issue('FM_MISSING', 'No frontmatter block found'));
  }
  const result = parseFrontmatter(split.frontmatter);
  return { ...result, parsed: { frontmatter: result.parsed, body: split.body } };
}
