import { parse as parseYaml } from 'yaml';
import { Marked, type Tokens } from 'marked';
import DOMPurify from 'dompurify';

export type HelpCategory = 'basic' | 'advanced' | 'admin';
export const HELP_CATEGORIES: HelpCategory[] = ['basic', 'advanced', 'admin'];

export interface HelpFrontmatter {
  id: string;
  title: string;
  category: HelpCategory;
  order: number;
  keywords: string[];
}

export interface HelpSection extends HelpFrontmatter {
  body: string;
}

export interface HelpHeading {
  id: string;
  text: string;
  depth: number; // 2 or 3
}

/** Split a `---\n...\n---\n` frontmatter block from the body. Front block must be at the very start. */
export function splitFrontmatter(raw: string): { frontmatter: string; body: string } {
  const normalized = raw.replace(/^﻿/, '').replace(/\r\n/g, '\n');
  const match = /^---\n([\s\S]*?)\n---\n?/.exec(normalized);
  if (!match) return { frontmatter: '', body: normalized };
  return { frontmatter: match[1], body: normalized.slice(match[0].length) };
}

export interface ValidationResult {
  ok: boolean;
  errors: string[];
  value?: HelpFrontmatter;
}

/** Validate a parsed frontmatter object. Pure — used by both runtime loader and the build-time validator. */
export function validateFrontmatter(data: unknown, source: string): ValidationResult {
  const errors: string[] = [];
  const d = (data && typeof data === 'object') ? data as Record<string, unknown> : {};
  const id = d.id;
  const title = d.title;
  const category = d.category;
  const order = d.order;
  const keywords = d.keywords ?? [];

  if (typeof id !== 'string' || !id.trim()) errors.push(`${source}: 'id' is required (string)`);
  if (typeof title !== 'string' || !title.trim()) errors.push(`${source}: 'title' is required (string)`);
  if (typeof category !== 'string' || !HELP_CATEGORIES.includes(category as HelpCategory)) {
    errors.push(`${source}: 'category' must be one of ${HELP_CATEGORIES.join('|')}`);
  }
  if (typeof order !== 'number' || !Number.isFinite(order)) errors.push(`${source}: 'order' is required (number)`);
  if (!Array.isArray(keywords) || keywords.some((k) => typeof k !== 'string')) {
    errors.push(`${source}: 'keywords' must be a string array`);
  }

  if (errors.length) return { ok: false, errors };
  return {
    ok: true,
    errors: [],
    value: {
      id: id as string,
      title: title as string,
      category: category as HelpCategory,
      order: order as number,
      keywords: keywords as string[],
    },
  };
}

/** Parse one raw markdown doc into frontmatter object + body. Throws on YAML syntax error. */
export function parseHelpDoc(raw: string): { data: unknown; body: string } {
  const { frontmatter, body } = splitFrontmatter(raw);
  const data = frontmatter ? parseYaml(frontmatter) : {};
  return { data, body };
}

// KEEP IN SYNC with scripts/validate-help-docs.mjs slugify()
/** Lowercase, strip tags, keep unicode letters/numbers (incl. Japanese), collapse to hyphens. */
export function slugify(text: string): string {
  const s = text
    .replace(/<[^>]*>/g, '')
    .trim()
    .toLowerCase()
    .replace(/[^\p{L}\p{N}]+/gu, '-')
    .replace(/^-+|-+$/g, '');
  return s || 'section';
}

/** Returns a slugger that appends -2, -3, ... on collisions. */
export function makeSlugger(): (text: string) => string {
  const seen = new Map<string, number>();
  return (text: string) => {
    const base = slugify(text);
    const n = seen.get(base) ?? 0;
    seen.set(base, n + 1);
    return n === 0 ? base : `${base}-${n + 1}`;
  };
}

/**
 * Render markdown body to sanitized HTML with id'd headings, and return the h2/h3 outline.
 * Uses ONE slugger pass so heading ids match the returned outline ids exactly.
 */
export function renderHelpHtml(body: string): { html: string; headings: HelpHeading[] } {
  const slugger = makeSlugger();
  const headings: HelpHeading[] = [];
  const instance = new Marked({ breaks: true, gfm: true });
  instance.use({
    renderer: {
      heading(this: { parser: { parseInline: (t: Tokens.Generic[]) => string } }, token: Tokens.Heading) {
        const id = slugger(token.text);
        const inner = this.parser.parseInline(token.tokens);
        if (token.depth === 2 || token.depth === 3) {
          headings.push({ id, text: token.text, depth: token.depth });
        }
        return `<h${token.depth} id="${id}">${inner}</h${token.depth}>\n`;
      },
    },
  });
  const rawHtml = instance.parse(body, { async: false }) as string;
  const html = DOMPurify.sanitize(rawHtml, { ADD_ATTR: ['id', 'target', 'rel'] });
  return { html, headings };
}

/** Case-insensitive match against title + keywords + body. Empty query returns all. */
export function filterSections(sections: HelpSection[], query: string): HelpSection[] {
  const q = query.trim().toLowerCase();
  if (!q) return sections;
  return sections.filter((s) => {
    if (s.title.toLowerCase().includes(q)) return true;
    if (s.keywords.some((k) => k.toLowerCase().includes(q))) return true;
    return s.body.toLowerCase().includes(q);
  });
}
