// ---------------------------------------------------------------------------
// customer-grid / ScriptViewPanel.tsx — owner item 6's renderer (W36-T04, contract C3).
//
// ⛔ ITS OWN MODULE, AND THAT IS A HARNESS FACT AS WELL AS A TIDINESS ONE. It began inside
// `viewModes.tsx`, where the other display modes live — and that module `require`s `./cells`,
// which imports glide-data-grid, whose CJS build cannot load under this repo's node harness. So
// putting it there made the whole component untestable: the ONE thing this surface must prove is
// that a hostile spec is rendered as text, and that claim can only be made by RENDERING. Here the
// import graph is `react` + `./scriptViews` and nothing else, so `gridUx.test.ts` renders it for
// real and asserts on the HTML a person would receive.
// ⚠ The stylesheet is imported by `CustomerGrid.tsx` (the mount) for the same reason: tsc
// preserves a side-effect CSS import into the emitted CommonJS and node dies on it.
// ---------------------------------------------------------------------------

import { memo, useEffect, useRef, useState } from "react";
import { barScale, renderableSpec, specKindName } from "./scriptViews";
import type { DrawableSpec, ScriptRun, ScriptView as ScriptViewRecord } from "./scriptViews";

// ═══════════════════════════════════════════════════════════════════════════════════════════
// ⭐⭐ W36-T04 (owner item 6 · rulings R3/R10 · contract C3) — THE CODE-SCRIPT VIEW
//
// Owner, verbatim: *"Add code script as an interface (database View) so a user can build whatever
// they want through the Agent chat interface. be able to create any dashboard they want. User
// should have the ability to see the code AND the dashboard output of course."* Both halves are
// on screen at once, which is the whole of that sentence.
//
// ⛔⛔ NO BRANCH OF THIS RENDERER EXECUTES A STRING, AND THAT IS THE TICKET'S OWN `done-when`.
// R10 runs the Python in a SERVER sandbox exactly so the browser never has to trust the result;
// a renderer that then evaluated one would hand back everything the sandbox contains. So there is
// no `dangerouslySetInnerHTML`, no `new Function`, no `eval`, no `href`/`src` taken from a spec,
// and no `style` built from spec data anywhere below. Every value reaching the DOM is a SCALAR
// that `renderableSpec` admitted, rendered as TEXT — the one exception being a bar's width, which
// is a number clamped to a percentage here and never a string from the wire.
//
// ⛔ AN UNKNOWN `kind` IS A SENTENCE, NOT A BLANK. `spec` is whatever the script emitted (E's C3
// note 1: there is no fixed vocabulary, deliberately), so a view whose author invented
// `kind: "sankey"` is a legitimate thing this build cannot draw. Saying so, by name, is the
// difference between "this build does not know that shape yet" and "your script is broken".
//
// ⚠ `ok: false` IS A 200. A script that timed out or divided by zero asked a well-formed question
// whose ANSWER is that no view was produced; it is rendered beside the code, never thrown, and
// never a toast that disappears while the reader is looking at the line that caused it.
// ═══════════════════════════════════════════════════════════════════════════════════════════

function SpecTable({ spec }: { spec: DrawableSpec }) {
  return (
    <table className="cg-script-table">
      <thead>
        <tr>{(spec.columns ?? []).map((c, i) => <th key={`${c}:${i}`} scope="col">{c}</th>)}</tr>
      </thead>
      <tbody>
        {(spec.rows ?? []).map((row, r) => (
          <tr key={r}>{row.map((cell, c) => <td key={c}>{cell}</td>)}</tr>
        ))}
      </tbody>
    </table>
  );
}

function SpecMetrics({ spec }: { spec: DrawableSpec }) {
  return (
    <div className="cg-script-metrics">
      {(spec.items ?? []).map((item, i) => (
        <div className="cg-script-metric" key={`${item.label}:${i}`}>
          <div className="cg-script-metric-label">{item.label}</div>
          <div className="cg-script-metric-value">{item.value}</div>
          {item.note ? <div className="cg-script-metric-note">{item.note}</div> : null}
        </div>
      ))}
    </div>
  );
}

function SpecBars({ spec }: { spec: DrawableSpec }) {
  const series = spec.series ?? [];
  const scale = barScale(series);
  return (
    <div className="cg-script-bars">
      {series.map((s, i) => (
        <div className="cg-script-bar" key={`${s.label}:${i}`}>
          <span>{s.label}</span>
          <span className="cg-script-bar-track">
            {/* ⚠ The ONE computed style on this surface, and it is arithmetic on a NUMBER the
                spec reader already validated as finite, clamped here. Never a string from the
                wire, which is what would make this an injection point. */}
            <span
              className="cg-script-bar-fill"
              style={{ width: `${Math.min(100, Math.round((Math.abs(s.value) / scale) * 100))}%` }}
            />
          </span>
          <span className="cg-script-bar-value">{s.display}</span>
        </div>
      ))}
    </div>
  );
}

/** The refusal, and the four limit fields PRINTED rather than re-worded (C3 note 2). */
function ScriptRefusal({ run }: { run: ScriptRun }) {
  return (
    <div className="cg-script-refusal" role="status">
      <h4>This script did not produce a view</h4>
      <p>{run.error ?? "The run ended without emitting anything."}</p>
      <dl>
        <dt>Reason</dt>
        <dd>{run.code}</dd>
        {run.limit ? <><dt>Subject</dt><dd>{run.limit.subject}</dd></> : null}
        {run.limit ? <><dt>Effect</dt><dd>{run.limit.effect}</dd></> : null}
        {run.limit ? <><dt>Cause</dt><dd>{run.limit.cause}</dd></> : null}
        {run.limit ? <><dt>What to do</dt><dd>{run.limit.recommendation}</dd></> : null}
      </dl>
    </div>
  );
}

export function ScriptOutput({ run }: { run: ScriptRun | null }) {
  if (run === null)
    return <p className="cg-script-empty">Run the script to see what it draws.</p>;
  const drawable = run.ok ? renderableSpec(run.spec) : null;
  const claimed = specKindName(run.spec);
  return (
    <>
      {!run.ok ? <ScriptRefusal run={run} /> : null}
      {run.ok && drawable === null ? (
        <div className="cg-script-refusal" role="status">
          <h4>This build cannot draw that shape yet</h4>
          <p>
            {claimed
              ? `The script emitted a view of kind "${claimed}", which this version does not
                 render. It knows table, metrics, bars and text.`
              : `The script emitted a view with no recognisable kind. This version renders
                 table, metrics, bars and text.`}
          </p>
        </div>
      ) : null}
      {drawable !== null ? (
        <>
          {drawable.title ? <h3 className="cg-script-title">{drawable.title}</h3> : null}
          {drawable.kind === "table" ? <SpecTable spec={drawable} /> : null}
          {drawable.kind === "metrics" ? <SpecMetrics spec={drawable} /> : null}
          {drawable.kind === "bars" ? <SpecBars spec={drawable} /> : null}
          {drawable.kind === "text" ? <p className="cg-script-text">{drawable.body}</p> : null}
        </>
      ) : null}
      {run.stdout ? (
        <pre className="cg-script-stdout">
          {run.stdout}
          {run.truncated ? "\n[output truncated]" : ""}
        </pre>
      ) : null}
    </>
  );
}

/**
 * The whole View: the source on the left, what it drew on the right.
 *
 * ⚠ A DRAFT RUN NEEDS NO SAVE (C3 note 3), which is what makes this usable: type, run, save when
 * it works. The Save button is separate and deliberately not automatic, because a PUT is a NEW
 * VERSION and an autosaving editor would fill the 40-deep history with keystrokes.
 */
export const ScriptView = memo(function ScriptView({
  view,
  running,
  run,
  saving,
  readOnly = false,
  onRun,
  onSave,
}: {
  view: ScriptViewRecord | null;
  running: boolean;
  run: ScriptRun | null;
  saving?: boolean;
  readOnly?: boolean;
  onRun: (draft: string) => void;
  onSave?: (source: string) => void;
}) {
  const [draft, setDraft] = useState(view?.source ?? "");
  const loadedId = useRef<string | null>(null);
  useEffect(() => {
    // Re-seed only when a DIFFERENT view is opened. Re-seeding on every `view` identity would
    // discard what somebody is typing the moment the list refreshes underneath them.
    if (!view || loadedId.current === view.id) return;
    loadedId.current = view.id;
    setDraft(view.source);
  }, [view]);
  const dirty = view !== null && draft !== view.source;
  if (view === null)
    return (
      <div className="cg-script">
        <p className="cg-script-empty cg-script-empty--pad">
          This script view could not be opened.
        </p>
      </div>
    );
  return (
    <div className="cg-script">
      <div className="cg-script-body">
        <section className="cg-script-pane" aria-label="Script source">
          <div className="cg-script-pane-head">
            <span>Code</span>
            <span className="cg-script-meta">
              {`v${view.version}`}
              {dirty ? ", unsaved changes" : ""}
            </span>
            <span className="cg-script-spacer" />
            <button
              type="button"
              className="cg-script-ghost"
              disabled={!dirty || saving === true || readOnly || onSave === undefined}
              onClick={() => onSave?.(draft)}
            >
              {saving === true ? "Saving" : "Save version"}
            </button>
            <button
              type="button"
              className="cg-script-run"
              disabled={running}
              onClick={() => onRun(draft)}
            >
              {running ? "Running" : "Run"}
            </button>
          </div>
          <textarea
            className="cg-script-code"
            value={draft}
            spellCheck={false}
            readOnly={readOnly}
            aria-label="Script source"
            onChange={(e) => setDraft(e.target.value)}
          />
        </section>
        <section className="cg-script-pane" aria-label="Script output">
          <div className="cg-script-pane-head">
            <span>Output</span>
            <span className="cg-script-spacer" />
            {run !== null ? (
              <span className="cg-script-meta">
                {run.ms > 0 ? `${run.ms} ms` : ""}
              </span>
            ) : null}
          </div>
          <div className="cg-script-out">
            <ScriptOutput run={run} />
          </div>
        </section>
      </div>
    </div>
  );
});
