import { Config, State, Observables, Capabilities, FlatGravitySystem } from "./flat_gravity";
import { Duration, mapRange, random, usePointer } from "../helpers";
import { createEffect, createSignal, onMount } from "solid-js";
import { ObservableEntry, ObservablesPanel } from "../ui_helpers";
import { Draw } from "../draw";
import { useSimulation } from "../useSimulation";

export type ViewConfig = {
  massRange: { min: number, max: number },
  radiusRange: { min: number, max: number },
  expectedNetMass: number,
}

function entries(obs: Observables): ObservableEntry[] {
  return [
    { type: "header", label: "General" },
    { type: "scalar", label: "Particles", value: obs.particleCount },
    { type: "scalar", label: "Net Mass", value: obs.netMass },
    
    { type: "header", label: "Energy" },
    { type: "scalar", label: "Kinetic", value: obs.netKineticEnergy },
    { type: "scalar", label: "Potential (formula)", value: obs.netPotentialEnergy },
    { type: "scalar", label: "Potential (integral)", value: obs.netPotentialEnergyIntegralCalculation },
    { type: "scalar", label: "formula - integral", value: obs.netPotentialEnergy - obs.netPotentialEnergyIntegralCalculation },
    { type: "scalar", label: "Total (formula)", value: obs.netEnergy },
    { type: "scalar", label: "Total (integral)", value: obs.netKineticEnergy + obs.netPotentialEnergyIntegralCalculation },
    
    { type: "header", label: "Vectors" },
    { 
      type: "vector", 
      label: "Center of Mass", 
      vector: [ obs.centerOfMass.x, obs.centerOfMass.y ],
      components: ["x", "y"] 
    },
    { 
      type: "vector", 
      label: "Net Force", 
      vector: [ obs.netForce.fx, obs.netForce.fy ],
      components: ["fx", "fy"] 
    },
    { 
      type: "vector", 
      label: "Net Momentum", 
      vector: [ obs.netMomentum.px, obs.netMomentum.py ],
      components: ["px", "py"] 
    },
  ];
}

function render(
  canvas: HTMLCanvasElement,
  ctx: CanvasRenderingContext2D,
  system: FlatGravitySystem,
  { massRange, radiusRange, expectedNetMass }: ViewConfig,
  showCenterOfMass: boolean,
) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  const { particleCount, centerOfMass, netMass } = system.observables;
  const { position, mass } = system.state;

  for (let i = 0; i < particleCount; i++) {
    const m = mass.val[i];
    const radius = mapRange(m, massRange.min, massRange.max, radiusRange.min, radiusRange.max);
    // Interpolate Color (Blue = 240, Red = 0)
    const hue = mapRange(m, massRange.min, massRange.max, 240, 0);
    
    Draw.particle(ctx, position.x[i], position.y[i], radius, `hsl(${hue}, 100%, 50%)`);
  }

  if (showCenterOfMass && particleCount > 0) {
    const radius = mapRange(netMass, 0, expectedNetMass, 0, 300);
    Draw.crosshair(ctx, centerOfMass.x, centerOfMass.y, radius, "red");
  }
}

function renderTrails(
  ctx: CanvasRenderingContext2D,
  system: FlatGravitySystem,
  config: ViewConfig,
  opacity: number = 0.03
) {
  ctx.fillStyle = `rgba(0, 0, 0, ${opacity})`;
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

  const { particleCount } = system.observables;
  const { position, mass } = system.state;
  const { massRange } = config;
  
  ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; // Faint white dots
  for (let i = 0; i < particleCount; i++) {
    const m = mass.val[i];
    
    const trailSize = mapRange(m, massRange.min, massRange.max, 1, 3);

    ctx.fillRect(
      position.x[i] - trailSize / 2, 
      position.y[i] - trailSize / 2, 
      trailSize, 
      trailSize
    );
  }
}

export function FlatGravityView(props: { system: FlatGravitySystem, viewConfig: ViewConfig }) {

  const [uiState, setUiState] = createSignal<Observables>(structuredClone(props.system.observables));
  const [showCenterOfMass, setShowCenterOfMass] = createSignal(true);
  const [showTrails, setShowTrails] = createSignal(true);

  const width = 800;
  const height = 600;

  let canvasRef!: HTMLCanvasElement;
  let ctx: CanvasRenderingContext2D;
  let trailCanvasRef!: HTMLCanvasElement;
  let trailCtx: CanvasRenderingContext2D;

  onMount(() => {
    ctx = canvasRef.getContext("2d")!;
    trailCtx = trailCanvasRef.getContext("2d")!;

    // Stop the 'ghost image' drag behavior
    canvasRef.addEventListener('dragstart', (e) => e.preventDefault());
    
    // prevent the context menu on right-click 
    // if you plan to use right-click for panning later
    // canvasRef.addEventListener('contextmenu', (e) => e.preventDefault());
  });

  const { fps, isPlaying, togglePlay, requestSyncedRender } = useSimulation({
    step(dt: Duration) {
      props.system.flow(dt);
    },
    render() {
      props.system.refreshObservables();
      if (showTrails()) {
        renderTrails(trailCtx, props.system, props.viewConfig);
      }
      render(canvasRef, ctx, props.system, props.viewConfig, showCenterOfMass());
    },
    sync() {
      setUiState(structuredClone(props.system.observables));
    },
    isPlaying: true,
  });

  const { position, isDown, handle: handlePointer } = usePointer(() => canvasRef);

  function toggleTrails() {
    if (showTrails()) {
      // Clear the trail canvas entirely when turned off
      trailCtx.clearRect(0, 0, trailCanvasRef.width, trailCanvasRef.height);
      setShowTrails(false);
    } else {
      setShowTrails(true);
    }
  }

  // spawn new particles on mouse-down
  createEffect(() => {
    if (isDown()) {
      if (props.system.observables.particleCount >= 10000) return; 

      const [x, y] = position();

      // Add a tiny bit of random scatter so they don't perfectly overlap.
      const rx = x + random(-5, 5);
      const ry = y + random(-5, 5);
      
      // Spawn a small particle with zero initial velocity (note that net-momentum is not gonna change)
      const m = random(1, 5); 
      props.system.capabilities.spawn(rx, ry, 0, 0, m);

      requestSyncedRender();
    }
  });

  return (
    <div class="app-layout">
      <div class="cell-0"></div>

      {/* === Top Controls === */}
      <div class="cell-1 controls">
        <button class="btn" onClick={togglePlay} style="width: 70px">
          {isPlaying() ? "Pause" : "Play"}
        </button>
        <button class="btn" onClick={props.system.reset} style="width: 70px">
          Reset
        </button>

        <span class="top-stats">
          FPS: {fps()}
        </span>
        <label class="toggle">
          <input 
            type="checkbox" 
            checked={showCenterOfMass()} 
            onChange={(e) => setShowCenterOfMass(e.currentTarget.checked)} 
          />
          Show Center-of-Mass
        </label>
        <label class="toggle">
          <input 
            type="checkbox" 
            checked={showTrails()} 
            onChange={() => toggleTrails() } 
          />
          Show trails
        </label>
      </div>

      <div class="cell-2"></div>

      {/* === Reserved for future brushes === */}
      <div class="cell-3 brushes-panel">
      </div>

      {/* === Canvas === */}
      <div class="cell-4 canvas-container">
        <div style="position: relative">
          <canvas
            ref={trailCanvasRef}
            width={width} height={height}
            style={{ position: "absolute" }}
          />
          <canvas
            ref={canvasRef}
            width={width} height={height}
            onPointerDown={handlePointer}
            onPointerMove={handlePointer}
            style={{ position: "absolute", background: "transparent" }}
          />
        </div>
      </div>

      {/* === Observables === */}
      <div class="cell-5 side-panel">
        <ObservablesPanel data={ entries(uiState()) }  />
      </div>

    </div>
  );
}

