// configuration.md as list + inspector. its tables become one entry per setting at build
// time, so the html has every setting written out (ctrl-f, no js, search all work) and the
// script only picks which entry shows. a table that doesn't look like a settings table
// fails the build instead of quietly losing settings.

import type { Root, Element, ElementContent, RootContent } from "hast";
import { toString } from "hast-util-to-string";

export interface Setting {
  key: string;
  /** section heading id */
  section: string;
  defaultText: string;
  firstLine: string;
}

export interface SettingsSection {
  id: string;
  title: string;
  settings: Setting[];
}

const HEADER = ["variable", "default", "description"];

const el = (tagName: string, className: string | null, children: ElementContent[], props: Record<string, string> = {}): Element => ({
  type: "element",
  tagName,
  properties: { ...(className ? { className: [className] } : {}), ...props },
  children,
});
const text = (value: string): ElementContent => ({ type: "text", value });
const isEl = (n: unknown, tag: string): n is Element => (n as Element)?.type === "element" && (n as Element).tagName === tag;
const kids = (n: Element, tag: string) => n.children.filter((c): c is Element => isEl(c, tag));

function rows(table: Element): Element[][] {
  return [...kids(table, "thead"), ...kids(table, "tbody")].flatMap((part) => kids(part, "tr").map((tr) => kids(tr, "th").concat(kids(tr, "td"))));
}

const isSettingsTable = (table: Element) => {
  const head = rows(table)[0];
  return head?.length === 3 && head.every((c, i) => toString(c).trim().toLowerCase() === HEADER[i]);
};

export function buildSettings(tree: Root, docId: string): SettingsSection[] {
  const sections: SettingsSection[] = [];
  const intro: RootContent[] = [];
  const out: RootContent[] = [];
  const seen = new Set<string>();
  let current: { h2: Element; body: RootContent[]; entries: Element[]; s: SettingsSection } | null = null;

  const flush = () => {
    if (!current) return;
    const { h2, body, entries, s } = current;
    const summary = s.settings.length
      ? [el("ul", "cfg-sum", s.settings.map((st) => el("li", null, [el("a", null, [
          el("code", null, [text(st.key)]),
          el("span", "d", [text(st.defaultText)]),
          el("span", "first", [text(st.firstLine)]),
        ], { href: `#${st.key}` })])))]
      : [];
    out.push(el("section", "cfg-item cfg-sec", [h2 as ElementContent, ...(body as ElementContent[]), ...summary], { "data-item": s.id }));
    out.push(...entries);
    current = null;
  };

  for (const node of tree.children) {
    if (isEl(node, "h2")) {
      flush();
      const id = String(node.properties.id);
      const s = { id, title: toString(node), settings: [] };
      sections.push(s);
      current = { h2: node, body: [], entries: [], s };
      continue;
    }
    if (!current) {
      intro.push(node);
      continue;
    }
    if (!isEl(node, "table") || !isSettingsTable(node)) {
      if (isEl(node, "table")) throw new Error(`${docId}.md: a table in "${current.s.title}" isn't a variable | default | description table`);
      current.body.push(node);
      continue;
    }
    for (const [name, dflt, desc] of rows(node).slice(1)) {
      const code = name?.children.filter((c) => !(c.type === "text" && !c.value.trim()));
      if (!code || code.length !== 1 || !isEl(code[0], "code") || !desc) {
        throw new Error(`${docId}.md: a row in "${current.s.title}" doesn't start with one \`VARIABLE\` cell: ${name ? toString(name) : "(empty)"}`);
      }
      const key = toString(code[0]).trim();
      if (seen.has(key)) throw new Error(`${docId}.md lists ${key} twice`);
      seen.add(key);
      const st: Setting = { key, section: current.s.id, defaultText: toString(dflt).trim(), firstLine: toString(desc).trim() };
      current.s.settings.push(st);
      current.entries.push(el("article", "cfg-item cfg-var", [
        el("h3", "cfg-name", [el("code", null, [text(key)])], { id: key }),
        ...(st.defaultText ? [el("p", "cfg-default", [text("default "), ...dflt.children])] : []),
        el("div", "cfg-desc", desc.children),
      ], { "data-item": key, "data-sec": current.s.id }));
    }
  }
  flush();

  tree.children = [el("div", "cfg", [
    el("section", "cfg-item cfg-intro", intro as ElementContent[], { "data-item": "" }),
    ...(out as ElementContent[]),
  ], { "data-cfg": "" })];
  return sections;
}
