import { linter, type Diagnostic } from '@codemirror/lint';
import type { AlertVariable } from './alert-variables';

/** Lints a Mustache template for (a) unclosed `{{`, (b) references to out-of-scope variables.
 *  Unknown refs become amber warnings; unclosed `{{` becomes a red error. */
export function mustacheLinter(allowed: readonly AlertVariable[]) {
  return linter((view) => {
    const diags: Diagnostic[] = [];
    const text = view.state.doc.toString();

    // 1. Unclosed / unmatched braces.
    // A single `{{` without a matching `}}` before end-of-doc is an error.
    let i = 0;
    while (i < text.length) {
      const open = text.indexOf('{{', i);
      if (open === -1) break;
      const close = text.indexOf('}}', open + 2);
      if (close === -1) {
        diags.push({
          from: open,
          to: text.length,
          severity: 'error',
          message: 'Unclosed Mustache tag `{{` — add `}}` to close.',
        });
        break;
      }
      i = close + 2;
    }

    // 2. Stray `}}` with no preceding `{{` on the same token stream.
    // Approximation: count opens/closes; if doc ends with more closes than opens, flag last.
    const openCount = (text.match(/\{\{/g) ?? []).length;
    const closeCount = (text.match(/\}\}/g) ?? []).length;
    if (closeCount > openCount) {
      const lastClose = text.lastIndexOf('}}');
      diags.push({
        from: lastClose,
        to: lastClose + 2,
        severity: 'error',
        message: 'Unmatched `}}` — no opening `{{` for this close.',
      });
    }

    // 3. Unknown variable references (amber warning).
    const allowedSet = new Set(allowed.map((v) => v.path));
    const refRe = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_.]*)\s*\}\}/g;
    let m: RegExpExecArray | null;
    while ((m = refRe.exec(text)) !== null) {
      const ref = m[1];
      if (!allowedSet.has(ref)) {
        diags.push({
          from: m.index,
          to: m.index + m[0].length,
          severity: 'warning',
          message: `\`${ref}\` is not available for this rule kind — will render as literal.`,
        });
      }
    }

    return diags;
  });
}
