import React, { useState, useEffect, useRef, useCallback } from 'react';
import { state, subscribe, addModule, emit, addConnection, removeModule, updateModulePosition, deserialize } from '../engine/state.js';
import { startAudio, stopAudio, connectWire, rebuildGraph } from '../engine/audioEngine.js';
import { getModuleDef } from '../engine/moduleRegistry.js';
import ModuleNode from '../components/ModuleNode.jsx';
import WireLayer from '../components/WireLayer.jsx';
import { playTarget, stopTarget, isTargetPlaying } from './targetAudio.js';
import LevelComplete from './LevelComplete.jsx';
import { completeLevel, saveLevelPatch, getLevelPatch, markHintUsed, wasHintUsed } from './gameState.js';
import { playLevelComplete, playFail, playHint, playEngineStart, playEngineStop, playNav } from '../engine/uiSounds.js';

export default function PuzzleView({ level, levelIndex, worldLevels, onBack, onNextLevel }) {
  const [, forceUpdate] = useState(0);
  const containerRef = useRef(null);
  const portPositions = useRef({});
  const [tempWire, setTempWire] = useState(null);
  const connectingRef = useRef(null);
  const [hintUsed, setHintUsed] = useState(false);
  const [showHint, setShowHint] = useState(false);
  const [result, setResult] = useState(null);
  const [targetPlaying, setTargetPlaying] = useState(false);

  useEffect(() => {
    const unsub = subscribe(() => {
      forceUpdate(n => n + 1);
      // Auto-save patch on every state change (debounced below)
      scheduleSave();
    });
    return unsub;
  }, [level.id]);

  // Debounced auto-save of the current patch
  const saveTimerRef = useRef(null);
  const scheduleSave = useCallback(() => {
    if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
    saveTimerRef.current = setTimeout(() => {
      if (state.modules.length > 0) {
        saveLevelPatch(level.id, state.modules, state.connections);
      }
    }, 1000);
  }, [level.id]);

  useEffect(() => {
    return () => {
      if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
    };
  }, []);

  useEffect(() => {
    loadLevel();
    return () => {
      stopAudio();
      stopTarget();
    };
  }, [level.id]);

  const loadLevel = useCallback((forceReset = false) => {
    // Check for a saved patch first (unless explicitly resetting)
    const saved = !forceReset ? getLevelPatch(level.id) : null;
    if (saved) {
      const data = {
        modules: saved.modules.map(m => ({
          id: m.id, type: m.type, x: m.x, y: m.y, params: { ...m.params },
        })),
        connections: saved.connections.map(c => ({ ...c })),
        camera: { camX: 0, camY: 0, zoom: 1 },
      };
      deserialize(data);
    } else {
      const data = {
        modules: (level.preplacedModules || []).map(m => ({
          id: m.id, type: m.type, x: m.x, y: m.y, params: { ...m.params },
        })),
        connections: [],
        camera: { camX: 0, camY: 0, zoom: 1 },
      };
      deserialize(data);
    }
    setResult(null);
    // Restore persisted hint state — no cheating by reloading!
    const hintPersisted = wasHintUsed(level.id);
    setHintUsed(hintPersisted);
    setShowHint(hintPersisted); // If they used it before, show it again
    if (state.isRunning) stopAudio();
  }, [level]);

  const handlePortPosition = useCallback((moduleId, portName, direction, el) => {
    const key = `${moduleId}-${portName}-${direction}`;
    portPositions.current[key] = el;
  }, []);

  const handleStartConnect = useCallback((info) => {
    connectingRef.current = info;
    const containerRect = containerRef.current.getBoundingClientRect();
    setTempWire({
      portType: info.portType,
      startX: info.startX - containerRect.left,
      startY: info.startY - containerRect.top,
      endX: info.startX - containerRect.left,
      endY: info.startY - containerRect.top,
    });
  }, []);

  // Robust port detection — searches all port-dots by bounding rect distance
  // instead of elementFromPoint (which gets blocked by SVG wire overlay)
  const findPortAtPoint = (clientX, clientY) => {
    const portDots = document.querySelectorAll('.port-dot[data-module-id]');
    let closest = null;
    let closestDist = 18;
    for (const dot of portDots) {
      const rect = dot.getBoundingClientRect();
      const cx = rect.left + rect.width / 2;
      const cy = rect.top + rect.height / 2;
      const dist = Math.sqrt((clientX - cx) ** 2 + (clientY - cy) ** 2);
      if (dist < closestDist) {
        closestDist = dist;
        closest = dot;
      }
    }
    return closest;
  };

  const handlePointerDown = useCallback((e) => {
    if (e.button === 1 || (e.button === 0 && e.altKey) || e.button === 2) {
      state.panning = true;
      state.panStart = { x: e.clientX - state.camX, y: e.clientY - state.camY };
      e.preventDefault();
    } else if (e.button === 0 && !connectingRef.current) {
      state.selectedModuleId = null;
      emit();
    }
  }, []);

  const handlePointerMove = useCallback((e) => {
    if (state.panning && state.panStart) {
      state.camX = e.clientX - state.panStart.x;
      state.camY = e.clientY - state.panStart.y;
      emit();
      return;
    }
    if (state.dragging) {
      const newX = e.clientX / state.zoom - state.dragging.offsetX;
      const newY = e.clientY / state.zoom - state.dragging.offsetY;
      updateModulePosition(state.dragging.moduleId, newX, newY);
      return;
    }
    if (connectingRef.current && containerRef.current) {
      const containerRect = containerRef.current.getBoundingClientRect();
      setTempWire(prev => prev ? {
        ...prev,
        endX: e.clientX - containerRect.left,
        endY: e.clientY - containerRect.top,
      } : null);
    }
  }, []);

  const handlePointerUp = useCallback((e) => {
    if (state.panning) {
      state.panning = false;
      state.panStart = null;
    }
    if (state.dragging) {
      state.dragging = null;
      emit();
    }
    if (connectingRef.current) {
      const portEl = findPortAtPoint(e.clientX, e.clientY);
      if (portEl) finishConnection(portEl);
      connectingRef.current = null;
      setTempWire(null);
    }
  }, []);

  const finishConnection = (portEl) => {
    const from = connectingRef.current;
    if (!from) return;
    const targetModuleId = parseInt(portEl.dataset.moduleId);
    const targetPort = portEl.dataset.portName;
    const targetDirection = portEl.dataset.portDirection;
    if (!targetModuleId || !targetPort || !targetDirection) return;
    if (targetModuleId === from.moduleId && targetPort === from.port) return;

    let fromMod, fromPort, toMod, toPort;
    if (from.direction === 'output' && targetDirection === 'input') {
      fromMod = from.moduleId; fromPort = from.port;
      toMod = targetModuleId; toPort = targetPort;
    } else if (from.direction === 'input' && targetDirection === 'output') {
      fromMod = targetModuleId; fromPort = targetPort;
      toMod = from.moduleId; toPort = from.port;
    } else return;

    const connId = addConnection(fromMod, fromPort, toMod, toPort);
    if (connId && state.isRunning) {
      const conn = state.connections.find(c => c.id === connId);
      if (conn) connectWire(conn);
    }
  };

  const handleWheel = useCallback((e) => {
    e.preventDefault();
    const delta = -e.deltaY * 0.001;
    state.zoom = Math.max(0.3, Math.min(3, state.zoom + delta));
    emit();
  }, []);

  const handleContextMenu = useCallback((e) => e.preventDefault(), []);

  // Zoom controls (Google Maps style)
  const handleZoomIn = useCallback(() => {
    state.zoom = Math.min(3, state.zoom * 1.25);
    emit();
  }, []);
  const handleZoomOut = useCallback(() => {
    state.zoom = Math.max(0.3, state.zoom / 1.25);
    emit();
  }, []);
  const handleZoomReset = useCallback(() => {
    state.zoom = 1;
    state.camX = 0;
    state.camY = 0;
    emit();
  }, []);

  const handleAddModule = (type) => {
    const x = (-state.camX + 250) / state.zoom + Math.random() * 30;
    const y = (-state.camY + 150) / state.zoom + Math.random() * 30;
    addModule(type, x, y);
    if (state.isRunning) rebuildGraph();
  };

  const handleToggleAudio = async () => {
    if (state.isRunning) {
      stopAudio();
      playEngineStop();
    } else {
      await startAudio();
      playEngineStart();
    }
    emit();
  };

  const handlePlayTarget = async () => {
    if (isTargetPlaying()) {
      stopTarget();
      setTargetPlaying(false);
    } else {
      setTargetPlaying(true);
      await playTarget(level.target);
      setTimeout(() => setTargetPlaying(false), (level.target.duration || 2) * 1000 + 100);
    }
  };

  // Reveal hint — PERMANENTLY caps this level at 2 stars (persisted, survives reload)
  const handleRevealHint = () => {
    setHintUsed(true);
    setShowHint(true);
    markHintUsed(level.id);
    playHint();
  };

  const handleCheck = () => {
    const mods = state.modules;
    const conns = state.connections;
    const checks = level.checks.map(check => ({
      ...check,
      passed: check.test(mods, conns),
    }));

    let stars = 0;
    for (const check of checks) {
      if (check.passed) stars = check.star;
      else break;
    }

    // Cap at 2 stars if hint was used
    if (hintUsed && stars > 2) stars = 2;

    setResult({ stars, checks, hintPenalty: hintUsed && stars >= 2 });

    if (stars >= 1) {
      completeLevel(level.id, stars);
      playLevelComplete();
    } else {
      playFail();
    }
  };

  const isLastLevel = levelIndex >= worldLevels.length - 1;

  return (
    <div className="gm-puzzle">
      {/* Top bar */}
      <div className="gm-puzzle-bar">
        <button className="gm-btn icon" onClick={() => { playNav(); onBack(); }}>← Mapa</button>
        <div className="gm-puzzle-title">
          <span className="gm-puzzle-num">{levelIndex + 1}/{worldLevels.length}</span>
          <span className="gm-puzzle-name">{level.title}</span>
        </div>
        <div className="gm-puzzle-actions">
          <button
            className={`gm-btn ${targetPlaying ? 'active' : 'target'}`}
            onClick={handlePlayTarget}
          >
            {targetPlaying ? '⏹ Parar' : '🎯 Objetivo'}
          </button>
          <button
            className={`gm-btn ${state.isRunning ? 'active' : ''}`}
            onClick={handleToggleAudio}
          >
            {state.isRunning ? '⏹ Parar' : '▶ Mi Sonido'}
          </button>
          <button className="gm-btn check" onClick={handleCheck}>
            ✓ Comprobar
          </button>
        </div>
      </div>

      <div className="gm-puzzle-content">
        {/* Left sidebar */}
        <div className="gm-puzzle-sidebar">
          {/* Description — always visible */}
          <div className="gm-concept-panel">
            <div className="gm-concept-header">
              <span>📖 Mision</span>
            </div>
            <div className="gm-concept-body">
              <p className="gm-concept-desc">{level.description}</p>
            </div>
          </div>

          {/* Hint — hidden, reveals with penalty */}
          <div className="gm-hint-panel">
            {!showHint ? (
              <button className="gm-hint-btn" onClick={handleRevealHint}>
                <span className="gm-hint-icon">💡</span>
                <span className="gm-hint-label">Mostrar Pista</span>
                <span className="gm-hint-penalty">max ★★</span>
              </button>
            ) : (
              <div className="gm-hint-revealed">
                <div className="gm-hint-header">
                  <span>💡 Pista</span>
                  <span className="gm-hint-penalty-tag">max ★★</span>
                </div>
                <p className="gm-hint-text">{level.concept}</p>
              </div>
            )}
          </div>

          {/* Objectives */}
          <div className="gm-objectives">
            <div className="gm-obj-title">Objetivos</div>
            {level.checks.map((check, i) => {
              const passed = result?.checks?.[i]?.passed;
              const cappedByStar = hintUsed && check.star === 3;
              return (
                <div key={i} className={`gm-obj ${passed === true ? (cappedByStar ? 'capped' : 'passed') : passed === false ? 'failed' : ''}`}>
                  <span className="gm-obj-star">{'★'.repeat(check.star)}</span>
                  <span className="gm-obj-name">
                    {check.desc}
                    {cappedByStar && <span className="gm-obj-locked"> 🔒</span>}
                  </span>
                  {passed === true && !cappedByStar && <span className="gm-obj-check">✓</span>}
                  {passed === false && <span className="gm-obj-x">✗</span>}
                </div>
              );
            })}
            {hintUsed && (
              <div className="gm-hint-warning">
                Pista usada — maximo 2 estrellas en este nivel (permanente).
              </div>
            )}
          </div>

          {/* Module palette */}
          {level.availableModules.length > 0 && (
            <div className="gm-module-palette">
              <div className="gm-palette-title">Modulos Disponibles</div>
              {level.availableModules.map(type => {
                const def = getModuleDef(type);
                if (!def) return null;
                return (
                  <div key={type} className="gm-palette-item" onClick={() => handleAddModule(type)}>
                    <span className="gm-palette-icon">{def.icon}</span>
                    <span className="gm-palette-name">{def.name}</span>
                    <span className="gm-palette-add">+</span>
                  </div>
                );
              })}
            </div>
          )}

          <button className="gm-btn danger" onClick={() => loadLevel(true)} style={{ marginTop: 'auto' }}>
            ↺ Reiniciar Nivel
          </button>
        </div>

        {/* Main canvas */}
        <div className="gm-puzzle-canvas-wrap">
          <div
            ref={containerRef}
            className={`node-canvas ${state.panning ? 'grabbing' : ''} ${connectingRef.current ? 'connecting' : ''}`}
            onPointerDown={handlePointerDown}
            onPointerMove={handlePointerMove}
            onPointerUp={handlePointerUp}
            onWheel={handleWheel}
            onContextMenu={handleContextMenu}
          >
            <svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', zIndex: 0 }}>
              <defs>
                <pattern id="puzzle-grid" width={20 * state.zoom} height={20 * state.zoom}
                  patternUnits="userSpaceOnUse"
                  x={state.camX % (20 * state.zoom)} y={state.camY % (20 * state.zoom)}>
                  <circle cx={1} cy={1} r={0.8} fill="#1a1a30" />
                </pattern>
              </defs>
              <rect width="100%" height="100%" fill="url(#puzzle-grid)" />
            </svg>

            <WireLayer portPositions={portPositions} tempWire={tempWire} containerRef={containerRef} zoom={state.zoom} camX={state.camX} camY={state.camY} />

            <div style={{ position: 'absolute', left: state.camX, top: state.camY, zIndex: 2 }}>
              {state.modules.map(mod => (
                <ModuleNode
                  key={mod.id}
                  mod={mod}
                  zoom={state.zoom}
                  onStartConnect={handleStartConnect}
                  onPortPosition={handlePortPosition}
                />
              ))}
            </div>
          </div>

          {/* Zoom controls — top right */}
          <div className="zoom-controls">
            <button className="zoom-btn" onClick={handleZoomIn} title="Acercar">+</button>
            <button className="zoom-btn zoom-label" onClick={handleZoomReset} title="Resetear zoom">
              {(state.zoom * 100).toFixed(0)}%
            </button>
            <button className="zoom-btn" onClick={handleZoomOut} title="Alejar">−</button>
          </div>

          {state.modules.length > 0 && state.connections.length === 0 && (
            <div className="gm-canvas-hint">
              Arrastra de un puerto (circulo) a otro para conectar modulos
            </div>
          )}
        </div>
      </div>

      {/* Level complete overlay */}
      {result && result.stars >= 1 && (
        <LevelComplete
          stars={result.stars}
          checks={result.checks}
          levelTitle={level.title}
          isLastLevel={isLastLevel}
          hintPenalty={result.hintPenalty}
          onRetry={() => setResult(null)}
          onMap={onBack}
          onNext={onNextLevel}
        />
      )}
    </div>
  );
}
