// Island3D.tsx
import { useRef, useState, useMemo } from "react";
import { useFrame } from "@react-three/fiber";
import { Float, Text, Html, Sparkles, Billboard } from "@react-three/drei";
import * as THREE from "three";
import type { QuestNode } from "../types/quest";

export interface Island3DProps {
  node: QuestNode;
  position: [number, number, number];
  accent: string;
  terrain: { l: string; m: string; d: string; s: string };
  onTap: (node: QuestNode) => void;
  onClaim: (node: QuestNode) => void;
  index: number;
  isCurrent?: boolean;
  modalOpen?: boolean;
}

// ─── Seeded RNG ───────────────────────────────────────────────────────────────
const makeRng = (seed: number) => {
  let s = ((seed + 1) * 1664525 + 1013904223) >>> 0;
  return () => {
    s ^= s << 13;
    s ^= s >>> 17;
    s ^= s << 5;
    return (s >>> 0) / 4294967296;
  };
};

// ─── Island shape ─────────────────────────────────────────────────────────────
const makeIslandShape = (seed: number, radiusBase = 1.0): THREE.Shape => {
  const rng = makeRng(seed);
  const sides = 8 + Math.floor(rng() * 5);
  const pts: THREE.Vector2[] = [];
  for (let i = 0; i < sides; i++) {
    const angle = (i / sides) * Math.PI * 2 - Math.PI / 2;
    const radius = radiusBase * (0.78 + rng() * 0.48);
    pts.push(
      new THREE.Vector2(Math.cos(angle) * radius, Math.sin(angle) * radius),
    );
  }
  return new THREE.Shape(pts);
};

// ─── Status palette ───────────────────────────────────────────────────────────
const PALETTE = {
  LOCKED: { top: "#778fa0", side: "#526070", emissive: "#000000", ei: 0 },
  ACTIVE: { top: "", side: "", emissive: "#ffffff", ei: 0 },
  CLAIMABLE: { top: "#f59e0b", side: "#d97706", emissive: "#fde68a", ei: 0.3 },
  COMPLETED: { top: "#d4a843", side: "#a07830", emissive: "#ffe8a0", ei: 0.15 },
};

// ─── Card scale ───────────────────────────────────────────────────────────────
const CARD_SCALE = 0.8;
const C = {
  width: 175 * CARD_SCALE,
  borderRadius: 13 * CARD_SCALE,
  padV: 10 * CARD_SCALE,
  padH: 12 * CARD_SCALE,
  padVb: 9 * CARD_SCALE,
  nameSize: 12.5 * CARD_SCALE,
  xpSize: 11 * CARD_SCALE,
  statusSize: 10 * CARD_SCALE,
  xpPadV: 2 * CARD_SCALE,
  xpPadH: 8 * CARD_SCALE,
  xpRadius: 100 * CARD_SCALE,
  gapRow: 6 * CARD_SCALE,
  nameMb: 7 * CARD_SCALE,
  barMt: 8 * CARD_SCALE,
  barH: 4 * CARD_SCALE,
  btnMt: 9 * CARD_SCALE,
  btnPadV: 6 * CARD_SCALE,
  btnSize: 11 * CARD_SCALE,
  btnRadius: 9 * CARD_SCALE,
  posX: 2.1 * CARD_SCALE,
};

const REQ_EMOJI: Record<string, string> = {
  questions: "❓",
  accuracy: "🎯",
  streak: "🔥",
  sessions: "📚",
  topics: "🗺️",
  xp: "⚡",
  leaderboard: "🏆",
};

// ─── Palm tree ────────────────────────────────────────────────────────────────
const PalmTree = ({
  position,
  scale,
  seed,
}: {
  position: [number, number, number];
  scale: number;
  seed: number;
}) => {
  const rng = makeRng(seed * 91 + 3);
  const trunkLean = (rng() - 0.5) * 0.35;
  const trunkHeight = 0.55 + rng() * 0.25;
  const frondCount = 5 + Math.floor(rng() * 3);

  const trunkGeo = useMemo(
    () => new THREE.CylinderGeometry(0.028, 0.045, trunkHeight, 6),
    [trunkHeight],
  );
  const frondGeo = useMemo(() => new THREE.ConeGeometry(0.22, 0.18, 4), []);

  return (
    <group position={position} scale={scale}>
      <mesh
        geometry={trunkGeo}
        position={[0, trunkHeight / 2, 0]}
        rotation-z={trunkLean}
        castShadow
      >
        <meshStandardMaterial color="#7c5c3a" roughness={0.9} />
      </mesh>
      {Array.from({ length: frondCount }, (_, i) => {
        const angle = (i / frondCount) * Math.PI * 2;
        const tilt = 0.5 + rng() * 0.3;
        const r = 0.18 + rng() * 0.06;
        return (
          <mesh
            key={i}
            geometry={frondGeo}
            position={[
              Math.sin(angle) * r + Math.sin(trunkLean) * trunkHeight,
              trunkHeight + 0.04,
              Math.cos(angle) * r,
            ]}
            rotation={[tilt, angle, 0]}
            castShadow
          >
            <meshStandardMaterial
              color={`hsl(${120 + Math.floor(rng() * 30)}, 65%, ${28 + Math.floor(rng() * 14)}%)`}
              roughness={0.8}
            />
          </mesh>
        );
      })}
      {rng() > 0.4 && (
        <mesh
          position={[
            Math.sin(trunkLean) * trunkHeight * 0.9,
            trunkHeight - 0.05,
            0,
          ]}
        >
          <sphereGeometry args={[0.045, 6, 6]} />
          <meshStandardMaterial color="#5c3d1a" roughness={0.9} />
        </mesh>
      )}
    </group>
  );
};

// ─── Pine / jungle tree ───────────────────────────────────────────────────────
const PineTree = ({
  position,
  scale,
  seed,
}: {
  position: [number, number, number];
  scale: number;
  seed: number;
}) => {
  const rng = makeRng(seed * 37 + 11);
  const layers = 2 + Math.floor(rng() * 2);
  const hue = 110 + Math.floor(rng() * 40);
  return (
    <group position={position} scale={scale}>
      <mesh position={[0, 0.15, 0]} castShadow>
        <cylinderGeometry args={[0.03, 0.05, 0.3, 5]} />
        <meshStandardMaterial color="#5c3d1a" roughness={0.95} />
      </mesh>
      {Array.from({ length: layers }, (_, i) => (
        <mesh key={i} position={[0, 0.28 + i * 0.22, 0]} castShadow>
          <coneGeometry args={[0.28 - i * 0.06, 0.32, 7]} />
          <meshStandardMaterial
            color={`hsl(${hue}, 60%, ${22 + i * 6}%)`}
            roughness={0.85}
          />
        </mesh>
      ))}
    </group>
  );
};

// ─── Rock cluster ─────────────────────────────────────────────────────────────
const RockCluster = ({
  position,
  scale,
  seed,
}: {
  position: [number, number, number];
  scale: number;
  seed: number;
}) => {
  const rng = makeRng(seed * 53 + 7);
  const count = 2 + Math.floor(rng() * 3);
  return (
    <group position={position} scale={scale}>
      {Array.from({ length: count }, (_, i) => {
        const angle = rng() * Math.PI * 2;
        const r = rng() * 0.12;
        const s = 0.06 + rng() * 0.1;
        const grey = Math.floor(90 + rng() * 80);
        return (
          <mesh
            key={i}
            position={[Math.sin(angle) * r, s * 0.5, Math.cos(angle) * r]}
            rotation={[rng() * 0.5, rng() * Math.PI, rng() * 0.5]}
            scale={[s * (0.8 + rng() * 0.4), s, s * (0.8 + rng() * 0.4)]}
            castShadow
          >
            <dodecahedronGeometry args={[1, 0]} />
            <meshStandardMaterial
              color={`rgb(${grey},${grey - 5},${grey - 10})`}
              roughness={0.95}
              metalness={0.05}
            />
          </mesh>
        );
      })}
    </group>
  );
};

// ─── Mountain ─────────────────────────────────────────────────────────────────
const Mountain = ({
  position,
  scale,
  seed,
  hasSnow,
}: {
  position: [number, number, number];
  scale: number;
  seed: number;
  hasSnow: boolean;
}) => {
  const rng = makeRng(seed * 17 + 5);
  const peakH = 0.55 + rng() * 0.35;
  const baseR = 0.38 + rng() * 0.18;
  const sides = 5 + Math.floor(rng() * 3);
  const rockH = Math.floor(20 + rng() * 30);

  const bodyGeo = useMemo(
    () => new THREE.ConeGeometry(baseR, peakH, sides),
    [baseR, peakH, sides],
  );
  const capGeo = useMemo(
    () => new THREE.ConeGeometry(baseR * 0.28, peakH * 0.28, sides),
    [baseR, peakH, sides],
  );

  return (
    <group position={position} scale={scale}>
      <mesh geometry={bodyGeo} position={[0, peakH / 2, 0]} castShadow>
        <meshStandardMaterial
          color={`hsl(${rockH}, 18%, ${28 + Math.floor(rng() * 14)}%)`}
          roughness={0.92}
        />
      </mesh>
      {hasSnow && (
        <mesh geometry={capGeo} position={[0, peakH * 0.78, 0]} castShadow>
          <meshStandardMaterial color="#f0f4ff" roughness={0.6} />
        </mesh>
      )}
      {/* Lava glow for volcanic */}
      {!hasSnow && rng() > 0.55 && (
        <mesh position={[0, peakH * 0.92, 0]}>
          <sphereGeometry args={[baseR * 0.12, 8, 8]} />
          <meshStandardMaterial
            color="#ff4500"
            emissive="#ff4500"
            emissiveIntensity={1.5}
            transparent
            opacity={0.85}
          />
        </mesh>
      )}
    </group>
  );
};

// ─── Beach strip ─────────────────────────────────────────────────────────────
const BeachStrip = ({ seed, radius }: { seed: number; radius: number }) => {
  const rng = makeRng(seed * 29 + 13);
  const arcStart = rng() * Math.PI * 2;
  const arcLen = 0.6 + rng() * 1.2;
  const segments = 18;

  const geo = useMemo(() => {
    const shape = new THREE.Shape();
    const r0 = radius * 0.78;
    const r1 = radius * 1.0;
    shape.moveTo(Math.cos(arcStart) * r0, Math.sin(arcStart) * r0);
    for (let i = 0; i <= segments; i++) {
      const a = arcStart + (i / segments) * arcLen;
      const jitter = 0.92 + Math.sin(i * 7.3) * 0.08;
      shape.lineTo(Math.cos(a) * r1 * jitter, Math.sin(a) * r1 * jitter);
    }
    for (let i = segments; i >= 0; i--) {
      const a = arcStart + (i / segments) * arcLen;
      shape.lineTo(Math.cos(a) * r0, Math.sin(a) * r0);
    }
    shape.closePath();
    const g = new THREE.ExtrudeGeometry(shape, {
      depth: 0.06,
      bevelEnabled: false,
    });
    g.rotateX(-Math.PI / 2);
    g.translate(0, 0.02, 0);
    return g;
  }, [seed, radius]);

  return (
    <mesh geometry={geo} receiveShadow>
      <meshStandardMaterial color="#e8d5a3" roughness={0.95} />
    </mesh>
  );
};

// ─── Lagoon ───────────────────────────────────────────────────────────────────
const Lagoon = ({ seed }: { seed: number }) => {
  const rng = makeRng(seed * 61 + 19);
  const r = 0.22 + rng() * 0.18;
  const ox = (rng() - 0.5) * 0.5;
  const oz = (rng() - 0.5) * 0.5;
  // Compute the ellipse y-radius using the RNG (mirrors previous ellipseGeometry usage)
  const ry = r * (0.7 + rng() * 0.3);
  const meshRef = useRef<THREE.Mesh>(null!);

  useFrame(({ clock }) => {
    if (meshRef.current) {
      (
        meshRef.current.material as THREE.MeshStandardMaterial
      ).emissiveIntensity = 0.18 + Math.sin(clock.elapsedTime * 1.1) * 0.06;
    }
  });
  return (
    <mesh
      ref={meshRef}
      rotation-x={-Math.PI / 2}
      position={[ox, 0.04, oz]}
      // Use a circleGeometry and non-uniform scale to create an ellipse
      scale={[r, ry, 1]}
    >
      <circleGeometry args={[1, 24]} />
      <meshStandardMaterial
        color="#1a9fc0"
        emissive="#0bb4d4"
        emissiveIntensity={0.2}
        roughness={0.08}
        metalness={0.3}
        transparent
        opacity={0.82}
        depthWrite={false}
      />
    </mesh>
  );
};

// ─── Flower patch ─────────────────────────────────────────────────────────────
const FlowerPatch = ({
  position,
  seed,
}: {
  position: [number, number, number];
  seed: number;
}) => {
  const rng = makeRng(seed * 43 + 17);
  const count = 3 + Math.floor(rng() * 4);
  const COLORS = [
    "#ff6b9d",
    "#ffd93d",
    "#ff8c42",
    "#c77dff",
    "#ff4d6d",
    "#ff9f1c",
  ];
  return (
    <group position={position}>
      {Array.from({ length: count }, (_, i) => {
        const angle = rng() * Math.PI * 2;
        const r = rng() * 0.18;
        const color = COLORS[Math.floor(rng() * COLORS.length)];
        return (
          <group
            key={i}
            position={[Math.sin(angle) * r, 0, Math.cos(angle) * r]}
          >
            <mesh position={[0, 0.06, 0]}>
              <cylinderGeometry args={[0.008, 0.008, 0.12, 4]} />
              <meshStandardMaterial color="#3a7d44" roughness={0.9} />
            </mesh>
            <mesh position={[0, 0.13, 0]}>
              <sphereGeometry args={[0.038, 6, 6]} />
              <meshStandardMaterial
                color={color}
                roughness={0.7}
                emissive={color}
                emissiveIntensity={0.15}
              />
            </mesh>
          </group>
        );
      })}
    </group>
  );
};

// ─── Island hut ───────────────────────────────────────────────────────────────
const IslandHut = ({
  position,
  scale,
  seed,
}: {
  position: [number, number, number];
  scale: number;
  seed: number;
}) => {
  const rng = makeRng(seed * 73 + 23);
  const roofColor = rng() > 0.5 ? "#c17f3a" : "#8b5e3c";
  return (
    <group position={position} scale={scale}>
      <mesh position={[0, 0.1, 0]} castShadow>
        <boxGeometry args={[0.22, 0.2, 0.22]} />
        <meshStandardMaterial color="#d4a96a" roughness={0.9} />
      </mesh>
      <mesh position={[0, 0.25, 0]} castShadow>
        <coneGeometry args={[0.18, 0.16, 4]} />
        <meshStandardMaterial color={roofColor} roughness={0.85} />
      </mesh>
      <mesh position={[0, 0.07, 0.112]}>
        <boxGeometry args={[0.06, 0.1, 0.01]} />
        <meshStandardMaterial color="#3d1c0c" roughness={0.95} />
      </mesh>
    </group>
  );
};

// ─── Terrain orchestrator ─────────────────────────────────────────────────────
const IslandTerrain = ({
  seed,
  isLocked,
}: {
  seed: number;
  isLocked: boolean;
}) => {
  const rng = makeRng(seed * 1000 + 42);

  // Six archetypes: 0=tropical 1=volcanic 2=snowy 3=savanna 4=jungle 5=desert
  const archetype = Math.floor(rng() * 6);
  const hasMountain = archetype === 1 || archetype === 2 || rng() > 0.55;
  const hasSnow = archetype === 2;
  const palmCount =
    archetype === 0 || archetype === 3
      ? 2 + Math.floor(rng() * 3)
      : Math.floor(rng() * 2);
  const pineCount =
    archetype === 4 ? 2 + Math.floor(rng() * 3) : Math.floor(rng() * 2);
  const rockCount = 1 + Math.floor(rng() * 3);
  const hasBeach = archetype !== 2 && rng() > 0.3;
  const hasLagoon = rng() > 0.6;
  const flowerCount =
    archetype === 4 || archetype === 0
      ? 2 + Math.floor(rng() * 3)
      : Math.floor(rng() * 2);
  const hasHut = rng() > 0.65;

  if (isLocked) return null;

  // Each call advances rng to a stable position for that feature
  const spot = (spread = 0.65): [number, number, number] => {
    const a = rng() * Math.PI * 2;
    const r = rng() * spread;
    return [Math.sin(a) * r, 0, Math.cos(a) * r];
  };

  return (
    <group position={[0, 0.3, 0]}>
      {hasBeach && <BeachStrip seed={seed + 1} radius={0.9} />}
      {hasLagoon && <Lagoon seed={seed + 2} />}

      {hasMountain && (
        <Mountain
          position={spot(0.45)}
          scale={0.7 + rng() * 0.5}
          seed={seed + 3}
          hasSnow={hasSnow}
        />
      )}

      {Array.from({ length: palmCount }, (_, i) => (
        <PalmTree
          key={`palm-${i}`}
          position={spot(0.65)}
          scale={0.8 + rng() * 0.5}
          seed={seed + 10 + i}
        />
      ))}
      {Array.from({ length: pineCount }, (_, i) => (
        <PineTree
          key={`pine-${i}`}
          position={spot(0.62)}
          scale={0.75 + rng() * 0.45}
          seed={seed + 20 + i}
        />
      ))}
      {Array.from({ length: rockCount }, (_, i) => (
        <RockCluster
          key={`rock-${i}`}
          position={spot(0.7)}
          scale={0.7 + rng() * 0.6}
          seed={seed + 30 + i}
        />
      ))}
      {Array.from({ length: flowerCount }, (_, i) => (
        <FlowerPatch
          key={`flower-${i}`}
          position={spot(0.6)}
          seed={seed + 40 + i}
        />
      ))}
      {hasHut && (
        <IslandHut
          position={spot(0.4)}
          scale={0.9 + rng() * 0.3}
          seed={seed + 50}
        />
      )}
    </group>
  );
};

// ─── Current island marker ────────────────────────────────────────────────────
const CurrentMarker = ({ accent }: { accent: string }) => {
  const groupRef = useRef<THREE.Group>(null!);
  const innerRef = useRef<THREE.Group>(null!);
  const ringRef = useRef<THREE.Mesh>(null!);
  const beamRef = useRef<THREE.Mesh>(null!);

  const accentColor = useMemo(() => new THREE.Color(accent), [accent]);
  const goldColor = useMemo(() => new THREE.Color("#fbbf24"), []);
  const torusGeo = useMemo(
    () => new THREE.TorusGeometry(0.38, 0.045, 12, 48),
    [],
  );
  const spokeGeo = useMemo(
    () => new THREE.CylinderGeometry(0.022, 0.022, 0.6, 8),
    [],
  );
  const orbGeo = useMemo(() => new THREE.SphereGeometry(0.1, 16, 16), []);
  const beamGeo = useMemo(
    () => new THREE.CylinderGeometry(0.018, 0.05, 1.6, 8, 1, true),
    [],
  );
  const haloGeo = useMemo(() => new THREE.CircleGeometry(0.55, 40), []);

  useFrame(({ clock }) => {
    const t = clock.elapsedTime;
    if (groupRef.current)
      groupRef.current.position.y = 2.1 + Math.sin(t * 1.4) * 0.18;
    if (innerRef.current) innerRef.current.rotation.y = t * 0.55;
    if (ringRef.current)
      (
        ringRef.current.material as THREE.MeshStandardMaterial
      ).emissiveIntensity = 0.6 + Math.sin(t * 2.2) * 0.35;
    if (beamRef.current)
      (beamRef.current.material as THREE.MeshStandardMaterial).opacity =
        0.18 + Math.sin(t * 1.8 + 1) * 0.1;
  });

  return (
    <group ref={groupRef} position={[0, 2.1, 0]}>
      <mesh ref={beamRef} geometry={beamGeo} position={[0, -0.5, 0]}>
        <meshStandardMaterial
          color={goldColor}
          emissive={goldColor}
          emissiveIntensity={0.8}
          transparent
          opacity={0.22}
          depthWrite={false}
          side={THREE.DoubleSide}
        />
      </mesh>
      <group ref={innerRef}>
        <mesh geometry={haloGeo} rotation-x={-Math.PI / 2}>
          <meshStandardMaterial
            color={accentColor}
            emissive={accentColor}
            emissiveIntensity={0.25}
            transparent
            opacity={0.12}
            depthWrite={false}
            side={THREE.DoubleSide}
          />
        </mesh>
        <mesh ref={ringRef} geometry={torusGeo} rotation-x={Math.PI / 2}>
          <meshStandardMaterial
            color={goldColor}
            emissive={goldColor}
            emissiveIntensity={0.7}
            roughness={0.2}
            metalness={0.8}
          />
        </mesh>
        {[0, Math.PI / 2, Math.PI, (3 * Math.PI) / 2].map((angle, i) => (
          <mesh
            key={i}
            geometry={spokeGeo}
            position={[Math.sin(angle) * 0.2, 0, Math.cos(angle) * 0.2]}
            rotation-z={angle === 0 || angle === Math.PI ? 0 : Math.PI / 2}
            rotation-y={angle}
          >
            <meshStandardMaterial
              color={goldColor}
              emissive={goldColor}
              emissiveIntensity={0.4}
              roughness={0.3}
              metalness={0.7}
            />
          </mesh>
        ))}
        {[
          { pos: [0, 0.38, 0] as [number, number, number], scale: 1.0 },
          { pos: [0, -0.38, 0] as [number, number, number], scale: 0.7 },
          { pos: [0.38, 0, 0] as [number, number, number], scale: 0.7 },
          { pos: [-0.38, 0, 0] as [number, number, number], scale: 0.7 },
        ].map(({ pos, scale }, i) => (
          <mesh key={i} position={pos} scale={scale}>
            <octahedronGeometry args={[0.09, 0]} />
            <meshStandardMaterial
              color={i === 0 ? "#ffffff" : goldColor}
              emissive={i === 0 ? "#ffffff" : goldColor}
              emissiveIntensity={i === 0 ? 1.2 : 0.6}
              roughness={0.1}
              metalness={0.9}
            />
          </mesh>
        ))}
        <mesh geometry={orbGeo}>
          <meshStandardMaterial
            color={accentColor}
            emissive={accentColor}
            emissiveIntensity={1.2}
            roughness={0.1}
            metalness={0.6}
          />
        </mesh>
      </group>
      <Billboard>
        <Text
          position={[0, 0.7, 0]}
          fontSize={0.18}
          color="#fbbf24"
          anchorX="center"
          anchorY="middle"
          outlineWidth={0.018}
          outlineColor="#000000"
          letterSpacing={0.08}
        >
          YOU ARE HERE
        </Text>
      </Billboard>
    </group>
  );
};

// ─── Island3D ─────────────────────────────────────────────────────────────────
export const Island3D = ({
  node,
  position,
  accent,
  terrain,
  onTap,
  onClaim,
  index,
  isCurrent = false,
  modalOpen = false,
}: Island3DProps) => {
  const topMeshRef = useRef<THREE.Mesh>(null!);
  const [hovered, setHovered] = useState(false);

  const status = (node.status?.toUpperCase() ??
    "LOCKED") as keyof typeof PALETTE;
  const isLocked = status === "LOCKED";
  const isClaimable = status === "CLAIMABLE";
  const isActive = status === "ACTIVE";
  const isCompleted = status === "COMPLETED";
  const seed = index * 13 + 7;

  const { topGeo, cliffGeo } = useMemo(() => {
    const topShape = makeIslandShape(seed, 1.0);
    const topGeo = new THREE.ExtrudeGeometry(topShape, {
      depth: 0.32,
      bevelEnabled: true,
      bevelThickness: 0.12,
      bevelSize: 0.08,
      bevelSegments: 5,
    });
    topGeo.rotateX(-Math.PI / 2);

    const cliffShape = makeIslandShape(seed, 1.14);
    const cliffGeo = new THREE.ExtrudeGeometry(cliffShape, {
      depth: 0.58,
      bevelEnabled: true,
      bevelThickness: 0.06,
      bevelSize: 0.04,
      bevelSegments: 2,
    });
    cliffGeo.rotateX(-Math.PI / 2);
    cliffGeo.translate(0, -0.3, 0);
    return { topGeo, cliffGeo };
  }, [seed]);

  const pal = PALETTE[status];
  const topColor = isActive
    ? terrain.l
    : isCompleted
      ? PALETTE.COMPLETED.top
      : pal.top;
  const sideColor = isActive
    ? terrain.m
    : isCompleted
      ? PALETTE.COMPLETED.side
      : pal.side;

  useFrame((_, dt) => {
    if (!topMeshRef.current) return;
    const target = hovered && !isLocked ? 1.07 : 1.0;
    const curr = topMeshRef.current.scale.x;
    topMeshRef.current.scale.setScalar(
      curr + (target - curr) * (1 - Math.exp(-14 * dt)),
    );
  });

  const emoji = isLocked
    ? "🔒"
    : isClaimable
      ? "📦"
      : isCompleted
        ? "✅"
        : (REQ_EMOJI[node.req_type] ?? "🏝️");
  const pct =
    node.req_target > 0
      ? Math.min(100, Math.round((node.current_value / node.req_target) * 100))
      : 0;
  const ringColor = isClaimable ? "#fbbf24" : isCompleted ? "#4ade80" : accent;

  return (
    <Float
      rotationIntensity={isLocked ? 0 : 0.05}
      floatIntensity={isLocked ? 0 : 0.35}
      speed={1.0 + (index % 4) * 0.2}
    >
      <group
        position={position}
        onPointerEnter={(e) => {
          e.stopPropagation();
          if (!isLocked) setHovered(true);
        }}
        onPointerLeave={(e) => {
          e.stopPropagation();
          setHovered(false);
        }}
        onClick={(e) => {
          e.stopPropagation();
          if (!isLocked) onTap(node);
        }}
      >
        {/* Top plateau */}
        <mesh ref={topMeshRef} geometry={topGeo} castShadow receiveShadow>
          <meshStandardMaterial
            color={topColor}
            emissive={pal.emissive}
            emissiveIntensity={hovered ? pal.ei * 2.2 : pal.ei}
            roughness={0.6}
            metalness={0.04}
          />
        </mesh>

        {/* Cliff */}
        <mesh geometry={cliffGeo} castShadow>
          <meshStandardMaterial
            color={sideColor}
            roughness={0.88}
            metalness={0.02}
            emissive={pal.emissive}
            emissiveIntensity={pal.ei * 0.25}
          />
        </mesh>

        {/* Procedural terrain */}
        <IslandTerrain seed={seed} isLocked={isLocked} />

        {/* Water-level glow ring */}
        {!isLocked && (
          <mesh rotation-x={-Math.PI / 2} position-y={-0.86}>
            <ringGeometry args={[1.3, 1.7, 48]} />
            <meshStandardMaterial
              color={ringColor}
              emissive={ringColor}
              emissiveIntensity={isClaimable ? 1.0 : 0.4}
              transparent
              opacity={isClaimable ? 0.65 : 0.25}
              side={THREE.DoubleSide}
              depthWrite={false}
            />
          </mesh>
        )}

        {/* Claimable sparkles */}
        {isClaimable && (
          <>
            <Sparkles
              count={30}
              scale={2.6}
              size={0.22}
              speed={1.2}
              color="#fbbf24"
            />
            <mesh rotation-x={-Math.PI / 2} position-y={0.05}>
              <ringGeometry args={[1.05, 1.4, 40]} />
              <meshStandardMaterial
                color="#fbbf24"
                emissive="#fbbf24"
                emissiveIntensity={0.8}
                transparent
                opacity={0.5}
                side={THREE.DoubleSide}
                depthWrite={false}
              />
            </mesh>
          </>
        )}

        {/* Hover ring */}
        {hovered && !isLocked && (
          <mesh rotation-x={-Math.PI / 2} position-y={0.06}>
            <ringGeometry args={[1.0, 1.65, 40]} />
            <meshStandardMaterial
              color="white"
              emissive="white"
              emissiveIntensity={0.5}
              transparent
              opacity={0.14}
              side={THREE.DoubleSide}
              depthWrite={false}
            />
          </mesh>
        )}

        {/* Emoji */}
        <Billboard>
          <Text
            position={[0, 1.3, 0]}
            fontSize={0.44}
            anchorX="center"
            anchorY="middle"
          >
            {emoji}
          </Text>
        </Billboard>

        {/* Current marker */}
        {isCurrent && <CurrentMarker accent={accent} />}

        {/* Info card */}
        <Html
          position={[C.posX, 0.1, 0]}
          transform={false}
          occlude={false}
          zIndexRange={modalOpen ? [0, 0] : [50, 0]}
          style={{
            pointerEvents: isLocked || modalOpen ? "none" : "auto",
            opacity: modalOpen ? 0 : 1,
            transition: "opacity 0.15s ease",
          }}
        >
          <div
            onClick={(e) => {
              e.stopPropagation();
              if (!isLocked) onTap(node);
            }}
            style={{
              width: C.width,
              background: isClaimable
                ? "rgba(28,18,4,0.96)"
                : isLocked
                  ? "rgba(12,18,28,0.72)"
                  : "rgba(5,12,24,0.93)",
              border: `1px solid ${
                isClaimable
                  ? "rgba(251,191,36,0.65)"
                  : isCompleted
                    ? "rgba(74,222,128,0.45)"
                    : isActive
                      ? `${accent}55`
                      : "rgba(255,255,255,0.1)"
              }`,
              borderLeft: `3px solid ${
                isClaimable
                  ? "#fbbf24"
                  : isCompleted
                    ? "#4ade80"
                    : isActive
                      ? accent
                      : "rgba(255,255,255,0.2)"
              }`,
              borderRadius: C.borderRadius,
              padding: `${C.padV}px ${C.padH}px ${C.padVb}px`,
              backdropFilter: "blur(16px)",
              WebkitBackdropFilter: "blur(16px)",
              boxShadow: isClaimable
                ? "0 0 24px rgba(251,191,36,0.28), 0 6px 20px rgba(0,0,0,0.6)"
                : "0 6px 20px rgba(0,0,0,0.55)",
              opacity: isLocked ? 0.5 : 1,
              cursor: isLocked ? "default" : "pointer",
              userSelect: "none",
              fontFamily: "'Nunito', 'Nunito Sans', sans-serif",
            }}
          >
            <div
              style={{
                fontWeight: 800,
                fontSize: C.nameSize,
                lineHeight: 1.3,
                marginBottom: C.nameMb,
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
                color: isClaimable
                  ? "#fde68a"
                  : isLocked
                    ? "rgba(255,255,255,0.28)"
                    : "rgba(255,255,255,0.93)",
              }}
            >
              {node.name ?? "—"}
            </div>

            <div
              style={{
                display: "flex",
                alignItems: "center",
                gap: C.gapRow,
                flexWrap: "wrap",
              }}
            >
              <span
                style={{
                  background: "rgba(251,191,36,0.13)",
                  border: "1px solid rgba(251,191,36,0.3)",
                  borderRadius: C.xpRadius,
                  padding: `${C.xpPadV}px ${C.xpPadH}px`,
                  fontSize: C.xpSize,
                  fontWeight: 900,
                  color: "#fbbf24",
                  whiteSpace: "nowrap",
                }}
              >
                ⚡ {node.reward_xp} XP
              </span>
              <span
                style={{
                  fontSize: C.statusSize,
                  fontWeight: 800,
                  textTransform: "uppercase",
                  letterSpacing: "0.07em",
                  whiteSpace: "nowrap",
                  color: isClaimable
                    ? "#fbbf24"
                    : isCompleted
                      ? "#4ade80"
                      : isActive
                        ? "rgba(255,255,255,0.5)"
                        : "rgba(255,255,255,0.18)",
                }}
              >
                {isClaimable
                  ? "✨ Claim!"
                  : isCompleted
                    ? "Done"
                    : isLocked
                      ? "Locked"
                      : `${pct}%`}
              </span>
            </div>

            {isActive && node.req_target > 0 && (
              <div
                style={{
                  marginTop: C.barMt,
                  height: C.barH,
                  background: "rgba(255,255,255,0.08)",
                  borderRadius: 100,
                  overflow: "hidden",
                }}
              >
                <div
                  style={{
                    height: "100%",
                    width: `${pct}%`,
                    background: `linear-gradient(90deg, ${accent}, #e2e8f0)`,
                    borderRadius: 100,
                    boxShadow: `0 0 6px ${accent}88`,
                  }}
                />
              </div>
            )}

            {isClaimable && (
              <button
                onClick={(e) => {
                  e.stopPropagation();
                  onClaim(node);
                }}
                style={{
                  width: "100%",
                  marginTop: C.btnMt,
                  padding: `${C.btnPadV}px 0`,
                  background: "linear-gradient(135deg, #fbbf24, #f59e0b)",
                  border: "none",
                  borderRadius: C.btnRadius,
                  cursor: "pointer",
                  fontFamily: "inherit",
                  fontSize: C.btnSize,
                  fontWeight: 900,
                  color: "#1a0e00",
                  letterSpacing: "0.04em",
                  boxShadow:
                    "0 2px 0 #d97706, 0 4px 14px rgba(251,191,36,0.28)",
                }}
              >
                ⚓ Open Chest
              </button>
            )}
          </div>
        </Html>
      </group>
    </Float>
  );
};
