import { useMemo, useRef, useState } from 'react';
import * as THREE from 'three';
import { Html, Line, useCursor } from '@react-three/drei';
import { useFrame } from '@react-three/fiber';
import type { ThreeEvent } from '@react-three/fiber';
import type { WorldConnector } from '../types';
import { useBuilder } from '../store/builderStore';
import { getWorldConnectors } from '../utils/connectors';
import { beginPartDrag, dragCtx } from '../utils/dragState';

/**
 * ConnectorHandles — grabbable spheres at every connector of the selected
 * parts. Dragging a handle moves the WHOLE part (and the rest of the
 * selection, rigidly) so the grabbed connector follows the cursor; the
 * DragPlane in SceneCanvas does the actual plane math, live-highlights the
 * nearest open mating connector, and magnetically snaps on release.
 */
export function ConnectorHandles() {
  const selectedIds = useBuilder((s) => s.selectedIds);
  const parts = useBuilder((s) => s.parts);
  const placingType = useBuilder((s) => s.placingType);
  const tool = useBuilder((s) => s.tool);

  if (placingType || tool === 'measure' || !selectedIds.length) return null;

  const conns: WorldConnector[] = [];
  for (const id of selectedIds) {
    const part = parts[id];
    if (part) conns.push(...getWorldConnectors(part));
  }
  const pipeIds = selectedIds.filter((id) => parts[id]?.type === 'pipe');

  return (
    <>
      {pipeIds.map((id) => (
        <PipeStretchHandles key={`pipe-stretch-${id}`} partId={id} />
      ))}
      {conns.map((c) => (
        <Handle key={c.key} conn={c} dimmed={parts[c.partId]?.type === 'pipe'} />
      ))}
      <SnapTargetHighlight />
    </>
  );
}

const dirQuat = (dir: [number, number, number]) =>
  new THREE.Quaternion().setFromUnitVectors(
    new THREE.Vector3(0, 0, 1),
    new THREE.Vector3(...dir).normalize(),
  );

function Handle({ conn, dimmed = false }: { conn: WorldConnector; dimmed?: boolean }) {
  const draggingId = useBuilder((s) => s.draggingId);
  const [hovered, setHovered] = useState(false);
  useCursor(hovered, 'grab');

  const quat = useMemo(() => dirQuat(conn.dir), [conn.dir[0], conn.dir[1], conn.dir[2]]);
  const isDragSource = draggingId !== null && dragCtx.connectorKey === conn.key;
  const active = hovered || isDragSource;

  const onPointerDown = (e: ThreeEvent<PointerEvent>) => {
    if (e.button !== 0) return;
    const s = useBuilder.getState();
    if (s.placingType || s.tool === 'measure') return;
    e.stopPropagation();

    const startX = e.clientX;
    const startY = e.clientY;

    const handlePointerUp = (upEvt: PointerEvent) => {
      window.removeEventListener('pointerup', handlePointerUp);
      const dx = upEvt.clientX - startX;
      const dy = upEvt.clientY - startY;
      const dist = Math.hypot(dx, dy);
      if (dist < 4) {
        s.spawnPipeAtConnector(conn.key);
      }
    };
    window.addEventListener('pointerup', handlePointerUp);

    beginPartDrag({
      partId: conn.partId,
      grabPoint: conn.pos,
      mode: 'handle',
      connectorKey: conn.key,
    });
  };

  return (
    <group position={conn.pos}>
      {/* Generous invisible hit sphere (raycasts despite invisible material). */}
      <mesh
        userData={{ connectorHandle: true }}
        onPointerDown={onPointerDown}
        onPointerOver={(e) => {
          // No stopPropagation (it would starve the DragPlane of move events
          // mid-drag). Handles take hover priority over part bodies when they
          // are roughly as close as the nearest hit (mirrors the
          // PartMesh pointerdown guard).
          if (useBuilder.getState().draggingId) return;
          const firstHandle = e.intersections.find(
            (i) => i.object.userData?.connectorHandle,
          );
          if (
            firstHandle?.object !== e.object ||
            firstHandle.distance > (e.intersections[0]?.distance ?? Infinity) + 0.5
          )
            return;
          setHovered(true);
        }}
        onPointerOut={() => setHovered(false)}
      >
        <sphereGeometry args={[0.22, 10, 10]} />
        <meshBasicMaterial visible={false} />
      </mesh>
      {/* Visible grab knob, always on top so it's never buried in geometry. */}
      <mesh scale={active ? 1.4 : 1} renderOrder={998}>
        <sphereGeometry args={[dimmed ? 0.05 : 0.075, 14, 14]} />
        <meshBasicMaterial
          color={active ? '#bae6fd' : '#38bdf8'}
          toneMapped={false}
          depthTest={false}
          transparent
          opacity={dimmed ? (active ? 0.85 : 0.45) : 0.95}
        />
      </mesh>
      {/* Direction ring hinting how the connector faces. */}
      <mesh quaternion={quat} scale={active ? 1.25 : 1} renderOrder={998}>
        <torusGeometry args={[dimmed ? 0.11 : 0.15, dimmed ? 0.014 : 0.018, 8, 24]} />
        <meshBasicMaterial
          color="#38bdf8"
          toneMapped={false}
          depthTest={false}
          transparent
          opacity={dimmed ? (active ? 0.8 : 0.35) : active ? 0.95 : 0.55}
        />
      </mesh>
    </group>
  );
}

function PipeStretchHandles({ partId }: { partId: string }) {
  const part = useBuilder((s) => s.parts[partId]);
  const draggingId = useBuilder((s) => s.draggingId);
  if (!part || part.type !== 'pipe') return null;

  const conns = getWorldConnectors(part);
  const a = conns.find((c) => c.connectorId === 'a');
  const b = conns.find((c) => c.connectorId === 'b');
  if (!a || !b) return null;

  const len = part.params.length ?? 0;
  const mid: [number, number, number] = [
    (a.pos[0] + b.pos[0]) / 2,
    (a.pos[1] + b.pos[1]) / 2 + 0.28,
    (a.pos[2] + b.pos[2]) / 2,
  ];
  const editing = draggingId === partId && !!dragCtx.pipeStretch;

  return (
    <group>
      <Line
        points={[a.pos, b.pos]}
        color={editing ? '#7dd3fc' : '#38bdf8'}
        lineWidth={2}
        transparent
        opacity={editing ? 0.95 : 0.55}
      />
      <PipeEndHandle conn={a} />
      <PipeEndHandle conn={b} />
      <Html position={mid} center distanceFactor={12}>
        <div
          className={`rounded-full border px-2 py-1 text-[10px] font-semibold whitespace-nowrap shadow-lg backdrop-blur ${
            editing
              ? 'border-sky-400 bg-sky-300/95 text-sky-950'
              : 'border-zinc-700 bg-zinc-950/90 text-zinc-200'
          }`}
        >
          {len.toFixed(2)} ft
          <span className="ml-1 text-zinc-400">{editing ? 'stretching' : 'drag ends to stretch'}</span>
        </div>
      </Html>
    </group>
  );
}

function PipeEndHandle({ conn }: { conn: WorldConnector }) {
  const draggingId = useBuilder((s) => s.draggingId);
  const [hovered, setHovered] = useState(false);
  useCursor(hovered, 'grab');
  const quat = useMemo(() => dirQuat(conn.dir), [conn.dir[0], conn.dir[1], conn.dir[2]]);
  const active = hovered || (draggingId !== null && dragCtx.connectorKey === conn.key);

  const onPointerDown = (e: ThreeEvent<PointerEvent>) => {
    if (e.button !== 0) return;
    const s = useBuilder.getState();
    if (s.placingType || s.tool === 'measure') return;
    e.stopPropagation();
    beginPartDrag({
      partId: conn.partId,
      grabPoint: conn.pos,
      mode: 'handle',
      connectorKey: conn.key,
    });
  };

  return (
    <group position={conn.pos}>
      <mesh
        userData={{ connectorHandle: true }}
        onPointerDown={onPointerDown}
        onPointerOver={(e) => {
          if (useBuilder.getState().draggingId) return;
          const firstHandle = e.intersections.find((i) => i.object.userData?.connectorHandle);
          if (
            firstHandle?.object !== e.object ||
            firstHandle.distance > (e.intersections[0]?.distance ?? Infinity) + 0.5
          )
            return;
          setHovered(true);
        }}
        onPointerOut={() => setHovered(false)}
      >
        <sphereGeometry args={[0.28, 12, 12]} />
        <meshBasicMaterial visible={false} />
      </mesh>
      <mesh quaternion={quat} scale={active ? 1.12 : 1} renderOrder={998}>
        <cylinderGeometry args={[0.05, 0.1, 0.18, 12]} />
        <meshBasicMaterial
          color={active ? '#e0f2fe' : '#7dd3fc'}
          toneMapped={false}
          depthTest={false}
          transparent
          opacity={0.98}
        />
      </mesh>
      <mesh quaternion={quat} position={[0, 0, 0.11]} scale={active ? 1.18 : 1} renderOrder={998}>
        <coneGeometry args={[0.08, 0.16, 12]} />
        <meshBasicMaterial
          color={active ? '#38bdf8' : '#0ea5e9'}
          toneMapped={false}
          depthTest={false}
          transparent
          opacity={0.95}
        />
      </mesh>
    </group>
  );
}

/** Pulsing glow ring on the nearest open connector during a handle drag. */
function SnapTargetHighlight() {
  const key = useBuilder((s) => s.snapTargetKey);
  const parts = useBuilder((s) => s.parts);
  const ref = useRef<THREE.Group>(null);

  useFrame(({ clock }) => {
    if (ref.current) {
      const k = 1 + 0.18 * Math.sin(clock.elapsedTime * 7);
      ref.current.scale.setScalar(k);
    }
  });

  if (!key) return null;
  const partId = key.split(':')[0];
  const part = parts[partId];
  if (!part) return null;
  const conn = getWorldConnectors(part).find((c) => c.key === key);
  if (!conn) return null;

  return (
    <group position={conn.pos}>
      <group ref={ref}>
        <mesh quaternion={dirQuat(conn.dir)} renderOrder={999}>
          <torusGeometry args={[0.24, 0.035, 10, 28]} />
          <meshBasicMaterial color="#34d399" toneMapped={false} depthTest={false} transparent opacity={0.95} />
        </mesh>
        <mesh renderOrder={999}>
          <sphereGeometry args={[0.1, 12, 12]} />
          <meshBasicMaterial color="#6ee7b7" toneMapped={false} depthTest={false} transparent opacity={0.9} />
        </mesh>
      </group>
    </group>
  );
}
