/**
 * scripts/generate-docs/lib/handlebars-setup.ts
 *
 * Registers every partial under `templates/partials/` and every helper the
 * page templates need. Single entry point: `getHandlebars()`.
 */
import Handlebars from 'handlebars';
import { readFileSync, readdirSync } from 'node:fs';
import { join, basename } from 'node:path';
import type { Skill } from './skill-parser';
import type { LocalisedString } from './markdown-parser';

const TEMPLATES_DIR = join(__dirname, '..', 'templates');
const PARTIALS_DIR = join(TEMPLATES_DIR, 'partials');

let prepared = false;

function isLocalised(value: unknown): value is { fr: string; en: string } {
  return (
    typeof value === 'object' &&
    value !== null &&
    'fr' in value &&
    'en' in value
  );
}

function localisedValue(value: LocalisedString | undefined, lang: 'fr' | 'en'): string {
  if (value == null) return '';
  if (typeof value === 'string') return value;
  return value[lang] ?? value.fr ?? '';
}

function registerHelpers(): void {
  Handlebars.registerHelper('eq', (a: unknown, b: unknown) => a === b);

  Handlebars.registerHelper('i18n', (value: LocalisedString | undefined, lang: 'fr' | 'en') =>
    localisedValue(value, lang ?? 'fr')
  );

  // Emit either a plain text node (if value is a string) or two
  // `<span data-lang>` nodes (if value is a localised object). An optional
  // second argument is a CSS class added to every emitted <span>.
  //
  // Generated DOM examples:
  //   - value = "Hello", extraClass omitted  → `Hello`
  //   - value = "Hello", extraClass = "x"    → `<span class="x">Hello</span>`
  //   - value = {fr, en}, extraClass omitted → `<span data-lang="fr">…</span><span data-lang="en">…</span>`
  //   - value = {fr, en}, extraClass = "x"   → `<span class="x" data-lang="fr">…</span><span class="x" data-lang="en">…</span>`
  //
  // The `app.js` runtime hides the inactive language by toggling
  // `[data-lang="<inactive>"] { display: none }`. Both spans must therefore
  // remain valid HTML attributes — earlier implementations used `replace()`
  // on the class string and produced broken `<span class= data-lang="fr"x">`
  // markup that the browser rendered as two visible items side by side.
  Handlebars.registerHelper(
    'i18nSpans',
    function (value: LocalisedString | undefined, extraClass?: unknown) {
      const klass = typeof extraClass === 'string' ? extraClass.trim() : '';
      const classAttr = klass ? ` class="${klass}"` : '';

      if (value == null) return new Handlebars.SafeString('');

      if (typeof value === 'string') {
        const text = Handlebars.escapeExpression(value);
        return new Handlebars.SafeString(
          klass ? `<span${classAttr}>${text}</span>` : text
        );
      }

      if (isLocalised(value)) {
        const fr = Handlebars.escapeExpression(value.fr);
        const en = Handlebars.escapeExpression(value.en);
        return new Handlebars.SafeString(
          `<span${classAttr} data-lang="fr">${fr}</span><span${classAttr} data-lang="en">${en}</span>`
        );
      }

      return new Handlebars.SafeString(Handlebars.escapeExpression(String(value)));
    }
  );

  Handlebars.registerHelper('firstSentence', (text: string) => {
    if (!text) return '';
    const m = text.match(/^(.*?[.!?])\s/);
    return m ? m[1] : text.length > 160 ? text.slice(0, 157) + '…' : text;
  });

  Handlebars.registerHelper('slugify', (text: string) =>
    String(text ?? '')
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/(^-|-$)/g, '')
  );

  Handlebars.registerHelper('count', (arr: unknown) =>
    Array.isArray(arr) ? arr.length : 0
  );

  // Group an array of skills by their `phase` field and yield each group as
  // an object the template can iterate over predictably.
  Handlebars.registerHelper('groupSkillsByPhase', function (skills: Skill[], options: Handlebars.HelperOptions) {
    if (!Array.isArray(skills)) return '';
    const groups = new Map<string, Skill[]>();
    for (const s of skills) {
      const arr = groups.get(s.phase) ?? [];
      arr.push(s);
      groups.set(s.phase, arr);
    }
    const sorted = Array.from(groups.entries()).sort(([a], [b]) => a.localeCompare(b));
    let out = '';
    for (const [phase, list] of sorted) {
      out += options.fn({ phase, skills: list });
    }
    return out;
  });

  // Stringify for debugging — `{{json someValue}}`
  Handlebars.registerHelper('json', (value: unknown) =>
    new Handlebars.SafeString(JSON.stringify(value, null, 2))
  );
}

function registerPartials(): void {
  const entries = readdirSync(PARTIALS_DIR);
  for (const file of entries) {
    if (!file.endsWith('.hbs')) continue;
    const name = basename(file, '.hbs');
    const src = readFileSync(join(PARTIALS_DIR, file), 'utf-8');
    Handlebars.registerPartial(name, src);
  }
}

export function getHandlebars(): typeof Handlebars {
  if (!prepared) {
    registerHelpers();
    registerPartials();
    prepared = true;
  }
  return Handlebars;
}

/** Compile and cache a single page template by name (no `.hbs` extension). */
const pageCache = new Map<string, HandlebarsTemplateDelegate>();
export function loadPageTemplate(name: string): HandlebarsTemplateDelegate {
  let cached = pageCache.get(name);
  if (cached) return cached;
  const file = join(TEMPLATES_DIR, 'pages', `${name}.hbs`);
  const src = readFileSync(file, 'utf-8');
  cached = getHandlebars().compile(src, { noEscape: false });
  pageCache.set(name, cached);
  return cached;
}

/** Compile the outer layout once and cache it. */
let layoutCache: HandlebarsTemplateDelegate | null = null;
export function loadLayoutTemplate(): HandlebarsTemplateDelegate {
  if (layoutCache) return layoutCache;
  const file = join(TEMPLATES_DIR, 'layout.hbs');
  const src = readFileSync(file, 'utf-8');
  layoutCache = getHandlebars().compile(src, { noEscape: false });
  return layoutCache;
}
