import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { Play, Pause, RefreshCw, Settings, Activity, Zap, Layers, Calculator } from 'lucide-react';
import GridCell from './components/GridCell';

// --- Constants (fix #8: removed unused CELL_SIZE, Info, ChevronRight) ---
const GRID_SIZE = 15;
const CELL_COUNT = GRID_SIZE * GRID_SIZE;
const DELAY_BASE = 500;
const START_IDX = 0;
const END_IDX = CELL_COUNT - 1;

// --- Heuristic (Manhattan Distance) ---
const heuristic = (ax, ay, bx, by) => Math.abs(ax - bx) + Math.abs(ay - by);

// Neighbor offsets
const DIRS = [{ dx: 1, dy: 0 }, { dx: -1, dy: 0 }, { dx: 0, dy: 1 }, { dx: 0, dy: -1 }];

// --- Main Application ---
export default function App() {
  const [grid, setGrid] = useState([]);
  const [beamWidth, setBeamWidth] = useState(3);
  const [isPlaying, setIsPlaying] = useState(false);
  const [searchState, setSearchState] = useState(null);
  const [currentStep, setCurrentStep] = useState(0);
  const [history, setHistory] = useState([]);
  const [speed, setSpeed] = useState(1);
  const [stats, setStats] = useState({ visited: 0, activeBeam: 0, currentBestCost: 0 });

  const timerRef = useRef(null);
  const speedRef = useRef(speed);
  useEffect(() => { speedRef.current = speed; }, [speed]);

  // Fix #9: stable callbacks
  const resetSearch = useCallback(() => {
    clearInterval(timerRef.current);
    setIsPlaying(false);
    setSearchState(null);
    setCurrentStep(0);
    setHistory([]);
    setStats({ visited: 0, activeBeam: 0, currentBestCost: 0 });
  }, []);

  const generateGrid = useCallback(() => {
    const newGrid = new Array(CELL_COUNT);
    for (let y = 0; y < GRID_SIZE; y++) {
      for (let x = 0; x < GRID_SIZE; x++) {
        const noise = Math.random();
        let cost = 1;
        if (noise > 0.7) cost = 3;
        if (noise > 0.9) cost = 8;
        newGrid[y * GRID_SIZE + x] = { id: `${x}-${y}`, x, y, cost };
      }
    }
    setGrid(newGrid);
    resetSearch();
  }, [resetSearch]);

  useEffect(() => { generateGrid(); }, [generateGrid]);

  // --- Beam Search (fixes #1, #6, #7) ---
  const runBeamSearch = useCallback(() => {
    if (grid.length === 0) return;

    const startNode = grid[START_IDX]; // fix #6: direct index
    const endNode = grid[END_IDX];
    const endX = endNode.x, endY = endNode.y;

    let beam = [{
      node: startNode,
      path: [startNode.id],
      pathSet: new Set([startNode.id]), // fix #1: O(1) cycle check
      gScore: 0,
      fScore: heuristic(startNode.x, startNode.y, endX, endY),
    }];

    const steps = [];
    const visitedGlobal = new Set([startNode.id]);
    let active = true;
    let finalPath = null;

    // Record initial state
    steps.push({
      beamIds: [startNode.id],
      beamScores: new Map([[startNode.id, { g: 0, f: beam[0].fScore }]]),
      visited: new Set(visitedGlobal),
      prunedSet: new Set(),
      bestPath: null,
      bestPathSet: null,
      complete: false,
    });

    while (active && beam.length > 0) {
      const candidates = [];

      for (const item of beam) {
        if (item.node.id === endNode.id) {
          finalPath = item.path;
          active = false;
          break;
        }

        for (const { dx, dy } of DIRS) {
          const nx = item.node.x + dx;
          const ny = item.node.y + dy;
          if (nx < 0 || nx >= GRID_SIZE || ny < 0 || ny >= GRID_SIZE) continue;

          const neighbor = grid[ny * GRID_SIZE + nx];
          if (item.pathSet.has(neighbor.id)) continue; // fix #1: O(1) lookup

          const newG = item.gScore + neighbor.cost;
          const newF = newG + heuristic(nx, ny, endX, endY);
          const newPathSet = new Set(item.pathSet);
          newPathSet.add(neighbor.id);

          candidates.push({
            node: neighbor,
            path: [...item.path, neighbor.id],
            pathSet: newPathSet,
            gScore: newG,
            fScore: newF,
          });
        }
      }

      if (!active) break;
      if (candidates.length === 0) { active = false; break; }

      candidates.sort((a, b) => a.fScore - b.fScore);

      const nextBeam = candidates.slice(0, beamWidth);
      const pruned = candidates.slice(beamWidth);

      const beamIds = [];
      const beamScores = new Map();
      for (const b of nextBeam) {
        visitedGlobal.add(b.node.id);
        beamIds.push(b.node.id);
        beamScores.set(b.node.id, { g: b.gScore, f: b.fScore });
      }

      const prunedSet = new Set();
      for (const p of pruned) prunedSet.add(p.node.id);

      const bestPath = nextBeam[0]?.path || [];
      steps.push({
        beamIds,
        beamScores,
        visited: new Set(visitedGlobal),
        prunedSet,
        bestPath,
        bestPathSet: new Set(bestPath), // fix #3: pre-computed Set
        complete: false,
      });

      beam = nextBeam;
    }

    if (finalPath) {
      steps.push({
        beamIds: [],
        beamScores: new Map(),
        visited: visitedGlobal,
        prunedSet: new Set(),
        bestPath: finalPath,
        bestPathSet: new Set(finalPath),
        complete: true,
      });
    }

    setHistory(steps);
    setSearchState('running');
    setIsPlaying(true);
    setCurrentStep(0);
  }, [grid, beamWidth]);

  // --- Animation Loop (fix #5: no nested state updates) ---
  useEffect(() => {
    if (!isPlaying || searchState !== 'running') {
      clearInterval(timerRef.current);
      return;
    }

    timerRef.current = setInterval(() => {
      setCurrentStep(prev => {
        const next = prev + 1;
        if (next >= history.length) {
          clearInterval(timerRef.current);
          setIsPlaying(false);
          // fix #5: schedule state update outside updater
          setTimeout(() => {
            setSearchState(history[history.length - 1]?.complete ? 'finished' : 'failed');
          }, 0);
          return prev;
        }
        return next;
      });
    }, DELAY_BASE / speedRef.current);

    return () => clearInterval(timerRef.current);
  }, [isPlaying, searchState, history]);

  // Update stats from current step
  useEffect(() => {
    const stepData = history[currentStep];
    if (stepData) {
      setStats({
        visited: stepData.visited.size,
        activeBeam: stepData.beamIds.length,
        currentBestCost: stepData.beamScores.values().next().value?.g || 0,
      });
    }
  }, [currentStep, history]);

  // --- Pre-compute per-step lookup maps for O(1) cell classification (fix #2, #3) ---
  const stepData = history[currentStep] || null;

  const beamScoreMap = stepData?.beamScores || null;
  const bestPathSet = stepData?.bestPathSet || null;

  // Compute cell props once per step, memoized
  const cellProps = useMemo(() => {
    if (!grid.length) return [];
    return grid.map((node) => {
      const isStart = node.x === 0 && node.y === 0;
      const isEnd = node.x === GRID_SIZE - 1 && node.y === GRID_SIZE - 1;
      const isObstacle = node.cost > 5;
      const isHighCost = node.cost > 1 && node.cost <= 5;

      // Determine visual class
      let cellClass = 'bg-slate-900 border-slate-800';
      if (isStart) {
        cellClass = 'bg-emerald-500 border-emerald-600 shadow-emerald-500/50';
      } else if (isEnd) {
        cellClass = 'bg-rose-500 border-rose-600 shadow-rose-500/50';
      } else if (node.cost > 1) {
        cellClass = 'bg-slate-800 border-slate-700';
      }

      let tooltipContent = null;

      if (stepData) {
        const beamScore = beamScoreMap?.get(node.id);
        const isOnBestPath = bestPathSet?.has(node.id);

        if (isOnBestPath && !isStart && !isEnd) {
          cellClass = 'bg-amber-500/20 border-amber-500 shadow-[0_0_10px_rgba(245,158,11,0.3)] animate-pulse';
        }

        if (beamScore) {
          cellClass = 'bg-blue-500 border-blue-400 shadow-[0_0_15px_rgba(59,130,246,0.6)] z-10 scale-110 transition-transform cursor-help';
          const h = beamScore.f - beamScore.g;
          tooltipContent = (
            <>
              <div className="font-bold text-blue-300 border-b border-slate-600 pb-1 mb-1">
                Node {node.x},{node.y}
              </div>
              <div className="flex justify-between w-full"><span className="text-slate-400">Total (f):</span> <span className="font-mono text-white">{beamScore.f.toFixed(1)}</span></div>
              <div className="flex justify-between w-full text-[10px]"><span className="text-slate-500">Cost (g):</span> <span className="font-mono text-slate-300">{beamScore.g.toFixed(1)}</span></div>
              <div className="flex justify-between w-full text-[10px]"><span className="text-slate-500">Dist (h):</span> <span className="font-mono text-slate-300">{h.toFixed(1)}</span></div>
            </>
          );
        } else if (stepData.prunedSet?.has(node.id) && !isStart && !isEnd) {
          cellClass = 'bg-red-900/30 border-red-900/50';
        } else if (stepData.visited.has(node.id) && !isStart && !isEnd && !isOnBestPath) {
          cellClass = 'bg-blue-900/20 border-blue-800/30';
        }
      }

      return { cellClass, isObstacle, isHighCost, isStart, isEnd, tooltipContent };
    });
  }, [grid, stepData, beamScoreMap, bestPathSet]);

  // Slider handlers (fix #13)
  const onBeamWidth = useCallback((e) => {
    setBeamWidth(parseInt(e.target.value));
    resetSearch();
  }, [resetSearch]);

  const onSpeed = useCallback((e) => {
    setSpeed(parseInt(e.target.value));
  }, []);

  const onScrub = useCallback((e) => {
    setIsPlaying(false);
    setCurrentStep(Number(e.target.value));
  }, []);

  const togglePlay = useCallback(() => {
    setIsPlaying(p => !p);
  }, []);

  return (
    <div className="min-h-screen bg-slate-950 text-slate-200 font-sans selection:bg-blue-500/30">

      {/* Header */}
      <header className="border-b border-slate-800 bg-slate-950/50 backdrop-blur-md sticky top-0 z-50">
        <div className="max-w-6xl mx-auto px-4 h-16 flex items-center justify-between">
          <div className="flex items-center gap-3">
            <div className="p-2 bg-blue-600 rounded-lg shadow-lg shadow-blue-900/20">
              <Layers size={20} className="text-white" />
            </div>
            <div>
              <h1 className="font-bold text-lg tracking-tight text-white">
                LGI <span className="text-slate-500 font-normal">| Lattice Grid Interface</span>
              </h1>
              <p className="text-xs text-slate-500 font-mono">Beam Search Visualizer v1.2</p>
            </div>
          </div>

          <div className="flex items-center gap-6">
            <div className="hidden md:flex gap-6 text-xs font-mono text-slate-400">
              <div className="flex flex-col items-end">
                <span className="text-slate-600 uppercase tracking-wider">Depth</span>
                <span className="text-white font-bold text-lg">{currentStep}</span>
              </div>
              <div className="flex flex-col items-end">
                <span className="text-slate-600 uppercase tracking-wider">Active Nodes</span>
                <span className="text-blue-400 font-bold text-lg">{stats.activeBeam || 0}</span>
              </div>
            </div>
            {/* fix #10: removed dead <a href="#"> */}
            <button
              className="p-2 hover:bg-slate-800 rounded-full transition-colors text-slate-500 hover:text-white"
              aria-label="Settings"
              onClick={() => {}}
            >
              <Settings size={20} />
            </button>
          </div>
        </div>
      </header>

      <main className="max-w-6xl mx-auto p-4 lg:p-8 grid grid-cols-1 lg:grid-cols-12 gap-8">

        {/* Left Control Panel */}
        <div className="lg:col-span-3 space-y-6">
          <div className="bg-slate-900/50 border border-slate-800 rounded-xl p-5 space-y-6">
            <div>
              <div className="flex justify-between items-center mb-2">
                <label htmlFor="beam-width" className="text-sm font-medium text-slate-300 flex items-center gap-2">
                  <Activity size={14} /> Beam Width (k)
                </label>
                <span className="text-xs font-mono bg-slate-800 px-2 py-1 rounded text-blue-400">{beamWidth}</span>
              </div>
              <input
                id="beam-width"
                type="range" min="1" max="10"
                value={beamWidth}
                onChange={onBeamWidth}
                disabled={isPlaying}
                className="w-full h-2 bg-slate-800 rounded-lg appearance-none cursor-pointer accent-blue-500"
              />
              <p className="text-xs text-slate-500 mt-2 leading-relaxed">
                Determines how many paths are kept in memory at each step. Higher width = better accuracy but slower.
              </p>
            </div>

            <div>
              <div className="flex justify-between items-center mb-2">
                <label htmlFor="speed" className="text-sm font-medium text-slate-300 flex items-center gap-2">
                  <Zap size={14} /> Simulation Speed
                </label>
                <span className="text-xs font-mono bg-slate-800 px-2 py-1 rounded text-amber-400">{speed}x</span>
              </div>
              <input
                id="speed"
                type="range" min="1" max="10"
                value={speed}
                onChange={onSpeed}
                className="w-full h-2 bg-slate-800 rounded-lg appearance-none cursor-pointer accent-amber-500"
              />
            </div>

            <hr className="border-slate-800" />

            <div className="grid grid-cols-2 gap-2">
              {!history.length || searchState === null ? (
                <button
                  onClick={runBeamSearch}
                  className="col-span-2 flex items-center justify-center gap-2 bg-blue-600 hover:bg-blue-500 text-white py-3 rounded-lg font-medium transition-all shadow-lg shadow-blue-900/20 active:scale-95"
                >
                  <Play size={18} fill="currentColor" /> Start Search
                </button>
              ) : (
                <>
                  <button
                    onClick={togglePlay}
                    className={`flex items-center justify-center gap-2 py-3 rounded-lg font-medium transition-all ${
                      isPlaying
                        ? 'bg-amber-600/20 text-amber-500 border border-amber-600/50'
                        : 'bg-emerald-600 text-white shadow-lg shadow-emerald-900/20'
                    }`}
                  >
                    {isPlaying ? <Pause size={18} fill="currentColor" /> : <Play size={18} fill="currentColor" />}
                    {isPlaying ? 'Pause' : 'Resume'}
                  </button>
                  <button
                    onClick={resetSearch}
                    className="flex items-center justify-center gap-2 bg-slate-800 hover:bg-slate-700 text-slate-300 py-3 rounded-lg font-medium transition-all"
                  >
                    Reset
                  </button>
                </>
              )}

              <button
                onClick={generateGrid}
                className="col-span-2 flex items-center justify-center gap-2 bg-transparent border border-slate-700 hover:border-slate-600 text-slate-400 hover:text-slate-300 py-2 rounded-lg text-sm transition-all mt-2"
              >
                <RefreshCw size={14} /> New Lattice
              </button>
            </div>
          </div>

          {/* Legend */}
          <div className="bg-slate-900/50 border border-slate-800 rounded-xl p-5">
            <h3 className="text-xs font-bold text-slate-500 uppercase tracking-wider mb-4">Node Legend</h3>
            <div className="space-y-3 text-sm">
              <div className="flex items-center gap-3">
                <div className="w-4 h-4 rounded bg-emerald-500 shadow shadow-emerald-500/50" />
                <span className="text-slate-300">Start Node</span>
              </div>
              <div className="flex items-center gap-3">
                <div className="w-4 h-4 rounded bg-rose-500 shadow shadow-rose-500/50" />
                <span className="text-slate-300">Target Node</span>
              </div>
              <div className="flex items-center gap-3">
                <div className="w-4 h-4 rounded bg-blue-500 shadow shadow-blue-500/50" />
                <span className="text-slate-300">Active Beam</span>
              </div>
              <div className="flex items-center gap-3">
                <div className="w-4 h-4 rounded bg-amber-500/20 border border-amber-500" />
                <span className="text-slate-300">Current Best Path</span>
              </div>
              <div className="flex items-center gap-3">
                <div className="flex items-center justify-center w-4 h-4">
                  <Calculator size={14} className="text-slate-400" />
                </div>
                <span className="text-slate-400 italic">Hover blue nodes to see score</span>
              </div>
            </div>
          </div>
        </div>

        {/* Right Grid Visualization */}
        <div className="lg:col-span-9">
          <div className="bg-slate-900 border border-slate-800 rounded-2xl p-4 lg:p-8 flex items-center justify-center relative overflow-hidden min-h-[500px]">
            {/* Grid Background */}
            <div
              className="absolute inset-0 opacity-10 pointer-events-none"
              style={{
                backgroundImage: 'radial-gradient(circle at 1px 1px, #475569 1px, transparent 0)',
                backgroundSize: '40px 40px',
              }}
            />

            {/* Grid */}
            <div
              className="grid gap-1 relative z-10 transition-all duration-300"
              style={{ gridTemplateColumns: `repeat(${GRID_SIZE}, minmax(0, 1fr))`, width: 'fit-content' }}
              role="grid"
              aria-label="Beam search lattice grid"
            >
              {cellProps.map((props, i) => (
                <GridCell
                  key={grid[i]?.id || i}
                  cellClass={props.cellClass}
                  isObstacle={props.isObstacle}
                  isHighCost={props.isHighCost}
                  isStart={props.isStart}
                  isEnd={props.isEnd}
                  tooltipContent={props.tooltipContent}
                />
              ))}
            </div>

            {/* Result Overlays */}
            {searchState === 'finished' && (
              <div className="absolute z-20 bottom-8 left-1/2 -translate-x-1/2 bg-slate-900/90 border border-emerald-500/30 text-emerald-400 px-6 py-3 rounded-full backdrop-blur-md shadow-2xl flex items-center gap-3 pointer-events-none" role="alert">
                <div className="w-2 h-2 bg-emerald-500 rounded-full animate-pulse" />
                Target Reached! Optimal path found within beam constraints.
              </div>
            )}
            {searchState === 'failed' && (
              <div className="absolute z-20 bottom-8 left-1/2 -translate-x-1/2 bg-slate-900/90 border border-rose-500/30 text-rose-400 px-6 py-3 rounded-full backdrop-blur-md shadow-2xl flex items-center gap-3 pointer-events-none" role="alert">
                <div className="w-2 h-2 bg-rose-500 rounded-full animate-pulse" />
                Search Exhausted. Beam width too narrow to find target.
              </div>
            )}
          </div>

          {/* Timeline Scrubber */}
          {history.length > 0 && (
            <div className="mt-4 flex items-center gap-4 bg-slate-900/50 p-3 rounded-xl border border-slate-800" aria-label="Search timeline">
              <span className="text-xs font-mono text-slate-500 w-16 text-right">START</span>
              <div className="flex-1 relative h-6 flex items-center">
                <input
                  type="range" min="0" max={history.length - 1}
                  value={currentStep}
                  onChange={onScrub}
                  className="w-full absolute z-20 opacity-0 cursor-pointer h-full"
                  aria-label="Step through search"
                />
                <div className="w-full h-1.5 bg-slate-800 rounded-full overflow-hidden relative z-10 pointer-events-none">
                  <div
                    className="h-full bg-gradient-to-r from-blue-600 to-emerald-500 transition-all duration-75 ease-linear"
                    style={{ width: `${history.length > 1 ? (currentStep / (history.length - 1)) * 100 : 0}%` }}
                  />
                </div>
                <div
                  className="absolute h-4 w-4 bg-white rounded-full shadow-lg z-10 pointer-events-none transition-all duration-75 ease-linear"
                  style={{ left: `calc(${history.length > 1 ? (currentStep / (history.length - 1)) * 100 : 0}% - 8px)` }}
                />
              </div>
              <span className="text-xs font-mono text-slate-500 w-16">GOAL</span>
            </div>
          )}
        </div>
      </main>
    </div>
  );
}
