/* global React, Icon, IconBtn, db */
const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ============================================================
// Minesweeper — classic. Three difficulties.
// First click is always safe (mines are placed AFTER it).
// Left click reveals, right click / long-press flags.
// ============================================================

const MS_PRESETS = {
  beginner:     { rows: 9,  cols: 9,  mines: 10 },
  intermediate: { rows: 16, cols: 16, mines: 40 },
  expert:       { rows: 16, cols: 30, mines: 99 },
};

const ADJ_COLORS = ['', 'hsl(212, 85%, 60%)', 'hsl(108, 50%, 50%)', 'hsl(8, 78%, 58%)', 'hsl(258, 70%, 65%)', 'hsl(28, 90%, 60%)', 'hsl(168, 60%, 50%)', 'hsl(280, 60%, 60%)', 'hsl(0, 0%, 50%)'];

function emptyBoard(rows, cols) {
  return Array.from({ length: rows }, () =>
    Array.from({ length: cols }, () => ({ mine: false, revealed: false, flagged: false, adj: 0 }))
  );
}

function placeMines(board, rows, cols, mineCount, safeR, safeC) {
  // Avoid placing on the safe cell or any of its 8 neighbors.
  const banned = new Set();
  for (let dr = -1; dr <= 1; dr++) for (let dc = -1; dc <= 1; dc++) {
    const r = safeR + dr, c = safeC + dc;
    if (r >= 0 && r < rows && c >= 0 && c < cols) banned.add(r * cols + c);
  }
  const candidates = [];
  for (let i = 0; i < rows * cols; i++) if (!banned.has(i)) candidates.push(i);
  // Fisher-Yates partial shuffle
  for (let i = candidates.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [candidates[i], candidates[j]] = [candidates[j], candidates[i]];
  }
  const placed = Math.min(mineCount, candidates.length);
  for (let i = 0; i < placed; i++) {
    const idx = candidates[i];
    board[Math.floor(idx / cols)][idx % cols].mine = true;
  }
  // Compute adjacency
  for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
    if (board[r][c].mine) continue;
    let n = 0;
    for (let dr = -1; dr <= 1; dr++) for (let dc = -1; dc <= 1; dc++) {
      if (!dr && !dc) continue;
      const rr = r + dr, cc = c + dc;
      if (rr >= 0 && rr < rows && cc >= 0 && cc < cols && board[rr][cc].mine) n++;
    }
    board[r][c].adj = n;
  }
}

function MinesweeperTool() {
  const [difficulty, setDifficulty] = useState('beginner');
  const [board, setBoard] = useState(() => emptyBoard(MS_PRESETS.beginner.rows, MS_PRESETS.beginner.cols));
  const [seeded, setSeeded] = useState(false); // mines placed yet
  const [over, setOver] = useState(false);
  const [won, setWon] = useState(false);
  const [startedAt, setStartedAt] = useState(null);
  const [elapsedMs, setElapsedMs] = useState(0);
  const [bestTimes, setBestTimes] = useState({});
  const [now, setNow] = useState(Date.now());
  const longPressRef = useRef({ timer: null, fired: false });

  const preset = MS_PRESETS[difficulty];

  // Load best times
  useEffect(() => {
    db.kv.get('msBestTimes').then(rec => {
      if (rec?.v) setBestTimes(rec.v);
    });
  }, []);

  // Tick timer
  useEffect(() => {
    if (!startedAt || over) return;
    const id = setInterval(() => setNow(Date.now()), 250);
    return () => clearInterval(id);
  }, [startedAt, over]);

  const elapsed = startedAt ? (over ? elapsedMs : now - startedAt) : 0;

  // Counts
  const flagCount = useMemo(() => {
    let n = 0;
    for (const row of board) for (const cell of row) if (cell.flagged) n++;
    return n;
  }, [board]);

  const reset = useCallback((diffId = difficulty) => {
    const p = MS_PRESETS[diffId];
    setBoard(emptyBoard(p.rows, p.cols));
    setSeeded(false);
    setOver(false);
    setWon(false);
    setStartedAt(null);
    setElapsedMs(0);
    setDifficulty(diffId);
  }, [difficulty]);

  const reveal = useCallback((r, c) => {
    if (over) return;
    if (board[r][c].revealed || board[r][c].flagged) return;

    // Clone board, place mines on first click
    let next = board.map(row => row.map(cell => ({ ...cell })));
    if (!seeded) {
      placeMines(next, preset.rows, preset.cols, preset.mines, r, c);
      setSeeded(true);
      setStartedAt(Date.now());
      setNow(Date.now());
    }

    // BFS flood-fill for zero-adj cells
    const stack = [[r, c]];
    let hitMine = false;
    while (stack.length) {
      const [rr, cc] = stack.pop();
      if (rr < 0 || rr >= preset.rows || cc < 0 || cc >= preset.cols) continue;
      const cell = next[rr][cc];
      if (cell.revealed || cell.flagged) continue;
      cell.revealed = true;
      if (cell.mine) { hitMine = true; continue; }
      if (cell.adj === 0) {
        for (let dr = -1; dr <= 1; dr++) for (let dc = -1; dc <= 1; dc++) {
          if (!dr && !dc) continue;
          stack.push([rr + dr, cc + dc]);
        }
      }
    }

    if (hitMine) {
      // Reveal all mines
      for (let rr = 0; rr < preset.rows; rr++) for (let cc = 0; cc < preset.cols; cc++) {
        if (next[rr][cc].mine) next[rr][cc].revealed = true;
      }
      setOver(true);
      setWon(false);
      setElapsedMs(Date.now() - startedAt);
    } else {
      // Win check: all non-mine cells revealed?
      let unrevealed = 0;
      for (let rr = 0; rr < preset.rows; rr++) for (let cc = 0; cc < preset.cols; cc++) {
        if (!next[rr][cc].mine && !next[rr][cc].revealed) unrevealed++;
      }
      if (unrevealed === 0) {
        // Auto-flag remaining mines
        for (let rr = 0; rr < preset.rows; rr++) for (let cc = 0; cc < preset.cols; cc++) {
          if (next[rr][cc].mine) next[rr][cc].flagged = true;
        }
        const finalMs = Date.now() - startedAt;
        setOver(true);
        setWon(true);
        setElapsedMs(finalMs);
        // Update best time
        const cur = bestTimes[difficulty];
        if (cur == null || finalMs < cur) {
          const nb = { ...bestTimes, [difficulty]: finalMs };
          setBestTimes(nb);
          db.kv.put({ k: 'msBestTimes', v: nb });
        }
      }
    }
    setBoard(next);
  }, [board, over, seeded, preset, startedAt, bestTimes, difficulty]);

  const toggleFlag = useCallback((r, c) => {
    if (over) return;
    const cell = board[r][c];
    if (cell.revealed) return;
    const next = board.map(row => row.map(c2 => ({ ...c2 })));
    next[r][c].flagged = !next[r][c].flagged;
    setBoard(next);
  }, [board, over]);

  // Mouse + touch handlers
  const onCellMouseDown = (e, r, c) => {
    if (e.button === 2) {
      e.preventDefault();
      toggleFlag(r, c);
    }
  };
  const onCellClick = (e, r, c) => {
    if (e.button !== 0) return;
    reveal(r, c);
  };
  const onCellContextMenu = (e) => e.preventDefault();
  const onCellTouchStart = (e, r, c) => {
    longPressRef.current.fired = false;
    longPressRef.current.timer = setTimeout(() => {
      longPressRef.current.fired = true;
      toggleFlag(r, c);
    }, 350);
  };
  const onCellTouchEnd = (e, r, c) => {
    clearTimeout(longPressRef.current.timer);
    if (!longPressRef.current.fired) reveal(r, c);
  };

  return (
    <div className="ms-tool">
      <header className="ms-head">
        <div>
          <h1>Minesweeper</h1>
          <p>Reveal every safe cell. Right-click (or long-press) to flag a suspected mine.</p>
        </div>
        <div className="ms-difficulty">
          {Object.entries(MS_PRESETS).map(([id, p]) => (
            <button
              key={id}
              className={`ms-diff ${difficulty === id ? 'is-active' : ''}`}
              onClick={() => reset(id)}
            >
              <div className="ms-diff-name">{id[0].toUpperCase() + id.slice(1)}</div>
              <div className="ms-diff-spec">{p.rows}×{p.cols} · {p.mines} mines</div>
              {bestTimes[id] != null && (
                <div className="ms-diff-best">Best: {formatMsTime(bestTimes[id])}</div>
              )}
            </button>
          ))}
        </div>
      </header>

      <div className="ms-status">
        <div className="ms-status-item">
          <Icon name="flag" />
          <span>{preset.mines - flagCount}</span>
        </div>
        <button className="ms-status-face" onClick={() => reset(difficulty)}>
          {won ? '😎' : over ? '🤯' : '🙂'}
        </button>
        <div className="ms-status-item">
          <Icon name="timer" />
          <span>{formatMsTime(elapsed)}</span>
        </div>
      </div>

      <div
        className="ms-board"
        style={{ '--cols': preset.cols }}
        onContextMenu={onCellContextMenu}
      >
        {board.map((row, r) => row.map((cell, c) => (
          <button
            key={r * preset.cols + c}
            className={`ms-cell ${cell.revealed ? 'is-revealed' : ''} ${cell.flagged ? 'is-flagged' : ''} ${cell.revealed && cell.mine ? 'is-mine' : ''}`}
            onClick={(e) => onCellClick(e, r, c)}
            onMouseDown={(e) => onCellMouseDown(e, r, c)}
            onContextMenu={(e) => { e.preventDefault(); toggleFlag(r, c); }}
            onTouchStart={(e) => onCellTouchStart(e, r, c)}
            onTouchEnd={(e) => onCellTouchEnd(e, r, c)}
            disabled={over && !cell.mine && !cell.flagged}
          >
            {cell.revealed
              ? (cell.mine
                  ? '💣'
                  : cell.adj > 0
                    ? <span style={{ color: ADJ_COLORS[cell.adj] }}>{cell.adj}</span>
                    : ''
                )
              : cell.flagged ? '🚩' : ''}
          </button>
        )))}
      </div>

      {over && (
        <div className="ms-result">
          <Icon name={won ? 'celebration' : 'mood_bad'} />
          <strong>{won ? `Cleared in ${formatMsTime(elapsedMs)}!` : 'Boom — you hit a mine.'}</strong>
          <button className="lh-btn primary" onClick={() => reset(difficulty)}>
            <Icon name="refresh" />Play again
          </button>
        </div>
      )}
    </div>
  );
}

function formatMsTime(ms) {
  const total = Math.floor(ms / 1000);
  const m = Math.floor(total / 60);
  const s = total % 60;
  if (m > 0) return `${m}:${String(s).padStart(2, '0')}`;
  return `${total}s`;
}

window.MinesweeperTool = MinesweeperTool;
