/**
 * 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, { useEffect, useRef } from 'react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useGame } from '@mine-contexts';
import { DESC } from '@mine-utils';
import useStepTimer from './useStepTimer';
import { WaitingOverlayContent } from '@mine-components';

/** Handles all server communication: SSE (Mercure), REST calls, and the initialization lifecycle. */
const useServerCommunication = (gameAssoc, gameInherited, isEnvDev) => {
  const {
    /** Async-safe refs */
    webPlayerRef, activePlayerRef, bombSelectedRef, connectionLostRef, endRef,
    /** State setters */
    setGridReady,
    /** Sync helpers */
    syncWebPlayer, syncActivePlayer, syncBombSelected, syncConnLost, syncRed, syncBlue,
    /** Game logic */
    showOverlay, hideOverlay,
    applyRevealedCell, applyStep,
    makeGameEndIfItEnds, resignProcess,
    /** Current cells snapshot (for active-check in onClick) */
    cells,
  } = useGame();

  const eventSourceRef = useRef(null);
  const rpcUsersRef = useRef(null);
  const stepCacheRef = useRef([]);
  const { getStepElapsed, resetStepTimer, startNewTurn } = useStepTimer();
  const isGameRunningRef = useRef(false);
  const lastActivePlayerRef = useRef(null);

  /** REST mutations / queries */

  const connectQuery = useQuery({
    queryKey: ['game-connect', gameAssoc],
    queryFn: () => fetch('/api/game/connect/' + gameAssoc)
      .then(r => r.text())
      .then(b64 => JSON.parse(window.atob(b64))),
    enabled: false,
    retry: false,
  });

  const startMutation = useMutation({
    mutationFn: () => fetch('/api/game/start', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ gameAssoc }),
    }),
  });

  const joinMutation = useMutation({
    mutationFn: () => fetch('/api/game/join/' + gameAssoc, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
    }).catch(e => isEnvDev && console.error('Join error', e)),
  });

  const stepMutation = useMutation({
    mutationFn: dataPack => fetch('/api/game/step/' + gameAssoc, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(dataPack),
    }).then(r => r.json()),
  });

  /** Game-start helpers (triggered by server events) */

  const wInit = (revealedCells = []) => {
    setGridReady(true);
    showOverlay('We are waiting for your opponent...', gameAssoc ? (
      <WaitingOverlayContent
        shareUrl={`${window.location.href}/${gameAssoc}`}
        currentGameAssoc={gameAssoc}
      />
    ) : '');
    setTimeout(() => revealedCells.forEach(cell => applyRevealedCell(cell, cell.player)), 0);
  };

  const makeGameStart = payload => {
    syncActivePlayer(1);
    syncRed(p => ({
      ...p,
      name: payload.users.red || payload.users.redAnon || p.name,
    }));
    syncBlue(p => ({
      ...p,
      name: payload.users.blue || payload.users.blueAnon || p.name,
      desc: 'blue' === webPlayerRef.current ? DESC.you : DESC.buddy,
      active: true,
    }));
    isGameRunningRef.current = true;
    lastActivePlayerRef.current = 1; // Blue starts
    startNewTurn();
    resetStepTimer();
    hideOverlay();
  };

  /** Mercure / SSE message handlers */

  const wSubscribe = (payload, rpcUsers = null) => {
    isEnvDev && console.info((payload.user ?? 'user') + ' subscribed');
    const firstUser = !rpcUsers;

    if (null === webPlayerRef.current) {
      const isBlue = payload.user === payload.users.blue
        || (firstUser ? '' !== payload.users.blueAnon : '' === rpcUsers.blueAnon && '' === rpcUsers.blue);
      syncWebPlayer(isBlue ? 'blue' : 'red');
    }

    if (
      2 === payload.userCnt
      && (!connectionLostRef.current
        || (connectionLostRef.current && false === activePlayerRef.current && !endRef.current))
    ) {
      makeGameStart(payload);
    }
  };

  const wUnsubscribe = payload => {
    isEnvDev && console.info(payload.msg);
    showOverlay('The connection has been lost w/ your friend...', 'Please, restart the game!');
  };

  const wChallenge = payload => {
    const { challengerName, challengerGameAssoc } = payload;

    const handleAccept = () => {
      fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ accepted: true, targetGameAssoc: gameAssoc }),
      }).then(() => {
        showOverlay('Challenge accepted!', 'Waiting for the challenger to join...');
      }).catch(() => {});
    };

    const handleDecline = () => {
      fetch('/api/game/challenge/respond/' + challengerGameAssoc, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ accepted: false, targetGameAssoc: gameAssoc }),
      }).then(() => {
        showOverlay('We are waiting for your opponent...', gameAssoc ? (
          <WaitingOverlayContent
            shareUrl={window.location.origin + '/play/' + gameAssoc}
            currentGameAssoc={gameAssoc}
          />
        ) : '');
      }).catch(() => {});
    };

    showOverlay(
      challengerName + ' wants to challenge you!',
      <div className="resign">
        <a onClick={handleAccept}>Accept</a>
        <a onClick={handleDecline}>Decline</a>
      </div>,
    );
  };

  const wChallengeResponse = payload => {
    if (payload.accepted) {
      window.location.href = '/play/' + payload.targetGameAssoc;
    } else {
      window.dispatchEvent(new CustomEvent('challenge-declined'));
    }
  };

  const wTopic = payload => {
    if (webPlayerRef.current !== payload.data.player) {
      if (null === payload.data.resign) {
        isEnvDev && console.warn(payload.user + ' stepped to ' + payload.data.coords.join(','));
        syncBombSelected(payload.data.bomb);

        // Detect if turn switched (other player made a move)
        // After their move, it's now our turn (or the opposite player's turn)
        if (lastActivePlayerRef.current !== activePlayerRef.current) {
          startNewTurn();
          lastActivePlayerRef.current = activePlayerRef.current;
        }

        applyStep(payload.data);
        makeGameEndIfItEnds(payload.data.bluePoints, payload.data.redPoints, false, payload.data.leftMines);
      } else {
        resignProcess(payload.data.resign);
      }
    }
  };

  const handleMercureMessage = payload => {
    if (undefined !== payload.type) {
      if ('challenge' === payload.type) wChallenge(payload);
      else if ('challenge-response' === payload.type) wChallengeResponse(payload);
      return;
    }

    if (undefined !== payload.data) {
      wTopic(payload);
    } else if (undefined === payload.msg) {
      wSubscribe(payload, rpcUsersRef.current);
    } else {
      wUnsubscribe(payload);
    }

    if (2 === payload.userCnt && connectionLostRef.current) {
      isEnvDev && console.info('Reconnection');
      stepCacheRef.current.forEach(item => stepMutation.mutate(item));
      stepCacheRef.current = [];
      syncConnLost(false);
    }
  };

  const openEventSource = () => {
    const wrapper = document.getElementById('mine-wrapper');
    const hubUrl = wrapper.dataset.mercureHubUrl;
    const subscriberJwt = wrapper.dataset.mercureSubscriberJwt;
    const url = new URL(hubUrl, window.location.origin);

    url.searchParams.append('topic', 'mineseeker/channel/' + gameAssoc);
    if (subscriberJwt) url.searchParams.append('authorization', subscriberJwt);

    if (eventSourceRef.current) eventSourceRef.current.close();

    const es = new EventSource(url.toString());
    es.onmessage = e => handleMercureMessage(JSON.parse(e.data));
    es.onopen = () => {
      isEnvDev && console.info('SSE opened');
      if (connectionLostRef.current) {
        isEnvDev && console.info('SSE reconnected');
        joinMutation.mutate();
      }
    };
    es.onerror = () => {
      isEnvDev && console.error('SSE error');
      syncConnLost(true);
    };
    eventSourceRef.current = es;
  };

  /** Initialization */

  useEffect(() => {
    (async () => {
      if (connectionLostRef.current) {
        openEventSource();
        return;
      }
      try {
        if (gameInherited) {
          const serverData = await connectQuery.refetch().then(r => {
            if (r.error) throw r.error;
            return r.data;
          });

          if ('undefined' === typeof serverData.users || null === serverData.users) {
            showOverlay('This channel does not exists!', <a href="/play" target="_self">Restart game!</a>);
            console.error('This channel does not exists!');
            return;
          }

          rpcUsersRef.current = serverData.users;
          openEventSource();
          wInit(serverData.revealedCells || []);
        } else {
          await startMutation.mutateAsync();
          openEventSource();
          wInit();
        }

        isEnvDev && console.info('Connection initialised — joining channel');
        await joinMutation.mutateAsync();
      } catch (e) {
        isEnvDev && console.error('Connection error', e);
        setTimeout(() => window.location.reload(), 500);
      }
    })();

    window.addEventListener('pagehide', () => navigator.sendBeacon('/api/game/leave/' + gameAssoc));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  /** UI-facing callbacks */

  const onClick = async coords => {
    const activeColor = activePlayerRef.current ? 'blue' : 'red';
    if (activeColor !== webPlayerRef.current) return;

    const [r, c] = coords;
    if (cells[r]?.[c]?.active) return;

    const stepElapsed = getStepElapsed(activePlayerRef.current, isGameRunningRef.current);
    const dataPack = { coords, player: activeColor, bomb: bombSelectedRef.current, resign: null, stepElapsed };

    if (connectionLostRef.current) {
      stepCacheRef.current.push(dataPack);
      return;
    }

    try {
      const result = await stepMutation.mutateAsync(dataPack);
      applyStep(result);
      makeGameEndIfItEnds(result.bluePoints, result.redPoints, false, result.leftMines);
    } catch (e) {
      isEnvDev && console.error('Step error', e);
    }
  };

  const clickResign = () => {
    const color = activePlayerRef.current ? 'blue' : 'red';
    const stepElapsed = getStepElapsed(activePlayerRef.current, isGameRunningRef.current);
    stepMutation.mutate({ resign: color, stepElapsed });
    resignProcess(webPlayerRef.current);
  };

  const resign = () => {
    const activeColor = activePlayerRef.current ? 'blue' : 'red';
    if (webPlayerRef.current !== activeColor) return;
    showOverlay('Are u sure u want to resign?!', (
      <div className="resign">
        <a onClick={clickResign}>Yes</a>
        <a onClick={hideOverlay}>No!</a>
      </div>
    ));
  };

  return { onClick, resign };
};

export default useServerCommunication;
