import { useEffect, useMemo, useState, type CSSProperties } from "react";
import { SurfView } from "./render/SurfView.js";
import type { SurfTrace } from "./render/types.js";
import { onData, onHostEvent, sendToMATLAB } from "./bridge.js";

/** Mesh payload from the numbl script: one flat (column-major) x/y/z array per
 *  patch, each an `n x n` grid. Mirrors what refine_demo.m sends. */
interface MeshData {
  n: number;
  x: number[][];
  y: number[][];
  z: number[][];
  npatches: number;
  level?: number;
  maxLevel?: number;
}

function isMeshData(d: unknown): d is MeshData {
  return (
    !!d &&
    typeof d === "object" &&
    Array.isArray((d as MeshData).x) &&
    typeof (d as MeshData).n === "number"
  );
}

export function App() {
  const [mesh, setMesh] = useState<MeshData | null>(null);
  const [level, setLevel] = useState(0);
  const [busy, setBusy] = useState(false);

  // Initial mesh arrives via Data; refinements arrive via "mesh" events
  // (which update React state without remounting the iframe, so the camera /
  // orientation is preserved across refinements).
  useEffect(() => {
    const apply = (d: unknown) => {
      if (!isMeshData(d)) return;
      setMesh(d);
      if (typeof d.level === "number") setLevel(d.level);
      setBusy(false);
    };
    const offData = onData(apply);
    const offMesh = onHostEvent("mesh", apply);
    return () => {
      offData();
      offMesh();
    };
  }, []);

  const traces = useMemo<SurfTrace[]>(() => {
    if (!mesh) return [];
    const out: SurfTrace[] = [];
    for (let k = 0; k < mesh.x.length; k++) {
      out.push({
        x: mesh.x[k],
        y: mesh.y[k],
        z: mesh.z[k],
        rows: mesh.n,
        cols: mesh.n,
      });
    }
    return out;
  }, [mesh]);

  const maxLevel = mesh?.maxLevel ?? 3;

  const onSlider = (v: number) => {
    setLevel(v);
    setBusy(true);
    sendToMATLAB("refine", v);
  };

  return (
    <div style={rootStyle}>
      {mesh ? (
        <SurfView surfTraces={traces} shading="faceted" />
      ) : (
        <div style={waitingStyle}>Waiting for mesh from the script…</div>
      )}

      <div style={panelStyle}>
        <div style={{ fontWeight: 600, marginBottom: 8 }}>surfacefun mesh</div>
        <label style={{ display: "block", fontSize: 13 }}>
          Refinement level: <b>{level}</b>
          <input
            type="range"
            min={0}
            max={maxLevel}
            step={1}
            value={level}
            onChange={e => onSlider(Number(e.target.value))}
            style={{ width: "100%", marginTop: 4 }}
          />
        </label>
        <div style={{ fontSize: 12, color: "#475569", marginTop: 4 }}>
          patches: {mesh?.npatches ?? "—"}
          {busy ? "  ·  refining…" : ""}
        </div>
        <div style={{ fontSize: 11, color: "#94a3b8", marginTop: 6 }}>
          drag to rotate · scroll to zoom
        </div>
      </div>
    </div>
  );
}

const rootStyle: CSSProperties = {
  position: "absolute",
  inset: 0,
  background: "#ffffff",
  fontFamily: "system-ui, -apple-system, Arial, sans-serif",
};

const waitingStyle: CSSProperties = {
  position: "absolute",
  inset: 0,
  display: "flex",
  alignItems: "center",
  justifyContent: "center",
  color: "#94a3b8",
};

const panelStyle: CSSProperties = {
  position: "absolute",
  top: 12,
  left: 12,
  width: 220,
  padding: "12px 14px",
  background: "rgba(255,255,255,0.92)",
  border: "1px solid #e2e8f0",
  borderRadius: 8,
  boxShadow: "0 1px 4px rgba(0,0,0,0.1)",
  color: "#0f172a",
};
