import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

import type { PolygonVehicleState } from "../core/polygon/liveWorker";

interface PolygonRoverSceneProps {
  live: PolygonVehicleState | null;
  trajectory: PolygonVehicleState[];
  workerAvailable: boolean;
}

const CAMERA_POSITION = new THREE.Vector3(5.3, 3.9, 5.3);
const CAMERA_TARGET = new THREE.Vector3(0, 0.55, 0);

function threePosition(state: PolygonVehicleState): THREE.Vector3 {
  return new THREE.Vector3(
    state.position.x,
    Math.max(0, state.position.z),
    -state.position.y,
  );
}

function yawRadians(state: PolygonVehicleState): number {
  const { x, y, z, w } = state.orientation;
  return Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z));
}

function createRover(): THREE.Group {
  const rover = new THREE.Group();
  rover.name = "ackermann-rover";

  const bodyMaterial = new THREE.MeshStandardMaterial({
    color: 0x2879ff,
    metalness: 0.42,
    roughness: 0.3,
  });
  const darkMaterial = new THREE.MeshStandardMaterial({
    color: 0x111820,
    metalness: 0.25,
    roughness: 0.58,
  });
  const trimMaterial = new THREE.MeshStandardMaterial({
    color: 0xb8c9dc,
    metalness: 0.72,
    roughness: 0.22,
  });
  const glassMaterial = new THREE.MeshPhysicalMaterial({
    color: 0x72b7d8,
    metalness: 0.05,
    roughness: 0.08,
    transmission: 0.3,
    transparent: true,
    opacity: 0.72,
  });
  const tireMaterial = new THREE.MeshStandardMaterial({
    color: 0x080a0d,
    metalness: 0.05,
    roughness: 0.9,
  });
  const headlightMaterial = new THREE.MeshStandardMaterial({
    color: 0xf2fbff,
    emissive: 0x8ecbff,
    emissiveIntensity: 2.4,
  });
  const tailMaterial = new THREE.MeshStandardMaterial({
    color: 0xff3355,
    emissive: 0xff1838,
    emissiveIntensity: 1.7,
  });

  const addBox = (
    name: string,
    size: [number, number, number],
    position: [number, number, number],
    material: THREE.Material,
  ) => {
    const mesh = new THREE.Mesh(new THREE.BoxGeometry(...size), material);
    mesh.name = name;
    mesh.position.set(...position);
    mesh.castShadow = true;
    mesh.receiveShadow = true;
    rover.add(mesh);
    return mesh;
  };

  addBox("lower-chassis", [2.55, 0.34, 1.32], [0, 0.58, 0], darkMaterial);
  addBox("body-shell", [2.34, 0.42, 1.18], [0.02, 0.86, 0], bodyMaterial);
  addBox("front-hood", [0.92, 0.24, 1.06], [0.69, 1.16, 0], bodyMaterial);
  addBox("equipment-cabin", [0.82, 0.58, 1.0], [-0.35, 1.22, 0], darkMaterial);
  addBox("windshield", [0.04, 0.42, 0.88], [0.08, 1.31, 0], glassMaterial)
    .rotation.z = -0.18;
  addBox("front-bumper", [0.16, 0.22, 1.48], [1.34, 0.56, 0], trimMaterial);
  addBox("rear-bumper", [0.15, 0.2, 1.42], [-1.33, 0.55, 0], trimMaterial);

  for (const x of [-0.82, 0.82]) {
    for (const z of [-0.76, 0.76]) {
      const wheel = new THREE.Mesh(
        new THREE.CylinderGeometry(0.42, 0.42, 0.28, 24),
        tireMaterial,
      );
      wheel.name = x > 0 ? "front-wheel" : "rear-wheel";
      wheel.position.set(x, 0.43, z);
      wheel.rotation.x = Math.PI / 2;
      wheel.castShadow = true;
      rover.add(wheel);

      const hub = new THREE.Mesh(
        new THREE.CylinderGeometry(0.16, 0.16, 0.3, 20),
        trimMaterial,
      );
      hub.position.copy(wheel.position);
      hub.rotation.x = Math.PI / 2;
      hub.castShadow = true;
      rover.add(hub);
    }
  }

  for (const z of [-0.4, 0.4]) {
    addBox("headlight", [0.08, 0.16, 0.22], [1.22, 0.9, z], headlightMaterial);
    addBox("tail-light", [0.06, 0.15, 0.2], [-1.19, 0.88, z], tailMaterial);
  }

  const mast = new THREE.Mesh(
    new THREE.CylinderGeometry(0.045, 0.055, 0.7, 16),
    trimMaterial,
  );
  mast.position.set(-0.45, 1.85, 0);
  mast.castShadow = true;
  rover.add(mast);

  const lidar = new THREE.Mesh(
    new THREE.CylinderGeometry(0.22, 0.22, 0.14, 32),
    darkMaterial,
  );
  lidar.name = "lidar";
  lidar.position.set(-0.45, 2.22, 0);
  lidar.castShadow = true;
  rover.add(lidar);

  const lidarBand = new THREE.Mesh(
    new THREE.CylinderGeometry(0.225, 0.225, 0.045, 32),
    glassMaterial,
  );
  lidarBand.position.set(-0.45, 2.22, 0);
  rover.add(lidarBand);

  return rover;
}

function disposeScene(scene: THREE.Scene) {
  scene.traverse((object) => {
    if (!(object instanceof THREE.Mesh || object instanceof THREE.Line)) return;
    object.geometry.dispose();
    const materials = Array.isArray(object.material) ? object.material : [object.material];
    materials.forEach((material) => material.dispose());
  });
}

export function PolygonRoverScene({
  live,
  trajectory,
  workerAvailable,
}: PolygonRoverSceneProps) {
  const hostRef = useRef<HTMLDivElement | null>(null);
  const cameraRef = useRef<THREE.PerspectiveCamera | null>(null);
  const controlsRef = useRef<OrbitControls | null>(null);
  const roverRef = useRef<THREE.Group | null>(null);
  const trajectoryRef = useRef<THREE.Line<THREE.BufferGeometry, THREE.LineBasicMaterial> | null>(
    null,
  );
  const desiredPositionRef = useRef(new THREE.Vector3());
  const desiredYawRef = useRef(0);
  const followRef = useRef(true);
  const [followRover, setFollowRover] = useState(true);
  const [renderError, setRenderError] = useState<string | null>(null);

  useEffect(() => {
    desiredPositionRef.current.copy(live ? threePosition(live) : new THREE.Vector3());
    desiredYawRef.current = live ? -yawRadians(live) : 0;
  }, [live]);

  useEffect(() => {
    followRef.current = followRover;
  }, [followRover]);

  useEffect(() => {
    const line = trajectoryRef.current;
    if (!line) return;
    const points = trajectory.map((state) => {
      const point = threePosition(state);
      point.y += 0.06;
      return point;
    });
    line.geometry.setFromPoints(points);
    line.geometry.computeBoundingSphere();
  }, [trajectory]);

  useEffect(() => {
    const host = hostRef.current;
    if (!host) return;

    let renderer: THREE.WebGLRenderer;
    try {
      renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        powerPreference: "high-performance",
      });
    } catch {
      setRenderError("Браузер не смог создать WebGL-сцену.");
      return;
    }

    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.06;
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.domElement.setAttribute("aria-label", "Интерактивная 3D-сцена Ackermann Rover");
    host.prepend(renderer.domElement);

    const scene = new THREE.Scene();
    scene.fog = new THREE.FogExp2(0x081019, 0.025);

    const camera = new THREE.PerspectiveCamera(46, 1, 0.08, 220);
    camera.position.copy(CAMERA_POSITION);
    cameraRef.current = camera;

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.07;
    controls.enablePan = true;
    controls.enableZoom = true;
    controls.minDistance = 2.2;
    controls.maxDistance = 70;
    controls.minPolarAngle = 0.04;
    controls.maxPolarAngle = Math.PI / 2 - 0.02;
    controls.target.copy(CAMERA_TARGET);
    controls.update();
    controlsRef.current = controls;

    const hemisphere = new THREE.HemisphereLight(0xaed7ff, 0x10151b, 2.2);
    scene.add(hemisphere);
    const key = new THREE.DirectionalLight(0xffffff, 3.7);
    key.position.set(5, 9, 4);
    key.castShadow = true;
    key.shadow.mapSize.set(1024, 1024);
    key.shadow.camera.near = 1;
    key.shadow.camera.far = 32;
    key.shadow.camera.left = -10;
    key.shadow.camera.right = 10;
    key.shadow.camera.top = 10;
    key.shadow.camera.bottom = -10;
    scene.add(key);
    const rim = new THREE.DirectionalLight(0x397dff, 2.1);
    rim.position.set(-5, 3, -5);
    scene.add(rim);

    const ground = new THREE.Mesh(
      new THREE.PlaneGeometry(160, 160),
      new THREE.MeshStandardMaterial({
        color: 0x0b1118,
        metalness: 0.12,
        roughness: 0.9,
        side: THREE.DoubleSide,
      }),
    );
    ground.name = "ground";
    ground.rotation.x = -Math.PI / 2;
    ground.receiveShadow = true;
    scene.add(ground);

    const grid = new THREE.GridHelper(160, 160, 0x2c7cff, 0x263746);
    grid.position.y = 0.012;
    const gridMaterials = Array.isArray(grid.material) ? grid.material : [grid.material];
    gridMaterials.forEach((material) => {
      material.transparent = true;
      material.opacity = 0.34;
    });
    scene.add(grid);

    const rover = createRover();
    roverRef.current = rover;
    scene.add(rover);

    const line = new THREE.Line(
      new THREE.BufferGeometry(),
      new THREE.LineBasicMaterial({
        color: 0x44a6ff,
        transparent: true,
        opacity: 0.92,
      }),
    );
    line.name = "ground-truth-trajectory";
    trajectoryRef.current = line;
    scene.add(line);

    const resize = () => {
      const width = Math.max(host.clientWidth, 1);
      const height = Math.max(host.clientHeight, 1);
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
      renderer.setSize(width, height, false);
    };
    const observer = new ResizeObserver(resize);
    observer.observe(host);
    resize();

    const clock = new THREE.Clock();
    let animationFrame = 0;
    const render = () => {
      animationFrame = window.requestAnimationFrame(render);
      const delta = Math.min(clock.getDelta(), 0.1);
      const smoothing = 1 - Math.exp(-8 * delta);
      rover.position.lerp(desiredPositionRef.current, smoothing);
      rover.rotation.y = THREE.MathUtils.lerp(rover.rotation.y, desiredYawRef.current, smoothing);
      if (followRef.current) {
        const nextTarget = rover.position.clone();
        nextTarget.y += 0.72;
        controls.target.lerp(nextTarget, smoothing);
      }
      controls.update();
      renderer.render(scene, camera);
    };
    render();

    return () => {
      window.cancelAnimationFrame(animationFrame);
      observer.disconnect();
      controls.dispose();
      renderer.dispose();
      renderer.domElement.remove();
      disposeScene(scene);
      cameraRef.current = null;
      controlsRef.current = null;
      roverRef.current = null;
      trajectoryRef.current = null;
    };
  }, []);

  const resetCamera = () => {
    const camera = cameraRef.current;
    const controls = controlsRef.current;
    if (!camera || !controls) return;
    camera.position.copy(CAMERA_POSITION);
    const rover = roverRef.current;
    controls.target.copy(rover ? rover.position.clone().add(new THREE.Vector3(0, 0.55, 0)) : CAMERA_TARGET);
    controls.update();
  };

  return (
    <div className="polygon-rover-scene" aria-label="Live 3D-полигон">
      <div ref={hostRef} className="polygon-rover-scene__viewport">
        {renderError ? <p className="polygon-rover-scene__error">{renderError}</p> : null}
      </div>
      <div className="polygon-rover-scene__toolbar">
        <button type="button" onClick={() => setFollowRover((value) => !value)}>
          {followRover ? "Следовать за ровером" : "Свободная камера"}
        </button>
        <button type="button" onClick={resetCamera}>Сбросить ракурс</button>
      </div>
      <div className="polygon-rover-scene__help">
        <span>ЛКМ · вращение</span>
        <span>Колесо · масштаб</span>
        <span>ПКМ · панорама</span>
      </div>
      <div className="polygon-rover-scene__frames">
        <span><i data-axis="east" />E</span>
        <span><i data-axis="north" />N</span>
        <span><i data-axis="up" />U</span>
        <small>map_enu · base_link_flu · ground truth</small>
      </div>
      {!live ? (
        <div className="polygon-rover-scene__idle">
          <strong>{workerAvailable ? "Ackermann Rover готов" : "Нет связи с worker"}</strong>
          <span>
            {workerAvailable
              ? "Запустите PX4/Gazebo — модель останется доступна для осмотра."
              : "Прогоны и датасеты доступны; запуск и управление откроются после подтверждения Simulation Worker."}
          </span>
        </div>
      ) : null}
    </div>
  );
}
