// The result of <layout-foundry-quantities>: the answer, its working and assumptions, the confirm button's
// words, and the diagram with its caption. src/quantities.ts loads this file the first time the shopper
// starts on the form, so a product page where nobody uses the planner never loads it (Shopify suggests at
// most 10 KB of compressed script for a theme app extension).
//
// It brings its own English words (i18n/quantities/ui-result-en.ts) and the styles only a result needs.
// Otherwise it imports types only: code it shared with the element would go into a chunk of its own,
// which the element would then load at start, so the few helpers it needs are written here.

import type { QuantitiesCoreMessages } from "./i18n/quantities/core-en";
import { uiResult } from "./i18n/quantities/ui-result-en";
// The lf-quantity payload, which the element sends once a result shows.
export { buildQuantityDetail } from "./quantities-payload";
import type { Translator } from "./i18n/translate";
import type { FenceDiagram, QuantityResult, TileDiagram, WallpaperDiagram } from "./quantities-core";
import type { Measures } from "./quantities-measures";
import type { QuantitiesText } from "./quantities-ui-text";

const SVG_NS = "http://www.w3.org/2000/svg";

// The result's share of the element's styles (src/styles.ts, quantitiesStyles), added after them the
// first time a result shows: the lists, the small print, the status line kept for screen readers once
// the answer shows, the answer, the diagram and the assumptions with their fold arrow. test/styles.test.ts checks that each
// rule is a rule of baseStyles.
export const resultStyles = `ul{margin:0}
ul{padding-left:1.25em}
.small{font-size:0.9em}
.visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.answer{font-size:1.1em;font-weight:700}
.result{display:grid;gap:var(--lf-gap,16px);min-width:0}
.result>*{min-width:0}
@container (min-width:620px){.result{grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:start}.result.single{grid-template-columns:minmax(0,1fr)}}
figure{margin:0;display:grid;gap:4px}
svg{display:block;max-width:100%;height:auto;background:var(--lf-surface,#f4f4f4);border:1px solid var(--lf-border,#8a8a8a)}
details>summary{cursor:pointer;min-height:44px;display:flex;align-items:center;font-weight:600}
details>summary::-webkit-details-marker{display:none}
details>summary::before{content:"";flex:none;width:0.45em;height:0.45em;margin:0 0.75em 0 0.2em;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(-45deg)}
details[open]>summary::before{transform:rotate(45deg)}`;

// Each language's result words, which the element sends to its worker with each request (English is
// built into the worker). One line for each language but English in SUPPORTED_LANGUAGES
// (src/i18n/quantities/index.ts).
export const CORE: Readonly<Record<string, () => Promise<{ core: QuantitiesCoreMessages }>>> = {
  de: () => import("./i18n/quantities/core-de"),
  fr: () => import("./i18n/quantities/core-fr"),
  es: () => import("./i18n/quantities/core-es"),
  nl: () => import("./i18n/quantities/core-nl"),
  it: () => import("./i18n/quantities/core-it"),
};
const coreLoads = new Map<string, Promise<QuantitiesCoreMessages | null>>();

/**
 * The result words for `lang`, loaded once. Null for English, and when they cannot load (the worker then
 * answers in English, and the next call tries again).
 */
export function coreWords(lang: string): Promise<QuantitiesCoreMessages | null> {
  const load = CORE[lang];
  if (!load) return Promise.resolve(null);
  let words = coreLoads.get(lang);
  if (!words) {
    words = load().then(
      (file) => file.core,
      () => {
        coreLoads.delete(lang);
        return null;
      },
    );
    coreLoads.set(lang, words);
  }
  return words;
}

const styled = new WeakSet<ShadowRoot>();

function addStyles(root: ShadowRoot) {
  if (styled.has(root)) return;
  styled.add(root);
  try {
    const sheet = new CSSStyleSheet();
    sheet.replaceSync(resultStyles);
    root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];
  } catch {
    const style = document.createElement("style");
    style.textContent = resultStyles;
    root.append(style);
  }
}

/** Shows `result` in the planner's shadow root. `label` is the confirm button's label, or null for none. */
export function renderResult(root: ShadowRoot, result: QuantityResult, x: QuantitiesText, imperial: boolean, label: string | null) {
  addStyles(root);
  const words = x.resultWords(uiResult);
  const q = <T extends Element = HTMLElement>(selector: string) => root.querySelector(selector) as T;
  const item = (text: string) => {
    const li = document.createElement("li");
    li.textContent = text;
    return li;
  };
  q("[data-result]").hidden = false;
  q("[data-answer]").textContent = result.summary;
  q("[data-details]").replaceChildren(...result.details.map(item));
  q("[data-assumptions]").replaceChildren(...result.assumptions.map(item));
  if (label) {
    q("[data-action]").textContent = label.replace("{quantity}", String(result.quantity)).replace("{unit}", words.t(`unit.${result.unit}`, { count: result.quantity }));
  }

  const holder = q("[data-diagram]");
  const caption = q("[data-caption]");
  holder.replaceChildren();
  caption.textContent = "";
  q("figure").hidden = !result.diagram;
  q("[data-result]").classList.toggle("single", !result.diagram);
  if (result.diagram?.kind === "tiles") {
    holder.append(tileSvg(result.diagram, x, words, imperial));
    caption.textContent = `${words.t("caption.tiles")}${result.diagram.truncated ? ` ${words.t("caption.tilesPart")}` : ""}`;
  } else if (result.diagram?.kind === "wallpaper") {
    holder.append(wallSvg(result.diagram, words));
    caption.textContent = words.t("caption.wallpaper");
  } else if (result.diagram?.kind === "fence") {
    holder.append(fenceSvg(result.diagram, words));
    caption.textContent = `${words.t("caption.fence")}${result.diagram.truncated ? ` ${words.t("caption.fencePart")}` : ""}`;
  }
}

function svg(width: number, height: number, label: string) {
  const node = document.createElementNS(SVG_NS, "svg");
  node.setAttribute("viewBox", `0 0 ${width} ${height}`);
  node.setAttribute("role", "img");
  node.setAttribute("aria-label", label);
  node.setAttribute("preserveAspectRatio", "xMidYMid meet");
  return node;
}

function rect(x: number, y: number, w: number, h: number, fill: string, stroke: string, strokeWidth: number) {
  const r = document.createElementNS(SVG_NS, "rect");
  r.setAttribute("x", String(x));
  r.setAttribute("y", String(y));
  r.setAttribute("width", String(w));
  r.setAttribute("height", String(h));
  r.setAttribute("fill", fill);
  r.setAttribute("stroke", stroke);
  r.setAttribute("stroke-width", String(strokeWidth));
  r.setAttribute("vector-effect", "non-scaling-stroke");
  return r;
}

/** A whole number as the English text always wrote it (no grouping), or grouped in other languages. */
export const whole = (x: Measures, value: number) => (x.lang === "en" ? String(value) : x.tr.count(value));

/** A number with exactly `digits` decimals ("3.00"), with the language's decimal separator. */
export const fixed = (x: Measures, value: number, digits: number) => x.local(value.toFixed(digits));

function tileSvg(d: TileDiagram, x: QuantitiesText, words: Translator, imperial: boolean) {
  const u = imperial ? "ft" : "m";
  const size = (mm: number) => `${fixed(x, mm / (u === "m" ? 1000 : 304.8), 2)} ${u}`;
  const label = words.t("svg.tiles", {
    width: size(d.widthMm),
    length: size(d.lengthMm),
    count: whole(x, d.tiles.length),
    cut: whole(x, d.tiles.filter((t) => t.cut).length),
  });
  const node = svg(d.widthMm, d.lengthMm, label);
  const clip = document.createElementNS(SVG_NS, "clipPath");
  clip.id = "lf-room";
  clip.append(rect(0, 0, d.widthMm, d.lengthMm, "none", "none", 0));
  const group = document.createElementNS(SVG_NS, "g");
  group.setAttribute("clip-path", "url(#lf-room)");
  for (const t of d.tiles) group.append(rect(t.x, t.y, t.w, t.l, t.cut ? "var(--lf-diagram-cut, #c9d9e6)" : "var(--lf-diagram-piece, #ffffff)", "var(--lf-diagram-line, #505050)", 1));
  node.append(clip, group, rect(0, 0, d.widthMm, d.lengthMm, "none", "var(--lf-text, #1c1c1c)", 2));
  return node;
}

function wallSvg(d: WallpaperDiagram, words: Translator) {
  const gap = 200;
  const width = d.walls.reduce((n, w) => n + w.widthMm, 0) + gap * (d.walls.length - 1);
  const height = Math.max(...d.walls.map((w) => w.heightMm));
  const drops = d.walls.reduce((n, w) => n + w.drops.length, 0);
  const node = svg(width, height, words.t("svg.wallpaper", { walls: words.t("n.wall", { count: d.walls.length }), drops: words.t("n.drop", { count: drops }) }));
  let left = 0;
  for (const wall of d.walls) {
    for (const drop of wall.drops) {
      node.append(rect(left + drop.startMm, height - wall.heightMm, drop.endMm - drop.startMm, wall.heightMm, "var(--lf-diagram-piece, #ffffff)", "var(--lf-diagram-line, #505050)", 1));
    }
    node.append(rect(left, height - wall.heightMm, wall.widthMm, wall.heightMm, "none", "var(--lf-text, #1c1c1c)", 2));
    left += wall.widthMm + gap;
  }
  return node;
}

function fenceSvg(d: FenceDiagram, words: Translator) {
  const node = svg(d.runMm, d.heightMm, words.t("svg.fence", { panels: words.t("n.panel", { count: d.panels.length }), posts: words.t("n.post", { count: d.posts.length }) }));
  for (const p of d.panels) {
    node.append(rect(p.x, d.heightMm * 0.1, p.w, d.heightMm * 0.9, p.cut ? "var(--lf-diagram-cut, #c9d9e6)" : "var(--lf-diagram-piece, #ffffff)", "var(--lf-diagram-line, #505050)", 1));
  }
  for (const p of d.posts) node.append(rect(p.x, 0, p.w, d.heightMm, "var(--lf-diagram-line, #505050)", "var(--lf-text, #1c1c1c)", 1));
  return node;
}
