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

// ============================================================
// Sudoku — classic 9×9, three difficulties, with pencil marks,
// conflict highlighting, undo, and a persisted in-progress game.
// ============================================================

const SUDOKU_DIFFICULTIES = [
  { id: 'easy',   label: 'Easy',   clues: 42 },
  { id: 'medium', label: 'Medium', clues: 33 },
  { id: 'hard',   label: 'Hard',   clues: 26 },
];

// ---- Puzzle generation ----
function shuffled(a) {
  const out = a.slice();
  for (let i = out.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [out[i], out[j]] = [out[j], out[i]];
  }
  return out;
}
function emptyBoard() { return Array.from({ length: 9 }, () => Array(9).fill(0)); }

function isValid(board, r, c, v) {
  for (let i = 0; i < 9; i++) {
    if (board[r][i] === v || board[i][c] === v) return false;
  }
  const br = Math.floor(r / 3) * 3, bc = Math.floor(c / 3) * 3;
  for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) {
    if (board[br + i][bc + j] === v) return false;
  }
  return true;
}

function solve(board, randomize = false) {
  for (let r = 0; r < 9; r++) for (let c = 0; c < 9; c++) {
    if (board[r][c] === 0) {
      const nums = randomize ? shuffled([1,2,3,4,5,6,7,8,9]) : [1,2,3,4,5,6,7,8,9];
      for (const v of nums) {
        if (isValid(board, r, c, v)) {
          board[r][c] = v;
          if (solve(board, randomize)) return true;
          board[r][c] = 0;
        }
      }
      return false;
    }
  }
  return true;
}

function generatePuzzle(clues) {
  // 1. Generate a random solved board
  const solved = emptyBoard();
  solve(solved, true);

  // 2. Remove cells to leave `clues` of them
  const puzzle = solved.map(r => r.slice());
  const cellOrder = shuffled(Array.from({ length: 81 }, (_, i) => i));
  let removed = 0;
  const target = 81 - clues;
  for (const idx of cellOrder) {
    if (removed >= target) break;
    const r = Math.floor(idx / 9), c = idx % 9;
    if (puzzle[r][c] === 0) continue;
    puzzle[r][c] = 0;
    removed++;
  }
  return { puzzle, solved };
}

// ============================================================
// SudokuTool
// ============================================================
function SudokuTool() {
  const [difficulty, setDifficulty] = useState('easy');
  const [initial, setInitial] = useState(null); // the puzzle's fixed cells (0 = empty)
  const [solved, setSolved] = useState(null);   // for checking
  const [board, setBoard] = useState(null);     // current state
  const [notes, setNotes] = useState(null);     // 9x9 array of Set of pencil marks
  const [selected, setSelected] = useState({ r: 0, c: 0 });
  const [notesMode, setNotesMode] = useState(false);
  const [startedAt, setStartedAt] = useState(null);
  const [elapsedMs, setElapsedMs] = useState(0);
  const [now, setNow] = useState(Date.now());
  const [history, setHistory] = useState([]); // for undo
  const [won, setWon] = useState(false);

  // Load persisted state
  useEffect(() => {
    db.kv.get('sudokuState').then(rec => {
      if (rec?.v && rec.v.initial) {
        setDifficulty(rec.v.difficulty || 'easy');
        setInitial(rec.v.initial);
        setSolved(rec.v.solved);
        setBoard(rec.v.board);
        setNotes((rec.v.notes || Array.from({ length: 9 }, () => Array.from({ length: 9 }, () => []))).map(row => row.map(n => new Set(n))));
        setStartedAt(rec.v.startedAt);
        setElapsedMs(rec.v.elapsedMs || 0);
        setWon(!!rec.v.won);
      } else {
        startNewGame('easy');
      }
    });
  }, []);

  const persist = useCallback((override = {}) => {
    if (!initial && !override.initial) return;
    const _initial = override.initial ?? initial;
    const _solved = override.solved ?? solved;
    const _board = override.board ?? board;
    const _notes = override.notes ?? notes;
    db.kv.put({
      k: 'sudokuState',
      v: {
        difficulty: override.difficulty ?? difficulty,
        initial: _initial,
        solved: _solved,
        board: _board,
        notes: _notes ? _notes.map(row => row.map(s => Array.from(s))) : null,
        startedAt: override.startedAt ?? startedAt,
        elapsedMs: override.elapsedMs ?? elapsedMs,
        won: override.won ?? won,
      },
    });
  }, [difficulty, initial, solved, board, notes, startedAt, elapsedMs, won]);

  // Persist on changes (debounced via change of state itself)
  useEffect(() => {
    if (initial) persist();
  }, [board, notes, won]);

  // Timer
  useEffect(() => {
    if (!startedAt || won) return;
    const id = setInterval(() => setNow(Date.now()), 1000);
    return () => clearInterval(id);
  }, [startedAt, won]);

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

  const startNewGame = useCallback((diff = difficulty) => {
    const config = SUDOKU_DIFFICULTIES.find(d => d.id === diff) || SUDOKU_DIFFICULTIES[0];
    const { puzzle, solved } = generatePuzzle(config.clues);
    const fresh = puzzle.map(r => r.slice());
    const freshNotes = Array.from({ length: 9 }, () => Array.from({ length: 9 }, () => new Set()));
    setDifficulty(diff);
    setInitial(puzzle.map(r => r.slice()));
    setSolved(solved);
    setBoard(fresh);
    setNotes(freshNotes);
    setSelected({ r: 0, c: 0 });
    setNotesMode(false);
    setStartedAt(Date.now());
    setElapsedMs(0);
    setHistory([]);
    setWon(false);
  }, [difficulty]);

  const setCellValue = (r, c, v) => {
    if (won) return;
    if (initial[r][c] !== 0) return;
    setHistory(h => [...h.slice(-30), { r, c, prev: board[r][c], prevNotes: new Set(notes[r][c]) }]);
    if (notesMode && v !== 0) {
      const nextNotes = notes.map(row => row.map(s => new Set(s)));
      if (nextNotes[r][c].has(v)) nextNotes[r][c].delete(v);
      else nextNotes[r][c].add(v);
      setNotes(nextNotes);
    } else {
      const nextBoard = board.map(row => row.slice());
      nextBoard[r][c] = v;
      // Clear notes on the cell since it's a confirmed value
      const nextNotes = notes.map(row => row.map(s => new Set(s)));
      nextNotes[r][c] = new Set();
      setBoard(nextBoard);
      setNotes(nextNotes);
      // Check win
      if (boardSolved(nextBoard, solved)) {
        setWon(true);
        setElapsedMs((now - startedAt) + elapsedMs);
      }
    }
  };

  const undo = () => {
    if (history.length === 0) return;
    const last = history[history.length - 1];
    const nextBoard = board.map(row => row.slice());
    const nextNotes = notes.map(row => row.map(s => new Set(s)));
    nextBoard[last.r][last.c] = last.prev;
    nextNotes[last.r][last.c] = last.prevNotes;
    setBoard(nextBoard);
    setNotes(nextNotes);
    setHistory(h => h.slice(0, -1));
    setWon(false);
  };

  // Keyboard
  useEffect(() => {
    const onKey = (e) => {
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
      if (!board) return;
      const { r, c } = selected;
      if (e.key === 'ArrowLeft')  { setSelected({ r, c: Math.max(0, c - 1) }); e.preventDefault(); return; }
      if (e.key === 'ArrowRight') { setSelected({ r, c: Math.min(8, c + 1) }); e.preventDefault(); return; }
      if (e.key === 'ArrowUp')    { setSelected({ r: Math.max(0, r - 1), c }); e.preventDefault(); return; }
      if (e.key === 'ArrowDown')  { setSelected({ r: Math.min(8, r + 1), c }); e.preventDefault(); return; }
      if (e.key === 'Backspace' || e.key === 'Delete' || e.key === '0') { setCellValue(r, c, 0); e.preventDefault(); return; }
      if (/^[1-9]$/.test(e.key))  { setCellValue(r, c, parseInt(e.key, 10)); e.preventDefault(); return; }
      if (e.key === 'n')          { setNotesMode(m => !m); e.preventDefault(); return; }
      if (e.key === 'z' && (e.metaKey || e.ctrlKey)) { undo(); e.preventDefault(); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  });

  // Highlighting
  const selValue = board && initial ? (board[selected.r][selected.c] || initial[selected.r][selected.c]) : 0;
  const conflicts = useMemo(() => {
    if (!board || !initial) return new Set();
    const out = new Set();
    for (let r = 0; r < 9; r++) for (let c = 0; c < 9; c++) {
      const v = board[r][c] || initial[r][c];
      if (!v) continue;
      // Check same row
      for (let i = 0; i < 9; i++) {
        if (i !== c && (board[r][i] || initial[r][i]) === v) out.add(`${r}:${c}`);
        if (i !== r && (board[i][c] || initial[i][c]) === v) out.add(`${r}:${c}`);
      }
      // Check box
      const br = Math.floor(r / 3) * 3, bc = Math.floor(c / 3) * 3;
      for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) {
        if ((br + i !== r || bc + j !== c) && (board[br + i][bc + j] || initial[br + i][bc + j]) === v) out.add(`${r}:${c}`);
      }
    }
    return out;
  }, [board, initial]);

  if (!board || !initial) return <div className="sud-tool sud-loading" />;

  return (
    <div className="sud-tool">
      <header className="sud-head">
        <div>
          <h1>Sudoku</h1>
          <p>Fill so each row, column and box contains 1–9. <kbd>N</kbd> toggles pencil marks; <kbd>⌘Z</kbd> undoes.</p>
        </div>
        <div className="sud-diff-row">
          {SUDOKU_DIFFICULTIES.map(d => (
            <button
              key={d.id}
              className={`sud-diff ${difficulty === d.id ? 'is-active' : ''}`}
              onClick={() => startNewGame(d.id)}
            >{d.label}</button>
          ))}
        </div>
      </header>

      <div className="sud-status">
        <span><Icon name="timer" />{formatSudokuTime(elapsed)}</span>
        <span><Icon name="speed" />{difficulty}</span>
        {won && <span className="sud-win"><Icon name="emoji_events" />Solved!</span>}
        <div style={{ flex: 1 }} />
        <button className={`lh-btn ${notesMode ? 'primary' : 'ghost'}`} onClick={() => setNotesMode(m => !m)}>
          <Icon name="edit_note" />Notes
        </button>
        <button className="lh-btn ghost" onClick={undo} disabled={history.length === 0}>
          <Icon name="undo" />Undo
        </button>
        <button className="lh-btn ghost" onClick={() => startNewGame(difficulty)}>
          <Icon name="refresh" />New
        </button>
      </div>

      <div className="sud-grid">
        {board.map((row, r) => row.map((cell, c) => {
          const fixed = initial[r][c] !== 0;
          const value = fixed ? initial[r][c] : cell;
          const isSelected = selected.r === r && selected.c === c;
          const isPeer = selected.r === r || selected.c === c
                       || (Math.floor(selected.r / 3) === Math.floor(r / 3) && Math.floor(selected.c / 3) === Math.floor(c / 3));
          const sameNumber = value && selValue && value === selValue;
          const isConflict = conflicts.has(`${r}:${c}`);
          const cellNotes = notes[r][c];
          return (
            <button
              key={`${r}:${c}`}
              className={`sud-cell ${fixed ? 'is-fixed' : ''} ${isSelected ? 'is-selected' : ''} ${isPeer && !isSelected ? 'is-peer' : ''} ${sameNumber && !isSelected ? 'is-same' : ''} ${isConflict ? 'is-conflict' : ''} ${(c % 3 === 2 && c < 8) ? 'has-right' : ''} ${(r % 3 === 2 && r < 8) ? 'has-bottom' : ''}`}
              onClick={() => setSelected({ r, c })}
            >
              {value ? <span className="sud-cell-val">{value}</span> : cellNotes && cellNotes.size > 0 ? (
                <div className="sud-cell-notes">
                  {[1,2,3,4,5,6,7,8,9].map(n => (
                    <span key={n} className={cellNotes.has(n) ? 'is-on' : ''}>{cellNotes.has(n) ? n : ''}</span>
                  ))}
                </div>
              ) : null}
            </button>
          );
        }))}
      </div>

      <div className="sud-pad">
        {[1,2,3,4,5,6,7,8,9].map(n => (
          <button key={n} className="sud-pad-btn" onClick={() => setCellValue(selected.r, selected.c, n)}>
            {n}
          </button>
        ))}
        <button className="sud-pad-btn sud-pad-clear" onClick={() => setCellValue(selected.r, selected.c, 0)}>
          <Icon name="backspace" />
        </button>
      </div>
    </div>
  );
}

function boardSolved(board, solved) {
  if (!board || !solved) return false;
  for (let r = 0; r < 9; r++) for (let c = 0; c < 9; c++) {
    if (board[r][c] !== solved[r][c]) return false;
  }
  return true;
}

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

window.SudokuTool = SudokuTool;
