/**
 * TeleopWebUI.jsx — Browser-based remote control with live video (Issue #534).
 *
 * Features:
 *   - Live camera stream via rosbridge (sensor_msgs/CompressedImage)
 *   - Camera selector: front / rear / left / right
 *   - Virtual joystick (touch + mouse) for mobile-friendly control
 *   - WASD / arrow-key keyboard fallback
 *   - Speed presets: SLOW (20%), NORMAL (50%), FAST (100%)
 *   - E-stop button (latching, prominent)
 *   - Real-time linear/angular velocity display
 *   - Mobile-responsive split layout
 *
 * Topics:
 *   /camera/<name>/image_raw/compressed   sensor_msgs/CompressedImage  (subscribe)
 *   /cmd_vel                              geometry_msgs/Twist           (publish)
 */

import { useEffect, useRef, useState, useCallback } from 'react';

// ── Constants ─────────────────────────────────────────────────────────────────

const MAX_LINEAR  = 0.5;   // m/s
const MAX_ANGULAR = 1.0;   // rad/s
const DEADZONE    = 0.10;  // 10 %
const CMD_HZ      = 20;    // publish rate (ms)

const SPEED_PRESETS = [
  { label: 'SLOW',   value: 0.20, color: 'text-green-400',  border: 'border-green-800',  bg: 'bg-green-950'  },
  { label: 'NORMAL', value: 0.50, color: 'text-amber-400',  border: 'border-amber-800',  bg: 'bg-amber-950'  },
  { label: 'FAST',   value: 1.00, color: 'text-red-400',    border: 'border-red-800',    bg: 'bg-red-950'    },
];

const CAMERAS = [
  { id: 'front', label: 'Front', topic: '/camera/front/image_raw/compressed' },
  { id: 'rear',  label: 'Rear',  topic: '/camera/rear/image_raw/compressed'  },
  { id: 'left',  label: 'Left',  topic: '/camera/left/image_raw/compressed'  },
  { id: 'right', label: 'Right', topic: '/camera/right/image_raw/compressed' },
];

// ── Utilities ─────────────────────────────────────────────────────────────────

function applyDeadzone(value) {
  const abs = Math.abs(value);
  if (abs < DEADZONE) return 0;
  return Math.sign(value) * ((abs - DEADZONE) / (1 - DEADZONE));
}

function clamp(v, lo, hi) {
  return Math.max(lo, Math.min(hi, v));
}

// ── Virtual joystick ──────────────────────────────────────────────────────────

function VirtualJoystick({ onMove }) {
  const canvasRef = useRef(null);
  const activeRef = useRef(false);
  const stickRef  = useRef({ x: 0, y: 0 });

  const draw = useCallback(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const W = canvas.width;
    const H = canvas.height;
    const cx = W / 2;
    const cy = H / 2;
    const R  = Math.min(W, H) * 0.38;
    const kr = Math.min(W, H) * 0.14;

    ctx.clearRect(0, 0, W, H);

    // Base plate
    ctx.fillStyle = 'rgba(15,23,42,0.85)';
    ctx.beginPath();
    ctx.arc(cx, cy, R + kr, 0, Math.PI * 2);
    ctx.fill();

    // Base ring
    ctx.strokeStyle = '#1e3a5f';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(cx, cy, R, 0, Math.PI * 2);
    ctx.stroke();

    // Deadzone ring
    ctx.strokeStyle = '#374151';
    ctx.lineWidth = 1;
    ctx.setLineDash([3, 3]);
    ctx.beginPath();
    ctx.arc(cx, cy, R * DEADZONE, 0, Math.PI * 2);
    ctx.stroke();
    ctx.setLineDash([]);

    // Crosshair
    ctx.strokeStyle = '#1e3a5f';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(cx - R * 0.3, cy); ctx.lineTo(cx + R * 0.3, cy);
    ctx.moveTo(cx, cy - R * 0.3); ctx.lineTo(cx, cy + R * 0.3);
    ctx.stroke();

    // Knob
    const kx = cx + stickRef.current.x * R;
    const ky = cy - stickRef.current.y * R;
    const active = activeRef.current;

    const grad = ctx.createRadialGradient(kx - kr * 0.3, ky - kr * 0.3, 0, kx, ky, kr);
    grad.addColorStop(0, active ? '#38bdf8' : '#1d4ed8');
    grad.addColorStop(1, active ? '#0369a1' : '#1e3a8a');
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.arc(kx, ky, kr, 0, Math.PI * 2);
    ctx.fill();

    ctx.strokeStyle = active ? '#7dd3fc' : '#3b82f6';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(kx, ky, kr, 0, Math.PI * 2);
    ctx.stroke();

    // Vector line
    const { x, y } = stickRef.current;
    if (Math.abs(x) > 0.02 || Math.abs(y) > 0.02) {
      ctx.strokeStyle = 'rgba(56,189,248,0.5)';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(cx, cy);
      ctx.lineTo(kx, ky);
      ctx.stroke();
    }
  }, []);

  const getPos = (clientX, clientY) => {
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    const R = Math.min(rect.width, rect.height) * 0.38;
    const cx = rect.left + rect.width  / 2;
    const cy = rect.top  + rect.height / 2;
    let x = (clientX - cx) / R;
    let y = (clientY - cy) / R;
    const mag = Math.sqrt(x * x + y * y);
    if (mag > 1) { x /= mag; y /= mag; }
    return { x: clamp(x, -1, 1), y: clamp(-y, -1, 1) };
  };

  const onStart = useCallback((clientX, clientY) => {
    activeRef.current = true;
    const { x, y } = getPos(clientX, clientY);
    stickRef.current = { x, y };
    onMove({ x: applyDeadzone(x), y: applyDeadzone(y) });
    draw();
  }, [draw, onMove]);

  const onContinue = useCallback((clientX, clientY) => {
    if (!activeRef.current) return;
    const { x, y } = getPos(clientX, clientY);
    stickRef.current = { x, y };
    onMove({ x: applyDeadzone(x), y: applyDeadzone(y) });
    draw();
  }, [draw, onMove]);

  const onEnd = useCallback(() => {
    activeRef.current = false;
    stickRef.current = { x: 0, y: 0 };
    onMove({ x: 0, y: 0 });
    draw();
  }, [draw, onMove]);

  // Mount: draw once + attach pointer events
  useEffect(() => {
    draw();
    const canvas = canvasRef.current;
    if (!canvas) return;

    const onPD = (e) => { e.preventDefault(); onStart(e.clientX, e.clientY); };
    const onPM = (e) => { e.preventDefault(); onContinue(e.clientX, e.clientY); };
    const onPU = (e) => { e.preventDefault(); onEnd(); };

    const onTD = (e) => { e.preventDefault(); const t = e.touches[0]; onStart(t.clientX, t.clientY); };
    const onTM = (e) => { e.preventDefault(); const t = e.touches[0]; onContinue(t.clientX, t.clientY); };
    const onTE = (e) => { e.preventDefault(); onEnd(); };

    canvas.addEventListener('pointerdown', onPD);
    window.addEventListener('pointermove', onPM);
    window.addEventListener('pointerup',   onPU);
    canvas.addEventListener('touchstart',  onTD, { passive: false });
    canvas.addEventListener('touchmove',   onTM, { passive: false });
    canvas.addEventListener('touchend',    onTE, { passive: false });

    return () => {
      canvas.removeEventListener('pointerdown', onPD);
      window.removeEventListener('pointermove', onPM);
      window.removeEventListener('pointerup',   onPU);
      canvas.removeEventListener('touchstart',  onTD);
      canvas.removeEventListener('touchmove',   onTM);
      canvas.removeEventListener('touchend',    onTE);
    };
  }, [onStart, onContinue, onEnd, draw]);

  return (
    <canvas
      ref={canvasRef}
      width={200}
      height={200}
      className="rounded-full cursor-grab active:cursor-grabbing select-none"
      style={{ touchAction: 'none' }}
    />
  );
}

// ── Camera feed ───────────────────────────────────────────────────────────────

function CameraFeed({ subscribe, topic }) {
  const [src, setSrc]       = useState(null);
  const [fps, setFps]       = useState(0);
  const frameCountRef       = useRef(0);
  const lastFpsTimeRef      = useRef(Date.now());

  useEffect(() => {
    setSrc(null);
    frameCountRef.current  = 0;
    lastFpsTimeRef.current = Date.now();

    const unsub = subscribe(topic, 'sensor_msgs/CompressedImage', (msg) => {
      const fmt    = msg.format?.includes('png') ? 'png' : 'jpeg';
      const dataUrl = `data:image/${fmt};base64,${msg.data}`;
      setSrc(dataUrl);

      frameCountRef.current++;
      const now = Date.now();
      const dt  = now - lastFpsTimeRef.current;
      if (dt >= 1000) {
        setFps(Math.round((frameCountRef.current * 1000) / dt));
        frameCountRef.current  = 0;
        lastFpsTimeRef.current = now;
      }
    });

    return unsub;
  }, [subscribe, topic]);

  return (
    <div className="relative w-full h-full flex items-center justify-center bg-black rounded-lg overflow-hidden">
      {src ? (
        <img
          src={src}
          alt="camera"
          className="max-w-full max-h-full object-contain"
          draggable={false}
        />
      ) : (
        <div className="flex flex-col items-center gap-2 text-gray-700">
          <div className="text-3xl">📷</div>
          <div className="text-xs tracking-widest">NO SIGNAL</div>
        </div>
      )}
      {/* FPS badge */}
      {src && (
        <div className="absolute top-2 right-2 bg-black bg-opacity-60 text-green-400 text-xs font-mono px-1.5 py-0.5 rounded">
          {fps} fps
        </div>
      )}
    </div>
  );
}

// ── Main component ────────────────────────────────────────────────────────────

export function TeleopWebUI({ subscribe, publish }) {
  const [selectedCam, setSelectedCam] = useState('front');
  const [speedPreset, setSpeedPreset] = useState(0.50);
  const [isEstopped,  setIsEstopped]  = useState(false);
  const [linearVel,   setLinearVel]   = useState(0);
  const [angularVel,  setAngularVel]  = useState(0);

  // Joystick normalized input ref [-1,1] each axis
  const joystickRef = useRef({ x: 0, y: 0 });
  // Keyboard pressed keys
  const keysRef = useRef({});

  const currentCam = CAMERAS.find(c => c.id === selectedCam);

  // ── Keyboard input ──────────────────────────────────────────────────────────
  useEffect(() => {
    const down = (e) => {
      const k = e.key.toLowerCase();
      if (['w','a','s','d','arrowup','arrowdown','arrowleft','arrowright',' '].includes(k)) {
        keysRef.current[k] = true;
        e.preventDefault();
      }
    };
    const up = (e) => {
      const k = e.key.toLowerCase();
      keysRef.current[k] = false;
    };
    window.addEventListener('keydown', down);
    window.addEventListener('keyup',   up);
    return () => {
      window.removeEventListener('keydown', down);
      window.removeEventListener('keyup',   up);
    };
  }, []);

  // ── Publish loop ────────────────────────────────────────────────────────────
  useEffect(() => {
    const timer = setInterval(() => {
      const k = keysRef.current;
      const j = joystickRef.current;

      // Keyboard overrides joystick if any key pressed
      let lin = j.y;
      let ang = -j.x;   // right stick-x → negative angular (turn left on left)

      const anyKey = k['w'] || k['s'] || k['a'] || k['d'] ||
                     k['arrowup'] || k['arrowdown'] || k['arrowleft'] || k['arrowright'];

      if (anyKey) {
        lin = 0;
        ang = 0;
        if (k['w'] || k['arrowup'])    lin =  1;
        if (k['s'] || k['arrowdown'])  lin = -1;
        if (k['a'] || k['arrowleft'])  ang =  1;
        if (k['d'] || k['arrowright']) ang = -1;
      }

      // Space = quick stop (doesn't latch e-stop)
      if (k[' ']) { lin = 0; ang = 0; }

      const factor = isEstopped ? 0 : speedPreset;
      const finalLin = clamp(lin * MAX_LINEAR  * factor, -MAX_LINEAR,  MAX_LINEAR);
      const finalAng = clamp(ang * MAX_ANGULAR * factor, -MAX_ANGULAR, MAX_ANGULAR);

      setLinearVel(finalLin);
      setAngularVel(finalAng);

      if (publish) {
        publish('/cmd_vel', 'geometry_msgs/Twist', {
          linear:  { x: finalLin, y: 0, z: 0 },
          angular: { x: 0, y: 0, z: finalAng },
        });
      }
    }, CMD_HZ);

    return () => clearInterval(timer);
  }, [isEstopped, speedPreset, publish]);

  // ── E-stop ──────────────────────────────────────────────────────────────────
  const handleEstop = () => {
    const next = !isEstopped;
    setIsEstopped(next);
    if (publish) {
      publish('/cmd_vel', 'geometry_msgs/Twist', {
        linear:  { x: 0, y: 0, z: 0 },
        angular: { x: 0, y: 0, z: 0 },
      });
    }
  };

  // Joystick callback
  const handleJoystick = useCallback((pos) => {
    joystickRef.current = pos;
  }, []);

  // ── Render ──────────────────────────────────────────────────────────────────
  return (
    <div className="flex flex-col lg:flex-row h-full gap-3">

      {/* ── Left: Camera feed ── */}
      <div className="flex flex-col gap-2 flex-1 min-h-0">

        {/* Camera selector */}
        <div className="flex gap-1 shrink-0">
          {CAMERAS.map((cam) => (
            <button
              key={cam.id}
              onClick={() => setSelectedCam(cam.id)}
              className={`px-3 py-1 text-xs font-bold rounded border tracking-widest transition-colors ${
                selectedCam === cam.id
                  ? 'bg-cyan-950 border-cyan-700 text-cyan-300'
                  : 'bg-gray-950 border-gray-800 text-gray-600 hover:text-gray-300'
              }`}
            >
              {cam.label.toUpperCase()}
            </button>
          ))}
          <div className="flex-1" />
          <div className="text-xs text-gray-700 font-mono self-center">
            {currentCam?.topic}
          </div>
        </div>

        {/* Video feed */}
        <div className="flex-1 min-h-[200px] lg:min-h-0">
          <CameraFeed
            subscribe={subscribe}
            topic={currentCam?.topic}
          />
        </div>
      </div>

      {/* ── Right: Controls ── */}
      <div className="flex flex-col gap-3 w-full lg:w-72 shrink-0">

        {/* E-stop */}
        <button
          onClick={handleEstop}
          className={`w-full py-4 text-sm font-black tracking-widest rounded-lg border-2 transition-all ${
            isEstopped
              ? 'bg-red-900 border-red-500 text-red-200 animate-pulse'
              : 'bg-red-950 border-red-800 text-red-400 hover:bg-red-900 hover:border-red-600'
          }`}
        >
          {isEstopped ? '🛑 E-STOP — TAP TO RESUME' : '⚡ E-STOP'}
        </button>

        {/* Speed presets */}
        <div className="bg-gray-950 rounded-lg border border-cyan-950 p-3 space-y-2">
          <div className="text-xs font-bold tracking-widest text-cyan-700">SPEED PRESET</div>
          <div className="grid grid-cols-3 gap-2">
            {SPEED_PRESETS.map((p) => (
              <button
                key={p.label}
                onClick={() => setSpeedPreset(p.value)}
                disabled={isEstopped}
                className={`py-2 text-xs font-bold rounded border tracking-widest transition-colors ${
                  speedPreset === p.value && !isEstopped
                    ? `${p.bg} ${p.border} ${p.color}`
                    : 'bg-gray-900 border-gray-800 text-gray-600 hover:text-gray-300 disabled:opacity-40 disabled:cursor-not-allowed'
                }`}
              >
                {p.label}
                <div className="text-gray-500 font-normal mt-0.5">{(p.value * 100).toFixed(0)}%</div>
              </button>
            ))}
          </div>
        </div>

        {/* Velocity display */}
        <div className="bg-gray-950 rounded-lg border border-cyan-950 p-3 grid grid-cols-2 gap-2">
          <div>
            <div className="text-gray-600 text-xs mb-1">LINEAR</div>
            <div className={`text-xl font-mono ${isEstopped ? 'text-gray-700' : 'text-cyan-300'}`}>
              {linearVel.toFixed(2)}
            </div>
            <div className="text-gray-700 text-xs">m/s</div>
          </div>
          <div>
            <div className="text-gray-600 text-xs mb-1">ANGULAR</div>
            <div className={`text-xl font-mono ${isEstopped ? 'text-gray-700' : 'text-amber-300'}`}>
              {angularVel.toFixed(2)}
            </div>
            <div className="text-gray-700 text-xs">rad/s</div>
          </div>
        </div>

        {/* Joystick */}
        <div className="bg-gray-950 rounded-lg border border-cyan-950 p-3 flex flex-col items-center gap-2">
          <div className="text-xs font-bold tracking-widest text-cyan-700">JOYSTICK</div>
          <VirtualJoystick onMove={handleJoystick} />
          <div className="text-xs text-gray-700 text-center">
            drag or use W A S D / arrow keys
          </div>
        </div>

        {/* Key reference */}
        <div className="bg-gray-950 rounded border border-gray-800 p-2 text-xs text-gray-700 space-y-1">
          <div className="font-bold text-gray-600 mb-1">KEYBOARD</div>
          <div className="grid grid-cols-2 gap-x-3">
            <div><span className="text-gray-500">W/↑</span> Forward</div>
            <div><span className="text-gray-500">S/↓</span> Backward</div>
            <div><span className="text-gray-500">A/←</span> Turn Left</div>
            <div><span className="text-gray-500">D/→</span> Turn Right</div>
            <div className="col-span-2"><span className="text-gray-500">Space</span> Quick stop</div>
          </div>
        </div>
      </div>
    </div>
  );
}
