import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useGameStore } from '../store/gameStore';
import { emit } from '../lib/socket';
import { leaveGame } from '../lib/leaveGame';
import { computePlayable, stashWinner } from '../lib/gameRules';
import { computeTotal } from '../lib/standings';
import { displayName } from '../lib/names';
import { useIsDesktop } from '../lib/useIsDesktop';
import { useFitScale } from '../lib/useFitScale';
import Hand from '../components/Hand';
import GuessControls from '../components/GuessControls';
import Trick from '../components/Trick';
import Standings from '../components/Standings';
import PlayerCircle from '../components/PlayerCircle';
import FaceDownCards from '../components/FaceDownCards';
import GameOver from './GameOver';
import type { Hand as HandCards, PlayerInfo, StashData } from '../types';

// A completed trick stays face-up for SETTLE_MS (so the last card visibly joins
// the pile), then is swept towards the winner over COLLECT_MS.
const SETTLE_MS = 1100;
const COLLECT_MS = 550;
// Sweep direction by the winner's seat offset from me: 0=me(bottom) 1=left 2=top 3=right.
const COLLECT_BY_OFFSET = ['collect-bottom', 'collect-left', 'collect-top', 'collect-right'];

export default function GameTable() {
  const navigate = useNavigate();
  const desktop = useIsDesktop();
  // Zooms the whole desktop board to fill the window (full width + height), so
  // cards, circles, text and the header all scale together. Up to 2.6×.
  const { containerRef, scale, contentWidth } = useFitScale([desktop], 860, 2.6);
  const myPlayer = useGameStore((s) => s.myPlayer);
  const gameStatus = useGameStore((s) => s.gameStatus);
  const hand = useGameStore((s) => s.hand);

  // Once a completed trick has been swept away, its key is remembered here so it
  // is not shown again while we wait for the winner to lead the next trick.
  const [dismissedKey, setDismissedKey] = useState<string | null>(null);
  // Turns on for the collect (fly-to-winner) phase, after the settle pause.
  const [collecting, setCollecting] = useState(false);

  const previousStash = gameStatus?.status.previous_stash ?? null;
  // Every game_status payload recreates the stash object, so identify the trick
  // by content — the timers must restart only when a *different* trick completes.
  const previousStashKey = previousStash
    ? `${previousStash.first_player}:${JSON.stringify(previousStash.cards)}`
    : null;

  // On first load of an already-running game (reconnect / restore-on-startup) a
  // completed trick is already present; adopt it as "already swept" so we don't
  // replay a stale sweep over the live board. A freshly started game has no
  // completed trick at this point, so its very first trick still animates.
  const booted = useRef(false);
  useEffect(() => {
    if (booted.current || !gameStatus) return;
    booted.current = true;
    if (previousStashKey) setDismissedKey(previousStashKey);
  }, [gameStatus, previousStashKey]);

  useEffect(() => {
    if (!previousStashKey) return;
    setCollecting(false);
    const settle = setTimeout(() => setCollecting(true), SETTLE_MS);
    const done = setTimeout(() => {
      setCollecting(false);
      setDismissedKey(previousStashKey);
    }, SETTLE_MS + COLLECT_MS);
    return () => {
      clearTimeout(settle);
      clearTimeout(done);
    };
  }, [previousStashKey]);

  // A just-completed trick that hasn't been swept away yet always wins the centre
  // — even once the winner has already led the next trick. The engine reveals that
  // lead card (and, at a round boundary, the next bidding phase) the instant the
  // 4th card lands, so without holding the pile here the sweep would be cut off
  // after every trick. Reading `previousStash` synchronously (rather than a state
  // set in an effect) also means the pile never blinks to empty on the frame the
  // 4th card lands — the last card simply joins the three already there, then the
  // whole pile is collected before the next trick takes over.
  const finishing = previousStashKey !== null && previousStashKey !== dismissedKey;

  // The new round's dealt hand arrives (via `player_cards`) the instant the last
  // trick of the previous round is played, but the centre oval is still sweeping
  // that trick away — hold an empty hand on screen (the previous round's last card
  // really was just played, the engine just never broadcasts that "0 cards" beat on
  // its own since it deals the new round in the same step) until the sweep finishes,
  // so the new cards don't appear before the previous round has visibly wrapped up.
  // Mid-round plays (round number unchanged) still update instantly, since that's
  // just the player's own card leaving their hand, not a fresh deal.
  const [displayedHand, setDisplayedHand] = useState<HandCards>(hand);
  const lastAppliedRoundRef = useRef<number | null>(gameStatus?.round_number ?? null);
  useEffect(() => {
    if (!gameStatus) return;
    const isNewRound = gameStatus.round_number !== lastAppliedRoundRef.current;
    if (isNewRound && finishing) {
      setDisplayedHand({}); // last card of the previous round is gone; new deal waits
      return;
    }
    lastAppliedRoundRef.current = gameStatus.round_number;
    setDisplayedHand(hand);
  }, [hand, gameStatus, finishing]);

  if (!gameStatus || !myPlayer) {
    return <p className="text-center text-green-dim pt-20 font-serif italic">Načítava sa…</p>;
  }

  const { completed, players, series_number, round_number, cards_in_round, status } = gameStatus;
  const {
    active_player,
    active_round_guesses,
    active_round_stashes,
    active_stash,
    standings = [],
    standings_guesses = [],
  } = status;

  if (completed) {
    return <GameOver players={players} standings={standings} />;
  }

  const myOrder = myPlayer.order;
  const isPlayPhase = active_stash !== undefined;
  const myTurnToPlay = isPlayPhase && active_player === myOrder;

  const activeCards = active_stash ? Object.keys(active_stash.cards).length : 0;
  const displayedStash: StashData | null = finishing
    ? previousStash
    : activeCards > 0 && active_stash
      ? active_stash
      : null;

  // During the collect phase, sweep the pile towards whoever won it.
  const collectAnim =
    collecting && finishing && previousStash
      ? COLLECT_BY_OFFSET[(stashWinner(previousStash) - myOrder + 4) % 4]
      : null;

  // Block play until the previous trick's sweep animation has finished — otherwise
  // the winner could lead the next card while the pile is still visibly clearing.
  const canPlayNow = myTurnToPlay && !finishing;

  const playableKeys = canPlayNow && active_stash
    ? computePlayable(displayedHand, active_stash.cards[String(active_stash.first_player)]?.color ?? null)
    : undefined;

  const activePlayerName = displayName(players.find((p) => p.order === active_player)?.name);

  // Seat mapping relative to "Ty": left / across / right.
  const seat = (offset: number): PlayerInfo | undefined =>
    players.find((p) => p.order === (myOrder + offset) % 4);
  const leftP = seat(1);
  const topP = seat(2);
  const rightP = seat(3);

  // Live round state of one seat, shaped as PlayerCircle props.
  const seatProps = (o?: number) => ({
    won: o === undefined ? 0 : active_round_stashes?.[o] ?? 0,
    guess: (o === undefined ? null : active_round_guesses?.[String(o)] ?? null) as number | null,
    active: o !== undefined && active_player === o,
  });

  // Exact cards still in a player's hand: started with cards_in_round, lost one
  // per completed trick, minus one more if they've already played this trick.
  const completedTricks = (active_round_stashes ?? []).reduce((a, b) => a + b, 0);
  const cardsInHandOf = (o?: number) => {
    if (o === undefined) return 0;
    const playedCurrent = active_stash?.cards[String(o)] ? 1 : 0;
    return Math.max(0, cards_in_round - completedTricks - playedCurrent);
  };

  const handleLeave = () => leaveGame(navigate);
  const handleEnd = () => {
    if (window.confirm('Naozaj ukončiť celú hru pre všetkých?')) {
      emit.endGame(gameStatus.gid);
    }
  };
  // The host can always end the game; other players only when the host is
  // currently offline, so an abandoned game isn't stuck forever waiting for
  // a host who won't come back, but it isn't open to casual misuse otherwise.
  const hostConnected = players.find((p) => p.order === 0)?.connected ?? false;
  const canEnd = myOrder === 0 || !hostConnected;

  // ── shared pieces ────────────────────────────────────────────────
  const bannerText = active_player === myOrder
    ? isPlayPhase
      ? 'Zahraj kartu'
      : 'Zadaj tip'
    : `${activePlayerName} ${isPlayPhase ? 'hrá' : 'tipuje'}`;

  const banner = (
    <div className="flex items-center justify-center gap-2">
      <span className="inline-block w-[7px] h-[7px] rounded-full bg-gold animate-tp flex-shrink-0" />
      <span className="font-serif italic text-[13px] text-gold-dim tracking-[.03em]">{bannerText}</span>
    </div>
  );

  const opponents = players
    .filter((p) => p.order !== myOrder)
    .sort((a, b) => a.order - b.order);

  const totalsRow = (compact: boolean) => (
    <div className="flex items-center justify-between gap-2">
      {opponents.map((p) => (
        <div key={p.order} className="text-center">
          <div className="uppercase tracking-[.1em] text-green-dim mb-0.5" style={{ fontSize: 11 }}>
            {displayName(p.name)}
          </div>
          <div className="font-serif text-green-score leading-none" style={{ fontSize: compact ? 16 : 20 }}>
            {computeTotal(standings, p.order)}
          </div>
        </div>
      ))}
      <div className="text-center rounded-lg px-3 py-1 bg-gold/[.06] border border-gold/[.15]">
        <div className="uppercase tracking-[.1em] text-gold mb-0.5" style={{ fontSize: 11 }}>
          {myPlayer.name}
        </div>
        <div className="font-serif font-semibold text-gold-dim leading-none" style={{ fontSize: compact ? 16 : 20 }}>
          {computeTotal(standings, myOrder)}
        </div>
      </div>
    </div>
  );

  // Center of the oval: trick during play, guess controls during bidding.
  // `finishing` also keeps the trick on screen while the round's *last* stash is
  // swept away: the engine advances to the next round's bidding the instant the
  // 4th card lands, so `isPlayPhase` flips to false immediately — without this,
  // that final trick would vanish straight into the guess controls with no sweep.
  const ovalContent = isPlayPhase || finishing ? (
    <div style={collectAnim ? { animation: `${collectAnim} ${COLLECT_MS}ms ease-in both` } : undefined}>
      <Trick stash={displayedStash} players={players} myOrder={myOrder} />
    </div>
  ) : (
    active_round_guesses !== undefined && active_player !== undefined ? (
      <GuessControls
        cardsInRound={cards_in_round}
        guesses={active_round_guesses}
        myOrder={myOrder}
        activePlayer={active_player}
        activePlayerName={activePlayerName}
      />
    ) : null
  );

  const topSeat = (
    <div className="flex flex-col items-center gap-1.5">
      <PlayerCircle name={displayName(topP?.name) || '—'} {...seatProps(topP?.order)} size={desktop ? 64 : 52} />
      <FaceDownCards count={cardsInHandOf(topP?.order)} direction="row" desktop={desktop} />
    </div>
  );

  const sideSeat = (p?: PlayerInfo) => (
    <div className="flex flex-col items-center gap-1.5">
      <PlayerCircle name={displayName(p?.name) || '—'} {...seatProps(p?.order)} size={desktop ? 60 : 48} />
      <FaceDownCards count={cardsInHandOf(p?.order)} direction="col" desktop={desktop} />
    </div>
  );

  const meSeat = (
    <div className="flex justify-center">
      <PlayerCircle name={myPlayer.name} {...seatProps(myOrder)} size={desktop ? 70 : 58} />
    </div>
  );

  const handArea = (
    <Hand hand={displayedHand} myTurn={canPlayNow} isPlayPhase={isPlayPhase} playableKeys={playableKeys} desktop={desktop} />
  );

  // ── DESKTOP LAYOUT ───────────────────────────────────────────────
  if (desktop) {
    return (
      <div ref={containerRef} className="h-[100dvh] w-full overflow-hidden bg-table">
        {/* Design canvas — fixed height, width spans the viewport; scaled as one
            unit so the whole board (and header) zooms with the window. */}
        <div
          className="flex"
          style={{ width: contentWidth, height: 860, transform: `scale(${scale})`, transformOrigin: 'top left' }}
        >
          {/* main */}
          <div className="flex-1 min-w-0 flex flex-col">
            {/* header */}
            <div className="shrink-0 h-[58px] bg-header flex items-center gap-4 px-6 border-b border-[#14221a]">
              <span className="font-serif uppercase tracking-[.14em] text-[15px] text-gold whitespace-nowrap">
                Bridžik
              </span>
              <div className="w-px h-[22px] bg-[#1a3a22]" />
              <span className="font-serif text-[12px] text-green-dim tracking-[.06em] whitespace-nowrap">
                Séria {series_number + 1} · Kolo {round_number + 1}
              </span>
              <div className="flex-1 flex items-center justify-center">{banner}</div>
              {totalsRow(true)}
              <div className="w-px h-[22px] bg-[#1a3a22]" />
              {canEnd && (
                <button onClick={handleEnd} className="text-[11px] text-[#8a8064] hover:text-gold whitespace-nowrap">
                  Ukončiť
                </button>
              )}
              <button onClick={handleLeave} className="text-[11px] text-[#7a7058] hover:text-gold whitespace-nowrap">
                Odísť
              </button>
            </div>

            {/* game content — players hug the edges so the felt uses full width */}
            <div className="flex-1 min-h-0 flex flex-col justify-center gap-3 px-16 py-4">
              {topSeat}

              <div className="flex items-center justify-center gap-16">
                {sideSeat(leftP)}
                <div
                  className="flex items-center justify-center rounded-full"
                  style={{
                    width: 620,
                    height: 372,
                    background:
                      'radial-gradient(ellipse at 42% 38%,#306845 0%,#1e5030 38%,#122e1c 72%,#091e12 100%)',
                    boxShadow:
                      'inset 0 10px 48px rgba(0,0,0,.72),0 0 0 3px rgba(0,0,0,.55),0 0 0 6px rgba(201,168,76,.1)',
                  }}
                >
                  {ovalContent}
                </div>
                {sideSeat(rightP)}
              </div>

              {meSeat}
            </div>

            {handArea}
          </div>

          {/* sidebar */}
          <Standings standings={standings} guesses={standings_guesses} players={players} myOrder={myOrder} desktop />
        </div>
      </div>
    );
  }

  // ── MOBILE LAYOUT ────────────────────────────────────────────────
  return (
    <div className="max-w-lg mx-auto min-h-screen flex flex-col">
      {/* header */}
      <div className="bg-header px-[18px] pt-[14px] pb-3 border-b border-[#14221a]">
        <div className="flex items-center justify-between mb-2.5">
          <span className="font-serif uppercase tracking-[.1em] text-[11px] text-gold">
            Séria {series_number + 1} · Kolo {round_number + 1}
          </span>
          <div className="flex items-center gap-3">
            {canEnd && (
              <button onClick={handleEnd} className="text-[11px] text-[#6a3030] hover:text-red-400">
                Ukončiť
              </button>
            )}
            <button onClick={handleLeave} className="text-[11px] text-[#7a7058] hover:text-gold">
              Odísť
            </button>
          </div>
        </div>
        {totalsRow(false)}
      </div>

      {/* turn banner */}
      <div
        className="py-[9px] px-4 border-b border-[#152a1a]"
        style={{ background: 'linear-gradient(90deg,#09190d,#14301e,#09190d)' }}
      >
        {banner}
      </div>

      {/* game area */}
      <div className="flex-1 bg-table px-2.5 pt-2.5 pb-1.5 flex flex-col">
        <div className="flex flex-col items-center mb-1.5">{topSeat}</div>

        <div className="flex items-center gap-1.5 mb-2">
          <div className="w-[54px] flex-shrink-0 flex justify-center">{sideSeat(leftP)}</div>
          <div
            className="flex-1 flex items-center justify-center rounded-full"
            style={{
              minHeight: 192,
              background:
                'radial-gradient(ellipse at 42% 38%,#306845 0%,#1e5030 38%,#122e1c 72%,#091e12 100%)',
              boxShadow:
                'inset 0 6px 32px rgba(0,0,0,.7),0 0 0 2px rgba(0,0,0,.5),0 0 0 4px rgba(201,168,76,.1)',
            }}
          >
            {ovalContent}
          </div>
          <div className="w-[54px] flex-shrink-0 flex justify-center">{sideSeat(rightP)}</div>
        </div>

        <div className="mb-1.5">{meSeat}</div>
      </div>

      {handArea}

      {/* score */}
      <div className="bg-table px-3 pb-4 pt-1">
        <Standings standings={standings} guesses={standings_guesses} players={players} myOrder={myOrder} />
      </div>
    </div>
  );
}
