import React, { useState, useEffect, useContext } from "react";
import { useParams } from "react-router-dom";
import useWebSocket, { ReadyState } from "react-use-websocket";

import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button";

import { GlobalContext, GlobalContextType } from "./GlobalContext";
import { PlayerList } from "./PlayerList";
import { Chat } from "./Chat";
import { Board } from "./Board";
import { Actions } from "./Actions";
import { ws_base, base } from "./Common";
import { GameOrder } from "./GameOrder";
import { Activities } from "./Activities";
import { SelectPlayer } from "./SelectPlayer";
import { PlayersStatus } from "./PlayersStatus";
import { ViewCard } from "./ViewCard";
import { ChooseCard } from "./ChooseCard";
import { Hand } from "./Hand";
import { Trade } from "./Trade";
import { Winner } from "./Winner";
import { HouseRules } from "./HouseRules";
import { Dice } from "./Dice";
import { assetsPath } from "./Common";
import { Session, Room } from "./GlobalContext";
// history replaced by react-router's useNavigate
import "./RoomView.css";
import equal from "fast-deep-equal";

import itsYourTurnAudio from "./assets/its-your-turn.mp3";
import robberAudio from "./assets/robber.mp3";
import knightsAudio from "./assets/the-knights-who-say-ni.mp3";
import volcanoAudio from "./assets/volcano-eruption.mp3";
import { ConnectionStatus } from "./ConnectionStatus";
import NameSetter from "./NameSetter";

const audioFiles: Record<string, string> = {
  "its-your-turn.mp3": itsYourTurnAudio,
  "robber.mp3": robberAudio,
  "the-knights-who-say-ni.mp3": knightsAudio,
  "volcano-eruption.mp3": volcanoAudio,
};

type AudioEffect = HTMLAudioElement & { hasPlayed?: boolean };
const audioEffects: Record<string, AudioEffect | undefined> = {};

const loadAudio = (src: string) => {
  const audio = document.createElement("audio") as AudioEffect;
  audio.src = audioFiles[src];
  audio.setAttribute("preload", "auto");
  audio.setAttribute("controls", "none");
  audio.style.display = "none";
  document.body.appendChild(audio);
  audio.load();
  audio.addEventListener("error", (e) => console.error("Audio load error:", e, audio.src));
  return audio;
};

type RoomProps = {
  session: Session;
  setSession: React.Dispatch<React.SetStateAction<Session | null>>;
  setError: React.Dispatch<React.SetStateAction<string | null>>;
};

const RoomView = (props: RoomProps) => {
  const { session, setSession, setError } = props;
  const [socketUrl, setSocketUrl] = useState<string | null>(null);
  const { roomName = "default" } = useParams<{ roomName: string }>();
  const [reconnectAttempt, setReconnectAttempt] = useState<number>(0);

  const [name, setName] = useState<string>("");
  const [warning, setWarning] = useState<string | undefined>(undefined);
  const [loaded, setLoaded] = useState<boolean>(false);

  type Turn = { color?: string; roll?: number; actions?: string[]; select?: Record<string, number> };
  type PrivateType = { name?: string; color?: string; turnNotice?: string };

  const [dice, setDice] = useState<number[] | undefined>(undefined);
  const [state, setState] = useState<string | undefined>(undefined);
  const [color, setColor] = useState<string | undefined>(undefined);
  const [priv, setPriv] = useState<PrivateType | undefined>(undefined);
  const [turn, setTurn] = useState<Turn | undefined>(undefined);
  const [buildActive, setBuildActive] = useState<boolean>(false);
  const [tradeActive, setTradeActive] = useState<boolean>(false);
  const [cardActive, setCardActive] = useState<unknown>(undefined);
  const [houseRulesActive, setHouseRulesActive] = useState<boolean>(false);
  const [winnerDismissed, setWinnerDismissed] = useState<boolean>(false);
  const [count, setCount] = useState<number>(0);
  const [audio, setAudio] = useState<boolean>(
    localStorage.getItem("audio") ? JSON.parse(localStorage.getItem("audio") as string) : false
  );
  const [animations, setAnimations] = useState<boolean>(
    localStorage.getItem("animations") ? JSON.parse(localStorage.getItem("animations") as string) : false
  );
  const [volume, setVolume] = useState<number>(
    localStorage.getItem("volume") ? parseFloat(localStorage.getItem("volume") as string) : 0.5
  );
  const fields = ["id", "state", "color", "name", "private", "dice", "turn"];

  const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket(socketUrl, {
    onOpen: () => {
      console.log("app - WebSocket connection opened.");
      if (reconnectAttempt > 0) {
        console.log("app - WebSocket reconnected after connection loss, refreshing room state");
      }
      sendJsonMessage({ type: "get", fields });
      setReconnectAttempt(0);
    },
    onClose: () => {
      console.log("app - WebSocket connection closed.");
      setReconnectAttempt((prev) => prev + 1);
    },
    onError: (event: Event) => {
      console.error("app - WebSocket error observed:", event);
      // If we get a WebSocket error, it might be due to invalid room ID
      // Reset the room state to force recreation
      console.log("app - WebSocket error, clearing room state to force refresh");
      setSocketUrl(null);
    },
    shouldReconnect: (closeEvent) => {
      // Don't reconnect if the room doesn't exist (4xx errors)
      if (closeEvent.code >= 4000 && closeEvent.code < 5000) {
        console.log("app - WebSocket closed with client error, not reconnecting");
        return false;
      }
      return true;
    },
    reconnectInterval: 5000, // Retry every 5 seconds
    onReconnectStop: (numAttempts) => {
      console.log(`Stopped reconnecting after ${numAttempts} attempts`);
    },
    share: true,
  });

  useEffect(() => {
    const socketUrl = `${ws_base}/${roomName}`;
    console.log("app - connecting to", socketUrl);
    setSocketUrl(socketUrl);
  }, [roomName, session.id]);

  useEffect(() => {
    if (!lastJsonMessage || !session) {
      return;
    }
    const data: any = lastJsonMessage;
    switch (data.type) {
      case "ping":
        // Respond to server ping immediately to maintain connection
        console.log("room-view - Received ping from server, sending pong");
        sendJsonMessage({ type: "pong" });
        break;
      case "error":
        console.error(`room-view - error`, data.error);
        setError(data.data.error || JSON.stringify(data));
        break;
      case "warning":
        console.warn(`room-view - warning`, data.warning);
        setWarning(data.warning);
        setTimeout(() => {
          setWarning("");
        }, 3000);
        break;
      case "game-update":
        if (!loaded) {
          setLoaded(true);
        }
        console.log(`app - message - ${data.type}`, data.update);

        if ("private" in data.update && !equal(priv, data.update.private)) {
          const priv = data.update.private;
          if (priv.name !== name) {
            setName(priv.name);
            // Mirror the name into the shared session so consumers that read
            // `session.name` (eg. MediaAgent) will see the name and can act
            // (for example, initiate the media join).
            try {
              setSession((s) => (s ? { ...s, name: priv.name } : s));
            } catch (e) {
              console.warn("Failed to set session name from private payload", e);
            }
          }
          if (priv.color !== color) {
            setColor(priv.color);
          }
          setPriv(priv);
        }

        if ("name" in data.update) {
          if (data.update.name) {
            setName(data.update.name);
            // Also update the session object so components using session.name
            // immediately observe the change.
            try {
              setSession((s) => (s ? { ...s, name: data.update.name } : s));
            } catch (e) {
              console.warn("Failed to set session name from name payload", e);
            }
          } else {
            setWarning("");
            setError("");
            setPriv(undefined);
          }
        }
        if ("state" in data.update && data.update.state !== state) {
          if (data.update.state !== "winner" && winnerDismissed) {
            setWinnerDismissed(false);
          }
          setState(data.update.state);
        }
        if ("dice" in data.update && !equal(data.update.dice, dice)) {
          setDice(data.update.dice);
        }
        if ("turn" in data.update && !equal(data.update.turn, turn)) {
          setTurn(data.update.turn);
        }
        if ("color" in data.update && data.update.color !== color) {
          setColor(data.update.color);
        }
        break;
      default:
        break;
    }
  }, [lastJsonMessage, session]);

  useEffect(() => {
    if (state === "volcano") {
      if (audio) {
        if (!audioEffects.volcano) {
          audioEffects.volcano = loadAudio("volcano-eruption.mp3");
          audioEffects.volcano.volume = volume * volume;
        } else {
          if (!audioEffects.volcano.hasPlayed && audioEffects.volcano.readyState >= 2) {
            audioEffects.volcano.hasPlayed = true;
            audioEffects.volcano.play().catch((e) => console.error("Audio play failed:", e));
          }
        }
      } else {
        // Audio disabled -> stop any currently playing volcano effect
        if (audioEffects.volcano) {
          try {
            audioEffects.volcano.pause();
            audioEffects.volcano.currentTime = 0;
          } catch (e) {
            /* ignore */
          }
          audioEffects.volcano.hasPlayed = false;
        }
      }
    } else {
      if (audioEffects.volcano) {
        audioEffects.volcano.hasPlayed = false;
      }
    }
  }, [state, volume]);

  useEffect(() => {
    // When audio is enabled we may create/play effects; when disabled ensure
    // any existing effects are stopped and reset.
    if (audio) {
      if (turn && turn.color === color && state !== "room") {
        if (!audioEffects.yourTurn) {
          audioEffects.yourTurn = loadAudio("its-your-turn.mp3");
          audioEffects.yourTurn.volume = volume * volume;
        } else {
          if (!audioEffects.yourTurn.hasPlayed && audioEffects.yourTurn.readyState >= 2) {
            audioEffects.yourTurn.hasPlayed = true;
            audioEffects.yourTurn.play().catch((e) => console.error("Audio play failed:", e));
          }
        }
      } else if (turn) {
        if (audioEffects.yourTurn) {
          audioEffects.yourTurn.hasPlayed = false;
        }
      }

      if (turn && turn.roll === 7) {
        if (!audioEffects.robber) {
          audioEffects.robber = loadAudio("robber.mp3");
          audioEffects.robber.volume = volume * volume;
        } else {
          if (!audioEffects.robber.hasPlayed && audioEffects.robber.readyState >= 2) {
            audioEffects.robber.hasPlayed = true;
            audioEffects.robber.play().catch((e) => console.error("Audio play failed:", e));
          }
        }
      } else if (turn) {
        if (audioEffects.robber) {
          audioEffects.robber.hasPlayed = false;
        }
      }

      if (turn && turn.actions && turn.actions.indexOf("playing-knight") !== -1) {
        if (!audioEffects.knights) {
          audioEffects.knights = loadAudio("the-knights-who-say-ni.mp3");
          audioEffects.knights.volume = volume * volume;
        } else {
          if (!audioEffects.knights.hasPlayed && audioEffects.knights.readyState >= 2) {
            audioEffects.knights.hasPlayed = true;
            audioEffects.knights.play().catch((e) => console.error("Audio play failed:", e));
          }
        }
      } else if (turn && turn.actions && turn.actions.indexOf("playing-knight") === -1) {
        if (audioEffects.knights) {
          audioEffects.knights.hasPlayed = false;
        }
      }
    } else {
      // audio disabled: stop any currently playing effects and reset their state
      const stopIfPlaying = (ae?: AudioEffect) => {
        if (!ae) return;
        try {
          ae.pause();
          ae.currentTime = 0;
        } catch (e) {
          /* ignore */
        }
        ae.hasPlayed = false;
      };
      stopIfPlaying(audioEffects.yourTurn);
      stopIfPlaying(audioEffects.robber);
      stopIfPlaying(audioEffects.knights);
    }
  }, [state, turn, color, volume]);

  useEffect(() => {
    for (const key in audioEffects) {
      if (audioEffects[key]) {
        try {
          audioEffects[key]!.volume = volume * volume;
        } catch (e) {
          /* ignore */
        }
      }
    }
  }, [volume]);

  if (readyState !== ReadyState.OPEN || !session) {
    return <ConnectionStatus readyState={readyState} reconnectAttempt={reconnectAttempt} />;
  }

  return (
    <GlobalContext.Provider value={{ socketUrl, session, name, roomName, sendJsonMessage, lastJsonMessage, readyState }}>
      <div className="RoomView">
        {!name ? (
          <NameSetter />
        ) : (
          <>
            <div className="ActivitiesBox">
              <Activities />
              {dice && dice.length && (
                <div className="DiceRoll">
                  {dice.length === 1 && <div>Volcano roll!</div>}
                  {dice.length === 2 && <div>Current roll</div>}
                  <div>
                    <Dice pips={dice[0]} />
                    {dice.length === 2 && <Dice pips={dice[1]} />}
                  </div>
                </div>
              )}
            </div>
            <div className="Game">
              <div className="Dialogs">
                {priv && priv.turnNotice && (
                  <div className="Dialog TurnNoticeDialog">
                    <Paper className="TurnNotice">
                      <div>{priv.turnNotice}</div>
                      <Button
                        onClick={() => {
                          sendJsonMessage({ type: "turn-notice" });
                        }}
                      >
                        dismiss
                      </Button>
                    </Paper>
                  </div>
                )}
                {warning && (
                  <div className="Dialog WarningDialog">
                    <Paper className="Warning">
                      <div>{warning}</div>
                      <Button
                        onClick={() => {
                          setWarning("");
                        }}
                      >
                        dismiss
                      </Button>
                    </Paper>
                  </div>
                )}
                {state === "normal" && <SelectPlayer />}
                {color && state === "game-order" && <GameOrder />}

                {!winnerDismissed && <Winner {...{ winnerDismissed, setWinnerDismissed }} />}
                {houseRulesActive && <HouseRules {...{ houseRulesActive, setHouseRulesActive }} />}
                <ViewCard {...{ cardActive, setCardActive }} />
                <ChooseCard />
              </div>

              <Board animations={animations} />
              <PlayersStatus active={false} />
              <PlayersStatus active={true} />
              <Hand {...{ buildActive, setBuildActive, setCardActive }} />
            </div>
            <div className="Sidebar">
              {name !== "" && volume !== undefined && (
                <Paper className="Volume">
                  <div>Audio effects</div>{" "}
                  <input
                    type="checkbox"
                    id="audio"
                    name="audio"
                    defaultChecked={audio ? true : false}
                    onInput={() => {
                      const value = !audio;
                      localStorage.setItem("audio", JSON.stringify(value));
                      setAudio(value);
                    }}
                  />
                  <div>Sound effects volume</div>{" "}
                  <input
                    type="range"
                    id="volume"
                    name="volume"
                    value={volume * 100}
                    min="0"
                    max="100"
                    onInput={(e) => {
                      const alpha = parseFloat((e.currentTarget as HTMLInputElement).value) / 100;

                      localStorage.setItem("volume", alpha.toString());
                      setVolume(alpha);
                    }}
                  />
                  <div>Animations</div>{" "}
                  <input
                    type="checkbox"
                    id="animations"
                    name="animations"
                    defaultChecked={animations ? true : false}
                    onInput={() => {
                      const value = !animations;
                      localStorage.setItem("animations", JSON.stringify(value));
                      setAnimations(value);
                    }}
                  />
                </Paper>
              )}
              {name && <PlayerList />}
              {tradeActive && <Trade />}
              {name !== "" && <Chat />}
              {/* name !== "" && <VideoFeeds/> */}
              {loaded && (
                <Actions
                  {...{
                    buildActive,
                    setBuildActive,
                    tradeActive,
                    setTradeActive,
                    houseRulesActive,
                    setHouseRulesActive,
                  }}
                />
              )}
            </div>
          </>
        )}
      </div>
    </GlobalContext.Provider>
  );
};

export { RoomView };
