import {
	addRoute,
	navigateTo,
	setTitle,
	type RouteHandlerParams,
	type RouteHandlerReturn,
} from "@app/routing";
import authHtml from "./pong.html?raw";
import tourScoresHtml from "./tourTable.html?raw";
import io from "socket.io-client";
import {
	JoinRes,
	type CSocket,
	type GameMove,
	type GameUpdate,
	type TourInfo,
} from "./socket";
import { showError, showInfo, showSuccess } from "@app/toast";
import { getUser as getSelfUser, type User } from "@app/auth";
import { isNullish } from "@app/utils";
import client from "@app/api";
import "./pong.css";
import { quitChat } from "@app/chat/chatHelperFunctions/quitChat";

declare module "ft_state" {
	interface State {
		pongSock?: CSocket;
		pongKeepAliveInterval?: ReturnType<typeof setInterval>;
	}
}

enum QueueState {
	InQueu = "In Queue",
	InGame = "In Game",
	Ready = "Ready-ing",
	Iddle = "Queue Up",
	In_local = "In Local",
}

enum ReadyState {
	readyUp = "ready ok",
	readyDown = "not ready",
}

enum TourBtnState {
	Joined = "Joined",
	Started = "Started",
	AbleToJoin = "Join Tournament",
	AbleToCreate = "Create Tournament",
	AbleToStart = "Start Tournament",
	AbeToProcreate = "He would be proud",
}

enum TourInfoState {
	Running = "🟢",
	Owner = "👑",
	Registered = "✅",
	NotRegisted = "❌",
	NoTournament = "⚪️",
}

document.addEventListener("ft:pageChange", (newUrl) => {
	if (window.__state.pongSock !== undefined) window.__state.pongSock.close();
	if (window.__state.pongKeepAliveInterval !== undefined)
		clearInterval(window.__state.pongKeepAliveInterval);
	window.__state.pongSock = undefined;
	window.__state.pongKeepAliveInterval = undefined;
});

export function getSocket(): CSocket {
	if (window.__state.pongSock === undefined) {
		window.__state.pongSock = io(window.location.host, {
			path: "/api/pong/socket.io/",
		}) as any as CSocket;
	}
	if (window.__state.pongKeepAliveInterval === undefined) {
		window.__state.pongKeepAliveInterval = setInterval(() => {
			window.__state.pongSock?.emit("hello");
		}, 100);
	}
	return window.__state.pongSock;
}

function pongClient(
	_url: string,
	_args: RouteHandlerParams,
): RouteHandlerReturn {
	setTitle("Pong Game");
	const urlParams = new URLSearchParams(window.location.search);
	let game_req_join = urlParams.get("game");
	// todo:
	// [ ] shape sock
	// 	- [ ] joinGame (guid) -> ["ok"|"no, dont ever talk to my kid or me ever again you creep"];
	//	- [ ] launch newgame evt?
	let inTournament = false;

	return {
		html: authHtml,
		postInsert: async (app) => {
			const DEFAULT_COLOR = "white";
			const SELF_COLOR = "red";

			const user = getSelfUser();
			let currentGame: {
				game: GameUpdate;
				spectating: boolean;
				playerL: { id: string; name: string; self: boolean };
				playerR: { id: string; name: string; self: boolean };
			} | null = null;
			const rdy_btn =
				document.querySelector<HTMLButtonElement>("#readyup-btn");
			const batLeft = document.querySelector<HTMLDivElement>("#batleft");
			const batRight =
				document.querySelector<HTMLDivElement>("#batright");
			const ball = document.querySelector<HTMLDivElement>("#ball");
			const score =
				document.querySelector<HTMLDivElement>("#score-board");
			const playerL =
				document.querySelector<HTMLDivElement>("#player-left");
			const playerR =
				document.querySelector<HTMLDivElement>("#player-right");
			const queueBtn =
				document.querySelector<HTMLButtonElement>("#QueueBtn");
			const LocalGameBtn =
				document.querySelector<HTMLButtonElement>("#LocalBtn");
			const gameBoard =
				document.querySelector<HTMLDivElement>("#pongbox");
			const queue_infos =
				document.querySelector<HTMLSpanElement>("#queue-info");
			const how_to_play_btn =
				document.querySelector<HTMLButtonElement>("#play-info");
			const protips =
				document.querySelector<HTMLDivElement>("#protips-box");
			const end_scr =
				document.querySelector<HTMLDivElement>("#pong-end-screen");
			const tournamentBtn =
				document.querySelector<HTMLButtonElement>("#TourBtn");
			const tour_infos =
				document.querySelector<HTMLButtonElement>("#tour-info");
			const tour_scores =
				document.querySelector<HTMLDivElement>("#tourscore-box");

			let socket = getSocket();

			if (isNullish(user)) {
				// if no user (no loggin / other) : GTFO
				navigateTo("/app");
				return;
			}
			if (
				!batLeft ||
				!batRight ||
				!ball ||
				!score ||
				!queueBtn ||
				!playerL ||
				!playerR ||
				!gameBoard ||
				!queue_infos ||
				!LocalGameBtn ||
				!rdy_btn ||
				!end_scr ||
				!tournamentBtn ||
				!tour_infos ||
				!tour_scores ||
				!how_to_play_btn ||
				!protips
			)
				// sanity check
				return showError("fatal error");

			tournamentBtn.addEventListener("click", () => {
				showInfo(`Button State: ${tournamentBtn.innerText}`);

				switch (tournamentBtn.innerText) {
					case TourBtnState.AbleToStart:
						socket.emit("tourStart");
						break;
					case TourBtnState.AbleToJoin:
						socket.emit("tourRegister");
						break;
					case TourBtnState.AbleToCreate:
						socket.emit("tourCreate");
						break;
					case TourBtnState.AbeToProcreate:
						showError("We are developpers, this is impossible !");
						break;
					case TourBtnState.Joined:
						socket.emit("tourUnregister");
						break;
					case TourBtnState.Started:
						break;
				}
			});

			// ---
			// keys handler
			// ---
			const keys: Record<string, boolean> = {};
			if (how_to_play_btn && protips)
				how_to_play_btn.addEventListener("click", () => {
					protips.classList.toggle("hidden");
					how_to_play_btn.innerText =
						how_to_play_btn.innerText === "?" ? "x" : "?";
				});

			document.addEventListener("keydown", (e) => {
				keys[e.key.toLowerCase()] = true;
			});
			document.addEventListener("keyup", (e) => {
				keys[e.key.toLowerCase()] = false;
			});

			tour_infos.addEventListener("click", () => {
				tour_scores.classList.toggle("hidden");
			});

			setInterval(() => {
				// key sender
				if (keys["escape"] === true) {
					protips.classList.add("hidden");
					tour_scores.classList.add("hidden");
					how_to_play_btn.innerText = "?";
				}
				if (queueBtn.innerText !== QueueState.InGame)
					//we're in game ? continue | gtfo
					return;
				// we are not in a game OR we are spectating a game => gtfo
				if (currentGame === null || currentGame.spectating) return;

				let packet: GameMove = {
					move: null,
					moveRight: null,
				};

				if (queueBtn.innerText !== QueueState.InGame)
					//we're in game ? continue | gtfo
					return;
				if (currentGame === null) return;

				if (keys["w"] !== keys["s"])
					packet.move = keys["w"] ? "up" : "down";
				if (currentGame.game.local && keys["o"] !== keys["l"])
					packet.moveRight = keys["o"] ? "up" : "down";
				socket.emit("gameMove", packet);
			}, 1000 / 60);
			// ---
			// keys end
			// ---

			// ---
			// join game
			// ---
			if (game_req_join != null) {
				socket.emit("joinGame", game_req_join, (res: JoinRes) => {
					switch (res) {
						case JoinRes.yes:
							showInfo("JoinRes = yes");
							quitChat();
							break;
						case JoinRes.no:
							showInfo("JoinRes = no");
							break;
						default:
							showError("JoinRes switch fail:" + res);
					}
				});
				game_req_join = null;
			}
			// ---
			// join game end
			// ---

			// ---
			// position logic (client)
			// ---
			const DEFAULT_POSITIONS: GameUpdate = {
				gameId: "",
				ball: { size: 16, x: 800 / 2, y: 450 / 2 },
				left: {
					id: "",
					paddle: { x: 40, y: 185, width: 12, height: 80 },
					score: 0,
				},
				right: {
					id: "",
					paddle: { x: 748, y: 185, width: 12, height: 80 },
					score: 0,
				},
				local: false,
			};

			function resetBoard(
				batLeft: HTMLDivElement,
				batRight: HTMLDivElement,
				playerL: HTMLDivElement,
				playerR: HTMLDivElement,
			) {
				render(DEFAULT_POSITIONS);
				batLeft.style.backgroundColor = DEFAULT_COLOR;
				batRight.style.backgroundColor = DEFAULT_COLOR;
				playerR.style.color = "";
				playerL.style.color = "";
				playerR.innerText = "";
				playerL.innerText = "";
				currentGame = null;
			}
			let render_tour_score_once = false;

			const renderTournamentScores = (info: TourInfo) => {
				let players = info.players.sort((l, r) => r.score - l.score);

				const medals = ["🥇", "🥈", "🥉"];
				if (!render_tour_score_once) {
					tour_scores.innerHTML = tourScoresHtml;
					render_tour_score_once = true;
				}
				let table = tour_scores.querySelector("#tour-score-body");
				let table_shadow = document.createElement("tbody");
				if (table) {
					table_shadow.innerHTML = players
						.map(
							(player, idx) =>
								`<tr class="${player.id === user.id ? "bg-amber-400 hover:bg-amber-500" : "hover:bg-gray-50"}" data-id="${player.id}">
								<td class="px-4 py-2 text-sm text-gray-800 text-center border-b font-semibold min-w-100px"><span class="font-lg medal">${idx < medals.length ? medals[idx] : ""}</span>${player.name}</td>
								<td class="px-4 py-2 text-sm text-gray-800 text-center border-b font-bold min-w-100px">${player.score}</td>
							</tr>`,
						)
						.join("");
					if (table_shadow.innerHTML !== table.innerHTML) {
						table.innerHTML = table_shadow.innerHTML;
					}
				}
			};

			// TODO: REMOVE THIS
			// const makePlayer = (n: number) => ({ name: `user${n}`, id: `${n}`, score: n })
			//
			// renderTournamentScores({
			// 	state: "playing",
			// 	ownerId: "ownerID",
			// 	remainingMatches: null,
			// 	players: [...Array.from(Array(10).keys()).map(makePlayer), { id: user.id, name: user.name, score: 99 }],
			// });

			const render = (state: GameUpdate) => {
				batLeft.style.top = `${state.left.paddle.y}px`;
				batLeft.style.left = `${state.left.paddle.x}px`;
				batLeft.style.width = `${state.left.paddle.width}px`;
				batLeft.style.height = `${state.left.paddle.height}px`;

				batRight.style.top = `${state.right.paddle.y}px`;
				batRight.style.left = `${state.right.paddle.x}px`;
				batRight.style.width = `${state.right.paddle.width}px`;
				batRight.style.height = `${state.right.paddle.height}px`;

				ball.style.transform = `translateX(${state.ball.x - state.ball.size}px) translateY(${state.ball.y - state.ball.size}px)`;
				ball.style.height = `${state.ball.size * 2}px`;
				ball.style.width = `${state.ball.size * 2}px`;

				score.innerText = `${state.left.score} | ${state.right.score}`;
			};
			socket.on("gameUpdate", (state: GameUpdate) => {
				updateCurrentGame(state);
				render(state);
			});

			socket.on("tourEnding", (ending) => {
				inTournament = false;
				showInfo(ending);
			});
			// ---
			// position logic (client) end
			// ---

			// ---
			// queue evt
			// ---
			// utils
			async function getUser(
				user: string,
			): Promise<{ id: string; name: string | null }> {
				let t = await client.getUser({ user });

				if (t.kind === "success")
					return { id: user, name: t.payload.name };
				return { id: user, name: null };
			}

			// btn setup
			queueBtn.addEventListener("click", () => {
				if (inTournament) {
					showError("You can't queue up currently !");
					return;
				}
				if (queueBtn.innerText !== QueueState.Iddle) {
					if (queueBtn.innerText === QueueState.InQueu) {
						socket.emit("dequeue");
						queueBtn.innerText = QueueState.Iddle;
					}
					return;
				}
				queueBtn.innerText = QueueState.InQueu;
				socket.emit("enqueue");
			});
			LocalGameBtn.addEventListener("click", () => {
				if (
					queueBtn.innerText !== QueueState.Iddle ||
					currentGame !== null ||
					inTournament
				) {
					showError("cant launch a game currently");
					return;
				}
				socket.emit("localGame");
				queueBtn.innerText = QueueState.In_local;
				LocalGameBtn.innerText = "playing";
			});
			rdy_btn.addEventListener("click", () => {
				showInfo("rdy-evt");
				switch (rdy_btn.innerText) {
					case ReadyState.readyDown:
						socket.emit("readyUp");
						rdy_btn.innerText = ReadyState.readyUp;
						rdy_btn.classList.remove("text-red-600");
						rdy_btn.classList.add("text-green-600");
						break;
					case ReadyState.readyUp:
						socket.emit("readyDown");
						rdy_btn.innerText = ReadyState.readyDown;
						rdy_btn.classList.remove("text-green-600");
						rdy_btn.classList.add("text-red-600");
						break;
					default:
						showError("error on ready btn");
				}
			});

			const updateCurrentGame = async (state: GameUpdate) => {
				const normalizeUser = (
					u: { id: string; name: string | null },
					d: string,
				) => {
					return {
						id: u.id,
						name: u.name ?? d,
						self: u.id === user.id,
					};
				};
				if (currentGame === null)
					currentGame = {
						spectating: !(
							state.left.id === user.id ||
							state.right.id === user.id
						),
						game: state,
						playerL: normalizeUser(
							await getUser(state.left.id),
							"left",
						),
						playerR: normalizeUser(
							await getUser(state.right.id),
							"right",
						),
					};
				else currentGame.game = state;
				if (
					(currentGame && currentGame?.game.local) ||
					currentGame?.playerL.self
				) {
					batLeft!.style.backgroundColor = SELF_COLOR;
					playerL!.style.color = SELF_COLOR;
				}
				if (
					currentGame &&
					!currentGame?.game.local &&
					currentGame?.playerR.self
				) {
					batRight!.style.backgroundColor = SELF_COLOR;
					playerR!.style.color = SELF_COLOR;
				}
				playerL!.innerText = currentGame!.playerL.name;
				playerR!.innerText = currentGame!.playerR.name;
			};

			socket.on("newGame", async (state) => {
				currentGame = null;
				updateCurrentGame(state);
				render(state);

				tour_scores.classList.add("hidden");
				queueBtn.innerText = QueueState.InGame;
				queueBtn.style.color = "red";
				batLeft.style.backgroundColor = DEFAULT_COLOR;
				batRight.style.backgroundColor = DEFAULT_COLOR;
				rdy_btn.classList.remove("hidden");
				rdy_btn.classList.add("text-red-600");
				rdy_btn.innerText = ReadyState.readyDown;
			});
			socket.on("rdyEnd", () => {
				rdy_btn.classList.remove("text-green-600");
				rdy_btn.classList.remove("text-red-600");
				rdy_btn.classList.add("hidden");
			});

			socket.on("gameEnd", (winner) => {
				rdy_btn.classList.add("hidden");
				queueBtn.innerHTML = QueueState.Iddle;
				queueBtn.style.color = "white";

				if (!isNullish(currentGame)) {
					let end_txt: string = "";
					if (
						(user.id === currentGame.game.left.id &&
							winner === "left") ||
						(user.id === currentGame.game.right.id &&
							winner === "right")
					)
						end_txt = "you won! #yippe";
					else end_txt = "you lost #sadge";
					if (currentGame.spectating)
						end_txt = `${winner === "left" ? currentGame.playerL.name : currentGame.playerR.name} won #gg`;
					end_scr.innerText = end_txt;
					end_scr.classList.remove("hidden");
					setTimeout(() => {
						end_scr.classList.add("hidden");
					}, 3 * 1000);

					if (currentGame.game.local) {
						LocalGameBtn.innerText = "Local Game";
					}
				}
				resetBoard(batLeft, batRight, playerL, playerR);
			});
			socket.on("updateInformation", (e) => {
				queue_infos.innerText = `${e.totalUser}👤 ${e.inQueue}⏳ ${e.totalGames}▮•▮`;
			});
			socket.on("queueEvent", (e) => {
				if (e === "registered") queueBtn.innerText = QueueState.InQueu;
				else if (e === "unregistered")
					queueBtn.innerText = QueueState.Iddle;
				showInfo(`QueueEvent: ${e}`);
			}); // MAYBE: play a sound? to notify user that smthing happend
			// ---
			// queue evt end
			// ---

			socket.on("tournamentInfo", (s) => {
				// no tournament => we can create it !
				if (s === null) {
					tournamentBtn.innerText = TourBtnState.AbleToCreate;
					// create tournament
					tour_infos.innerText = `${TourInfoState.NoTournament} 0👤 0▮•▮`;
					return;
				}

				let weIn = s.players.some((p) => p.id === user.id);
				let imOwner = s.ownerId === user.id;
				switch (s.state) {
					case "ended":
						inTournament = false;
						tournamentBtn.innerText = TourBtnState.AbleToCreate;
						break;
					case "playing":
						inTournament = weIn;
						tournamentBtn.innerText = TourBtnState.Started;
						tour_infos.innerText = `${TourInfoState.Running} ${s.players.length}👤 ${s.remainingMatches ?? "?"}▮•▮`;
						break;
					case "prestart":
						inTournament = weIn;
						tour_infos.innerText = `${imOwner ? TourInfoState.Owner : weIn ? TourInfoState.Registered : TourInfoState.NotRegisted} ${s.players.length}👤 ?▮•▮`;
						if (imOwner) {
							tournamentBtn.innerText = TourBtnState.AbleToStart;
						} else {
							tournamentBtn.innerText = weIn
								? TourBtnState.Joined
								: TourBtnState.AbleToJoin;
						}
						break;
				}
				renderTournamentScores(s);
			});

			socket.on("tournamentRegister", ({ kind, msg }) => {
				if (kind === "success") showSuccess(msg ?? "Success");
				if (kind === "failure") showError(msg ?? "An error Occured");
			});

			// init
			rdy_btn.classList.add("hidden");
			queueBtn.innerText = QueueState.Iddle;
			rdy_btn.innerText = ReadyState.readyUp;
			resetBoard(batLeft, batRight, playerL, playerR);
		},
	};
}
addRoute("/pong", pongClient);
