import Handlebars from 'handlebars';

/** The raw, unrendered strings of a template (from the DB row or an inline source). */
export interface RawTemplate {
  subject?: string | null;
  bodyHtml?: string | null;
  bodyText?: string | null;
  /** Declared variable names. Every one MUST be supplied at render time (fail loud). */
  variables?: string[] | null;
}

export interface RenderedContent {
  subject?: string;
  html?: string;
  text?: string;
}

/** A declared variable was not supplied — we refuse to send a half-rendered "Hi ," message. */
export class MissingTemplateVariableError extends Error {
  constructor(public readonly missing: string[]) {
    super(`missing required template variable(s): ${missing.join(', ')}`);
    this.name = 'MissingTemplateVariableError';
  }
}

/**
 * Pure render: interpolate `vars` into a template's strings. HTML output is auto-escaped (values
 * like a customer name go into an email body — XSS risk); subject and plaintext are NOT escaped.
 * No I/O — the caller supplies the raw template. Throws if any declared variable is absent.
 */
export function renderTemplate(tpl: RawTemplate, vars: Record<string, unknown>): RenderedContent {
  const declared = tpl.variables ?? [];
  const missing = declared.filter((name) => vars[name] === undefined || vars[name] === null);
  if (missing.length > 0) throw new MissingTemplateVariableError(missing);

  const out: RenderedContent = {};
  if (tpl.subject != null) out.subject = compile(tpl.subject, false)(vars);
  if (tpl.bodyHtml != null) out.html = compile(tpl.bodyHtml, true)(vars);
  if (tpl.bodyText != null) out.text = compile(tpl.bodyText, false)(vars);
  return out;
}

/** `escape=true` → HTML-escape interpolated values (for the html body); false → verbatim. */
function compile(src: string, escape: boolean): Handlebars.TemplateDelegate {
  return Handlebars.compile(src, { noEscape: !escape, strict: false });
}
