/**
 * Golden fixtures for the EDM renderer.
 *
 * ── What this suite is, and is not ───────────────────────────────────────────
 *
 * IS:     a regression lock. Once a case passes, any future edit to
 *         edm-render.ts that changes the output fails loudly.
 *
 * IS NOT: proof of SendGrid parity. Every expectation here was written from
 *         SendGrid's *documented* behaviour, and documented is not observed.
 *         Nothing in this file has been diffed against real SendGrid output.
 *
 * That distinction matters because the whole risk of this migration is the gap
 * between the two. Establishing real parity needs `cmd/diff-edm-render.ts`,
 * which sends the same template through both paths to a live mailbox — this
 * suite cannot substitute for it, and a green run here should not be read as
 * "safe to switch production traffic over".
 *
 * Cases marked `provenance: "observed"` HAVE been confirmed against SendGrid.
 * Everything else is `"documented"`. Promote a case only after checking it for
 * real.
 */

import type { EDMTemplateObject } from "@/lib/storage/edm-storage";

export type RenderFixture = {
  name: string;
  provenance: "documented" | "observed";
  template: Partial<EDMTemplateObject> & { html: string };
  data: Record<string, unknown>;
  /** Exact expected html. */
  expectHtml?: string;
  /** Substrings that must appear. */
  contains?: string[];
  /** Substrings that must NOT appear — the silent-drop detector. */
  excludes?: string[];
  expectSubject?: string;
  expectThrows?: boolean;
  note?: string;
};

const t = (html: string, subject = ""): Partial<EDMTemplateObject> & { html: string } => ({
  html,
  subject,
  plain: "",
  generatePlain: false,
});

export const RENDER_FIXTURES: RenderFixture[] = [
  // ── basic substitution ────────────────────────────────────────────────────
  {
    name: "simple variable",
    provenance: "documented",
    template: t("<p>Hello {{full_name}}</p>"),
    data: { full_name: "Rohit" },
    expectHtml: "<p>Hello Rohit</p>",
  },
  {
    name: "missing variable renders empty, does not throw",
    provenance: "documented",
    template: t("<p>Hello {{full_name}}!</p>"),
    data: {},
    expectHtml: "<p>Hello !</p>",
    note: "SendGrid drops unknown merge fields rather than erroring.",
  },
  {
    name: "double-brace escapes html",
    provenance: "documented",
    template: t("<p>{{name}}</p>"),
    data: { name: '<script>alert("x")</script>' },
    excludes: ["<script>"],
    note: "Handlebars escapes & < > \" ' ` = — SendGrid's set may differ slightly.",
  },
  {
    name: "triple-brace emits raw html",
    provenance: "documented",
    template: t("<div>{{{block}}}</div>"),
    data: { block: "<b>bold</b>" },
    expectHtml: "<div><b>bold</b></div>",
  },
  {
    name: "dot notation",
    provenance: "documented",
    template: t("<p>{{member.first_name}} {{member.address.city}}</p>"),
    data: { member: { first_name: "Rohit", address: { city: "Goa" } } },
    expectHtml: "<p>Rohit Goa</p>",
  },

  // ── built-in blocks ───────────────────────────────────────────────────────
  {
    name: "#if truthy",
    provenance: "documented",
    template: t("{{#if is_member}}<p>Member</p>{{else}}<p>Guest</p>{{/if}}"),
    data: { is_member: true },
    expectHtml: "<p>Member</p>",
  },
  {
    name: "#if falsy takes else",
    provenance: "documented",
    template: t("{{#if is_member}}<p>Member</p>{{else}}<p>Guest</p>{{/if}}"),
    data: { is_member: false },
    expectHtml: "<p>Guest</p>",
  },
  {
    name: "#unless",
    provenance: "documented",
    template: t("{{#unless dormant}}<p>Active</p>{{/unless}}"),
    data: { dormant: false },
    expectHtml: "<p>Active</p>",
  },
  {
    name: "#each with @index and this",
    provenance: "documented",
    template: t("{{#each nights}}<li>{{@index}}:{{this}}</li>{{/each}}"),
    data: { nights: ["Mon", "Tue"] },
    expectHtml: "<li>0:Mon</li><li>1:Tue</li>",
  },
  {
    name: "#each over objects",
    provenance: "documented",
    template: t("{{#each resorts}}<li>{{name}}</li>{{/each}}"),
    data: { resorts: [{ name: "Sitabani" }, { name: "Haathi Mahal" }] },
    expectHtml: "<li>Sitabani</li><li>Haathi Mahal</li>",
  },
  {
    name: "#each over empty list renders nothing",
    provenance: "documented",
    template: t("<ul>{{#each nights}}<li>{{this}}</li>{{/each}}</ul>"),
    data: { nights: [] },
    expectHtml: "<ul></ul>",
  },
  {
    name: "#with",
    provenance: "documented",
    template: t("{{#with booking}}<p>{{resort}}</p>{{/with}}"),
    data: { booking: { resort: "Karma Sitabani" } },
    expectHtml: "<p>Karma Sitabani</p>",
  },

  // ── SendGrid-specific helpers ─────────────────────────────────────────────
  // These are the reason this file exists. Losing one of these shims does not
  // silently blank a section — because they all take arguments, Handlebars
  // throws Missing helper and the send fails loudly. What these cases actually
  // guard is the subtler kind of regression: a shim that still exists but
  // compares differently (see the loose-equality and coercion cases below).
  {
    name: "#equals matches",
    provenance: "documented",
    template: t("{{#equals brand 'KCI'}}<p>India</p>{{/equals}}"),
    data: { brand: "KCI" },
    expectHtml: "<p>India</p>",
  },
  {
    name: "#equals is loose — string 1 equals number 1",
    provenance: "documented",
    template: t("{{#equals count 1}}<p>one</p>{{/equals}}"),
    data: { count: "1" },
    expectHtml: "<p>one</p>",
    note: "SendGrid compares loosely; a strict === here would drop the block.",
  },
  {
    name: "#notEquals",
    provenance: "documented",
    template: t("{{#notEquals brand 'KCB'}}<p>not bali</p>{{/notEquals}}"),
    data: { brand: "KCI" },
    expectHtml: "<p>not bali</p>",
  },
  {
    name: "#greaterThan",
    provenance: "documented",
    template: t("{{#greaterThan points 100}}<p>lots</p>{{else}}<p>few</p>{{/greaterThan}}"),
    data: { points: 250 },
    expectHtml: "<p>lots</p>",
  },
  {
    name: "#greaterThan coerces numeric strings",
    provenance: "documented",
    template: t("{{#greaterThan points 100}}<p>lots</p>{{else}}<p>few</p>{{/greaterThan}}"),
    data: { points: "250" },
    expectHtml: "<p>lots</p>",
    note: "Template data arriving from JSON is routinely stringified.",
  },
  {
    name: "#lessThan takes else",
    provenance: "documented",
    template: t("{{#lessThan points 100}}<p>few</p>{{else}}<p>lots</p>{{/lessThan}}"),
    data: { points: 500 },
    expectHtml: "<p>lots</p>",
  },
  {
    name: "#and",
    provenance: "documented",
    template: t("{{#and a b}}<p>both</p>{{else}}<p>not both</p>{{/and}}"),
    data: { a: true, b: false },
    expectHtml: "<p>not both</p>",
  },
  {
    name: "#or",
    provenance: "documented",
    template: t("{{#or a b}}<p>either</p>{{/or}}"),
    data: { a: false, b: true },
    expectHtml: "<p>either</p>",
  },
  {
    name: "insert with value present",
    provenance: "documented",
    template: t("<p>{{insert first_name 'there'}}</p>"),
    data: { first_name: "Rohit" },
    expectHtml: "<p>Rohit</p>",
  },
  {
    name: "insert falls back when missing",
    provenance: "documented",
    template: t("<p>Hi {{insert first_name 'there'}}</p>"),
    data: {},
    expectHtml: "<p>Hi there</p>",
  },
  {
    name: "insert falls back on empty string",
    provenance: "documented",
    template: t("<p>Hi {{insert first_name 'there'}}</p>"),
    data: { first_name: "" },
    expectHtml: "<p>Hi there</p>",
  },

  // ── subject ───────────────────────────────────────────────────────────────
  {
    name: "subject is rendered, not passed through",
    provenance: "documented",
    template: t("<p>body</p>", "Welcome, {{full_name}}"),
    data: { full_name: "Rohit" },
    expectSubject: "Welcome, Rohit",
    note: "All 46 publish() call sites pass no subject, so this is the only source.",
  },
  {
    name: "subject supports conditionals",
    provenance: "documented",
    template: t("<p>body</p>", "{{#if vip}}VIP: {{/if}}Your booking"),
    data: { vip: true },
    expectSubject: "VIP: Your booking",
  },

  // ── email-html structural preservation ────────────────────────────────────
  {
    name: "Outlook conditional comments survive rendering",
    provenance: "documented",
    template: t(
      "<!--[if gte mso 9]><v:rect><v:fill src='x.png'/></v:rect><![endif]--><p>{{n}}</p>",
    ),
    data: { n: "hi" },
    contains: ["<!--[if gte mso 9]>", "<![endif]-->", "<v:fill", "<p>hi</p>"],
  },
  {
    name: "unsubscribe tags pass through untouched",
    provenance: "documented",
    template: t("<a href='<%asm_group_unsubscribe_raw_url%>'>Unsubscribe</a>"),
    data: {},
    contains: ["<%asm_group_unsubscribe_raw_url%>"],
    note: "SendGrid substitutes these on the message body, so they must survive us.",
  },
  {
    name: "css in a style block is not mangled",
    provenance: "documented",
    template: t("<style>.a{color:#fff;background:url(x.png)}</style><p>{{n}}</p>"),
    data: { n: "hi" },
    contains: [".a{color:#fff;background:url(x.png)}"],
  },
];

/**
 * Cases that document the failure mode findUnknownBlockHelpers exists to catch.
 * Deliberately separate: these assert on the DETECTOR, not on rendered output,
 * because the whole problem is that the rendered output looks fine.
 */
export const UNKNOWN_HELPER_FIXTURES: {
  name: string;
  html: string;
  expectNames: string[];
  expectAbsent?: string[];
}[] = [
  {
    name: "unshimmed helper with arguments is flagged",
    html: "{{#formatDate start_date 'YYYY'}}{{/formatDate}}",
    expectNames: ["formatDate"],
  },
  {
    name: "known helpers are not flagged",
    html: "{{#if a}}x{{/if}}{{#each b}}y{{/each}}{{#greaterThan c 1}}z{{/greaterThan}}",
    expectNames: [],
  },
  {
    name: "plain context section is reported but marked argument-free",
    html: "{{#has_points}}<p>x</p>{{/has_points}}",
    expectNames: ["has_points"],
    expectAbsent: ["if"],
  },
];
