/**
 * This file is part of the SplendidBear Websites' projects.
 *
 * Copyright (c) 2026 @ www.splendidbear.org
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

import React, { useRef } from 'react';
import { Howl } from 'howler';
import GameContext from './GameContext';
import { useGameRefs, useGameState } from '@mine-hooks';
import { DESC, IMAGES, patchCells } from '@mine-utils';

export const GameProvider = ({ children }) => {
  const {
    webPlayerRef,
    activePlayerRef,
    bombSelectedRef,
    connectionLostRef,
    redRef,
    blueRef,
    lastClickedRef,
    endRef,
  } = useGameRefs();

  const {
    webPlayer, setWebPlayer,
    activePlayer, setActivePlayer,
    overlay, setOverlay,
    overlayTitle, setOverlayTitle,
    overlaySubTitle, setOverlaySubTitle,
    mines, setMines,
    bombSelected, setBombSelected,
    foundMines, setFoundMines,
    red, setRed,
    blue, setBlue,
    cells, setCells,
    gridReady, setGridReady,
    connectionLost, setConnectionLost,
  } = useGameState();

  const [gameUuid, setGameUuid] = React.useState(null);

  const sounds = useRef({
    click: new Howl({ src: ['/sound/click.mp3'] }),
    bomb: new Howl({ src: ['/sound/bomb.mp3'] }),
    mine: new Howl({ src: ['/sound/mine.mp3'] }),
    warning: new Howl({ src: ['/sound/warning.mp3'] }),
    won: new Howl({ src: ['/sound/won.mp3'] }),
  });

  /** Sync helpers (keep ref + state in lockstep) */

  const syncWebPlayer = v => {
    webPlayerRef.current = v;
    setWebPlayer(v);
  };
  const syncActivePlayer = v => {
    activePlayerRef.current = v;
    setActivePlayer(v);
  };
  const syncBombSelected = v => {
    bombSelectedRef.current = v;
    setBombSelected(v);
  };
  const syncConnLost = v => {
    connectionLostRef.current = v;
    setConnectionLost(v);
  };
  const syncRed = fn => {
    const n = fn(redRef.current);
    redRef.current = n;
    setRed(n);
  };
  const syncBlue = fn => {
    const n = fn(blueRef.current);
    blueRef.current = n;
    setBlue(n);
  };

  /** Overlay */

  const showOverlay = (title, sub) => {
    setOverlay(true);
    setOverlayTitle(title);
    setOverlaySubTitle(sub);
  };

  const hideOverlay = () => setOverlay(false);

  /** Cell helpers */

  const applyRevealedCell = (cell, player, isMainCell = false) => {
    const { row, col, value } = cell;
    setCells(prev => {
      if (prev[row][col].active) return prev;
      const patch = 'm' === value
        ? { currentImage: IMAGES.flag(player), currentObj: 'm', active: true }
        : { currentImage: value, currentObj: value, active: true };
      if (isMainCell) {
        patch.lastClickedRed = 'red' === player;
        patch.lastClickedBlue = 'blue' === player;
      }
      return patchCells(prev, [{ row, col, ...patch }]);
    });
    if (isMainCell) lastClickedRef.current = { ...lastClickedRef.current, [player]: [row, col] };
  };

  const showLeftMines = (leftMines = []) => {
    if (!leftMines.length) return;
    setCells(prev => {
      const patches = leftMines
        .filter(({ row, col }) => !prev[row][col].active)
        .map(({ row, col }) => ({ row, col, currentImage: IMAGES.leftMine }));
      return patches.length ? patchCells(prev, patches) : prev;
    });
  };

  /** Game logic */

  const changePlayer = () => {
    const wasColor = activePlayerRef.current ? 'blue' : 'red';
    const nextColor = activePlayerRef.current ? 'red' : 'blue';
    const nextVal = activePlayerRef.current ? 0 : 1;
    const desc = wasColor === webPlayerRef.current ? DESC.buddy : DESC.you;

    syncActivePlayer(nextVal);
    syncRed(p => ({ ...p, active: 'red' === nextColor, desc: 'red' === nextColor ? desc : '' }));
    syncBlue(p => ({ ...p, active: 'blue' === nextColor, desc: 'blue' === nextColor ? desc : '' }));
  };

  const applyStep = stepData => {
    const {
      player,
      bomb: isBomb,
      minesFound = 0,
      revealedCells = [],
      redPoints: rp,
      bluePoints: bp,
      redBonusPoints = 0,
      blueBonusPoints = 0,
      redBonusStats = {},
      blueBonusStats = {},
    } = stepData;

    if (isBomb) {
      sounds.current.bomb.play();
    } else if (0 < minesFound) {
      const cur = ('red' === player ? redRef : blueRef).current.mines;
      sounds.current[20 < cur + minesFound ? 'warning' : 'mine'].play();
    } else {
      sounds.current.click.play();
    }

    const lc = lastClickedRef.current[player];
    setCells(prev => {
      let next = prev;
      if (lc) next = patchCells(next, [{ row: lc[0], col: lc[1], lastClickedRed: false, lastClickedBlue: false }]);

      revealedCells.forEach(({ row, col, value }, idx) => {
        if (next[row][col].active) return;
        const patch = 'm' === value
          ? { currentImage: IMAGES.flag(player), currentObj: 'm', active: true }
          : { currentImage: value, currentObj: value, active: true };
        if (0 === idx) {
          patch.lastClickedRed = 'red' === player;
          patch.lastClickedBlue = 'blue' === player;
        }
        next = patchCells(next, [{ row, col, ...patch }]);
      });

      if (isBomb) next = next.map(r => r.map(c => null !== c.bombTargetArea ? { ...c, bombTargetArea: null } : c));
      return next;
    });

    if (0 < revealedCells.length) {
      lastClickedRef.current = { ...lastClickedRef.current, [player]: [revealedCells[0].row, revealedCells[0].col] };
    }

    if (0 < minesFound) {
      setMines(51 - rp - bp);
      setFoundMines(true);
      setTimeout(() => setFoundMines(false), 500);
      syncRed(p => ({ ...p, mines: 'red' === player ? rp : p.mines }));
      syncBlue(p => ({ ...p, mines: 'blue' === player ? bp : p.mines }));
    }

    // Update bonus points and stats
    syncRed(p => ({
      ...p,
      bonusPoints: 'red' === player ? redBonusPoints : p.bonusPoints,
      bonusStats: 'red' === player ? redBonusStats : p.bonusStats,
    }));
    syncBlue(p => ({
      ...p,
      bonusPoints: 'blue' === player ? blueBonusPoints : p.bonusPoints,
      bonusStats: 'blue' === player ? blueBonusStats : p.bonusStats,
    }));

    syncRed(p => ({ ...p, enabledBomb: rp <= bp }));
    syncBlue(p => ({ ...p, enabledBomb: bp <= rp }));

    if (isBomb || 0 === minesFound) changePlayer();

    if (isBomb) {
      syncBombSelected(false);
      syncRed(p => 'red' === player ? { ...p, haveBomb: false } : p);
      syncBlue(p => 'blue' === player ? { ...p, haveBomb: false } : p);
    }
  };

  const makeGameEndIfItEnds = (bluePoints, redPoints, resign = false, leftMines = []) => {
    const redWins = 25 < redPoints;
    const blueWins = 25 < bluePoints;

    if (redWins || blueWins || resign) {
      sounds.current.won.play();

      if (!resign) showOverlay((redWins ? 'Red' : 'Blue') + ' wins the game!', 'Play again!');

      showLeftMines(leftMines);
      syncActivePlayer(false);
      syncRed(p => ({ ...p, desc: '' }));
      syncBlue(p => ({ ...p, desc: '' }));
    }
  };

  const resignProcess = (color, uuid = null) => {
    const wp = webPlayerRef.current;
    if (uuid) {
      setGameUuid(uuid);
    }
    showOverlay(
      color === wp ? 'You have been give up' : 'Your opponent has been resigned',
      color === wp ? 'You LOSE!' : 'You WIN!',
    );
    endRef.current = true;
    makeGameEndIfItEnds(0, 0, true);
  };

  const onBombToggle = () => {
    const next = !bombSelectedRef.current;
    syncBombSelected(next);
    if (!next) {
      setCells(prev => prev.map(r => r.map(c => null !== c.bombTargetArea ? { ...c, bombTargetArea: null } : c)));
    }
  };

  return (
    <GameContext.Provider
      value={{
        // State (for rendering)
        webPlayer, activePlayer, overlay, overlayTitle, overlaySubTitle,
        mines, bombSelected, foundMines, red, blue, cells, gridReady, connectionLost, gameUuid,
        // Setters needed by useServerComm
        setCells, setGridReady, setGameUuid,
        // Refs (needed by useServerComm for async-safe reads)
        webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, endRef,
        // Sync helpers
        syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
        // Game logic called by useServerComm
        showOverlay, hideOverlay,
        applyRevealedCell, applyStep,
        makeGameEndIfItEnds, resignProcess,
        // UI action
        onBombToggle,
      }}
    >
      {children}
    </GameContext.Provider>
  );
};
