/* ATLAS — in-chat charts.
   The AI returns a `render_chart` client tool whose input is a CHART SPEC (validated JSON).
   This module renders that spec with D3 into an SVG inside a chat bubble, with a
   "Download PNG" button (html-to-image). The same render path is reused off-screen by the
   report generator (assets/agent-reports.jsx) to rasterise charts for PPTX/DOCX.

   Chart spec shape:
     { type: "bar" | "grouped-bar" | "line" | "scatter",
       title: string, xLabel?: string, yLabel?: string, unit?: string,
       categories: [string],                         // x labels (bar / grouped-bar / line)
       series: [ { name: string, values: [number] } ],
       footnote?: string }

   Exposes: window.AtlasChartBlock (React component), window.AtlasChartSpec.validate,
   window.AtlasChartSpec.renderInto (imperative D3 render used by both paths),
   window.AtlasChartPNG(spec) -> Promise<dataURL>. */
(function () {
  "use strict";
  const { useRef, useEffect, useState } = React;
  const h = React.createElement;

  const PALETTE = ["#074D47", "#22867C", "#89E4DA", "#B9783F", "#6B7280", "#155E55", "#C2A878"];
  const TYPES = ["bar", "grouped-bar", "line", "scatter"];

  // ---- spec validation: never render a malformed spec ----
  function validate(spec) {
    if (!spec || typeof spec !== "object") return { ok: false, error: "Chart spec missing." };
    if (TYPES.indexOf(spec.type) === -1) return { ok: false, error: "Unknown chart type: " + spec.type };
    const series = Array.isArray(spec.series) ? spec.series : [];
    if (!series.length) return { ok: false, error: "Chart has no series." };
    for (const s of series) {
      if (!s || !Array.isArray(s.values) || !s.values.length) return { ok: false, error: "A series has no values." };
      if (s.values.some((v) => typeof v !== "number" || !isFinite(v))) return { ok: false, error: "A series has non-numeric values." };
    }
    const cats = Array.isArray(spec.categories) ? spec.categories : [];
    if (spec.type !== "scatter" && !cats.length) return { ok: false, error: "Chart has no categories." };
    return { ok: true };
  }

  // ---- imperative D3 render into a provided SVG element ----
  function renderInto(svgEl, spec, opts) {
    const d3 = window.d3;
    const o = opts || {};
    const width = o.width || 520, height = o.height || 300;
    const m = { top: 34, right: 16, bottom: 52, left: 56 };
    const iw = width - m.left - m.right, ih = height - m.top - m.bottom;

    const svg = d3.select(svgEl)
      .attr("viewBox", "0 0 " + width + " " + height)
      .attr("width", width).attr("height", height)
      .style("background", "#fff").style("font-family", "'IBM Plex Sans', system-ui, sans-serif");
    svg.selectAll("*").remove();

    // title
    svg.append("text").attr("x", m.left).attr("y", 18)
      .attr("font-size", 13).attr("font-weight", 600).attr("fill", "#074D47")
      .text(spec.title || "");

    const g = svg.append("g").attr("transform", "translate(" + m.left + "," + m.top + ")");
    const series = spec.series, cats = spec.categories || [];

    const drawAxes = (x, y, isBand) => {
      const xAxis = isBand ? d3.axisBottom(x) : d3.axisBottom(x).ticks(6);
      g.append("g").attr("transform", "translate(0," + ih + ")").call(xAxis)
        .selectAll("text").attr("font-size", 10).attr("fill", "#4B5563")
        .attr("transform", isBand && cats.length > 4 ? "rotate(-28)" : null)
        .style("text-anchor", isBand && cats.length > 4 ? "end" : "middle");
      g.append("g").call(d3.axisLeft(y).ticks(5)).selectAll("text").attr("font-size", 10).attr("fill", "#4B5563");
      if (spec.yLabel || spec.unit) {
        svg.append("text").attr("transform", "rotate(-90)").attr("x", -(m.top + ih / 2)).attr("y", 14)
          .attr("font-size", 10).attr("fill", "#6B7280").attr("text-anchor", "middle")
          .text(spec.yLabel || spec.unit);
      }
    };

    const allVals = series.reduce((a, s) => a.concat(s.values), []);
    const yMax = Math.max(0, d3.max(allVals));
    const yMin = Math.min(0, d3.min(allVals));

    if (spec.type === "bar" || spec.type === "grouped-bar") {
      const x0 = d3.scaleBand().domain(cats).range([0, iw]).padding(0.22);
      const y = d3.scaleLinear().domain([yMin, yMax * 1.08 || 1]).nice().range([ih, 0]);
      drawAxes(x0, y, true);
      g.append("line").attr("x1", 0).attr("x2", iw).attr("y1", y(0)).attr("y2", y(0)).attr("stroke", "#E5E7EB");
      if (spec.type === "bar") {
        g.selectAll("rect").data(cats.map((c, i) => ({ c, v: series[0].values[i] }))).join("rect")
          .attr("x", (d) => x0(d.c)).attr("width", x0.bandwidth())
          .attr("y", (d) => y(Math.max(0, d.v))).attr("height", (d) => Math.abs(y(d.v) - y(0)))
          .attr("fill", PALETTE[0]);
      } else {
        const x1 = d3.scaleBand().domain(series.map((s) => s.name)).range([0, x0.bandwidth()]).padding(0.08);
        cats.forEach((c, ci) => {
          g.append("g").attr("transform", "translate(" + x0(c) + ",0)")
            .selectAll("rect").data(series).join("rect")
            .attr("x", (s) => x1(s.name)).attr("width", x1.bandwidth())
            .attr("y", (s) => y(Math.max(0, s.values[ci]))).attr("height", (s) => Math.abs(y(s.values[ci]) - y(0)))
            .attr("fill", (s, si) => PALETTE[si % PALETTE.length]);
        });
      }
    } else if (spec.type === "line") {
      const x = d3.scalePoint().domain(cats).range([0, iw]).padding(0.5);
      const y = d3.scaleLinear().domain([yMin, yMax * 1.08 || 1]).nice().range([ih, 0]);
      drawAxes(x, y, true);
      series.forEach((s, si) => {
        const line = d3.line().x((v, i) => x(cats[i])).y((v) => y(v));
        g.append("path").datum(s.values).attr("fill", "none")
          .attr("stroke", PALETTE[si % PALETTE.length]).attr("stroke-width", 2).attr("d", line);
        g.selectAll(".pt" + si).data(s.values).join("circle")
          .attr("cx", (v, i) => x(cats[i])).attr("cy", (v) => y(v)).attr("r", 3)
          .attr("fill", PALETTE[si % PALETTE.length]);
      });
    } else if (spec.type === "scatter") {
      const xs = series[0].values, ys = (series[1] || series[0]).values;
      const x = d3.scaleLinear().domain([d3.min(xs), d3.max(xs)]).nice().range([0, iw]);
      const y = d3.scaleLinear().domain([d3.min(ys), d3.max(ys)]).nice().range([ih, 0]);
      drawAxes(x, y, false);
      g.selectAll("circle").data(xs.map((v, i) => [v, ys[i]])).join("circle")
        .attr("cx", (d) => x(d[0])).attr("cy", (d) => y(d[1])).attr("r", 4).attr("fill", PALETTE[1]);
    }

    // legend (grouped-bar / multi-line)
    if ((spec.type === "grouped-bar" || spec.type === "line") && series.length > 1) {
      const lg = svg.append("g").attr("transform", "translate(" + m.left + "," + (height - 14) + ")");
      let ox = 0;
      series.forEach((s, si) => {
        const item = lg.append("g").attr("transform", "translate(" + ox + ",0)");
        item.append("rect").attr("width", 10).attr("height", 10).attr("y", -9).attr("fill", PALETTE[si % PALETTE.length]);
        const t = item.append("text").attr("x", 14).attr("font-size", 10).attr("fill", "#4B5563").text(s.name);
        ox += 24 + (s.name.length * 6);
        void t;
      });
    }
    return svg;
  }

  // ---- off-screen rasterise to PNG dataURL (used by report generator) ----
  function toPNG(spec, opts) {
    return new Promise((resolve, reject) => {
      const v = validate(spec);
      if (!v.ok) return reject(new Error(v.error));
      const host = document.createElement("div");
      host.style.cssText = "position:fixed;left:-9999px;top:0;background:#fff;";
      const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      host.appendChild(svg);
      document.body.appendChild(host);
      try { renderInto(svg, spec, opts); } catch (e) { document.body.removeChild(host); return reject(e); }
      const w = (opts && opts.width) || 520, ht = (opts && opts.height) || 300;
      window.htmlToImage.toPng(host, { pixelRatio: 2, width: w, height: ht, backgroundColor: "#fff" })
        .then((url) => { document.body.removeChild(host); resolve(url); })
        .catch((e) => { document.body.removeChild(host); reject(e); });
    });
  }

  function downloadDataUrl(url, name) {
    const a = document.createElement("a");
    a.href = url; a.download = name; a.click();
  }

  // ---- React component for the chat scrollback ----
  function ChartBlock({ spec }) {
    const ref = useRef(null);
    const [err, setErr] = useState(null);
    const [busy, setBusy] = useState(false);

    useEffect(() => {
      const v = validate(spec);
      if (!v.ok) { setErr(v.error); return; }
      setErr(null);
      if (ref.current) { try { renderInto(ref.current, spec); } catch (e) { setErr(e.message || String(e)); } }
    }, [spec]);

    if (err) return h("div", { className: "agent-chart error" }, "Chart could not render: " + err);

    const safeName = (spec.title || "atlas-chart").replace(/[^\w]+/g, "-").toLowerCase().slice(0, 60);
    const download = () => {
      setBusy(true);
      const node = ref.current && ref.current.parentNode;
      window.htmlToImage.toPng(node, { pixelRatio: 2, backgroundColor: "#fff" })
        .then((url) => downloadDataUrl(url, safeName + ".png"))
        .catch((e) => setErr(e.message || String(e)))
        .finally(() => setBusy(false));
    };

    return h("div", { className: "agent-chart" },
      h("div", { className: "agent-chart-canvas" }, h("svg", { ref })),
      spec.footnote ? h("div", { className: "agent-chart-foot" }, spec.footnote) : null,
      h("div", { className: "agent-chart-actions" },
        h("button", { className: "chart-dl", type: "button", disabled: busy, onClick: download },
          busy ? "Saving…" : "Download PNG"),
      ),
    );
  }

  window.AtlasChartBlock = ChartBlock;
  window.AtlasChartSpec = { validate, renderInto };
  window.AtlasChartPNG = toPNG;
})();
