import { useEffect, useMemo, useState, type CSSProperties } from "react";
import { SurfView } from "./render/SurfView.js";
import type { SurfTrace, Quiver3Trace } 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;
}

/** Vector field payload: surface patches + sampled tangent vectors at patch centers. */
interface VectorFieldData extends MeshData {
  type: "vectorfield";
  vectors: {
    x: number[];
    y: number[];
    z: number[];
    u: number[];
    v: number[];
    w: 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"
  );
}

function isVectorFieldData(d: unknown): d is VectorFieldData {
  return (
    isMeshData(d) &&
    (d as VectorFieldData).type === "vectorfield" &&
    !!(d as VectorFieldData).vectors
  );
}

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

  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 surfTraces = useMemo<SurfTrace[]>(() => {
    if (!mesh) return [];
    return mesh.x.map((_, k) => ({
      x: mesh.x[k],
      y: mesh.y[k],
      z: mesh.z[k],
      rows: mesh.n,
      cols: mesh.n,
    }));
  }, [mesh]);

  const quiverTraces = useMemo<Quiver3Trace[]>(() => {
    if (!isVectorFieldData(mesh)) return [];
    const { vectors } = mesh;
    return [
      {
        x: vectors.x,
        y: vectors.y,
        z: vectors.z,
        u: vectors.u.map(v => v * arrowScale),
        v: vectors.v.map(v => v * arrowScale),
        w: vectors.w.map(v => v * arrowScale),
        showArrowHead: true,
        color: [0.85, 0.325, 0.098] as [number, number, number],
        lineWidth: 1.5,
      },
    ];
  }, [mesh, arrowScale]);

  const isVectorField = isVectorFieldData(mesh);
  const maxLevel = mesh?.maxLevel ?? 3;

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

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

      <div style={panelStyle}>
        {isVectorField ? (
          <>
            <div style={{ fontWeight: 600, marginBottom: 8 }}>
              Vector field on surface
            </div>
            <label style={{ display: "block", fontSize: 13 }}>
              Arrow scale: <b>{arrowScale.toFixed(2)}</b>
              <input
                type="range"
                min={0.02}
                max={0.5}
                step={0.01}
                value={arrowScale}
                onChange={e => setArrowScale(Number(e.target.value))}
                style={{ width: "100%", marginTop: 4 }}
              />
            </label>
            <div style={{ fontSize: 12, color: "#475569", marginTop: 4 }}>
              patches: {mesh?.npatches ?? "—"}
            </div>
          </>
        ) : (
          <>
            <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",
};
