import { useEffect, useMemo, useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { useSearchParams } from "react-router-dom";

import { api } from "../api/client";
import type { AudioClip, GameAssignment } from "../api/types";
import { ClipSummaryRow } from "../components/ClipSummaryRow";
import { useClipPlayback } from "../hooks/useClipPlayback";
import { useWalkupContext } from "../hooks/useWalkupContext";
import { loadPreparedGame } from "../lib/offlinePrep";
import { teamSnapQueryKeys, teamsnapClient } from "../lib/teamsnap";
import {
  formatGameDate,
  formatGameTitle,
  formatMemberName,
  formatMemberJerseyNumber,
  isPlayerMember,
  orderMembersByLineupAndRsvps,
} from "../lib/teamsnapHelpers";

function clipKey(kind: "assignment" | "library", id: number | string): string {
  return `${kind}:${id}`;
}

const DEFAULT_FADE_OUT_MS = 1000;

function getAvailabilityIconClass(statusCode: number | null | undefined): string {
  if (statusCode === 1) {
    return "bi-check-circle-fill text-success";
  }
  if (statusCode === 0) {
    return "bi-x-circle-fill text-danger";
  }
  if (statusCode === 2) {
    return "bi-question-circle-fill text-primary";
  }
  return "bi-circle-fill text-secondary";
}

function getAvailabilityDotLabel(statusCode: number | null | undefined): string {
  if (statusCode === 1) {
    return "Availability yes";
  }
  if (statusCode === 0) {
    return "Availability no";
  }
  if (statusCode === 2) {
    return "Availability maybe";
  }
  return "Availability unset";
}

export function GamedayPage() {
  const walkup = useWalkupContext();
  const [searchParams, setSearchParams] = useSearchParams();
  const [selectedGameId, setSelectedGameId] = useState(searchParams.get("gameId") ?? "");
  const [expandedPlayerId, setExpandedPlayerId] = useState("");
  const [playerFilter, setPlayerFilter] = useState<"players" | "nonPlayers" | "all">("players");
  const [playerFilterMenuOpen, setPlayerFilterMenuOpen] = useState(false);
  const playerFilterMenuRef = useRef<HTMLDivElement | null>(null);
  const teamId = walkup.selectedTeamId;
  const resolvedSelectedGameId =
    selectedGameId || searchParams.get("gameId") || (walkup.nextGame ? String(walkup.nextGame.id) : "");
  const {
    activeKey: playingClipKey,
    activeDetails: nowPlaying,
    fadeOutClip,
    isPlaying: isPlaybackPlaying,
    playClip: playClipPreview,
    stopClip,
  } = useClipPlayback();

  useEffect(() => {
    const requestedGameId = searchParams.get("gameId");
    if (requestedGameId) {
      setSelectedGameId(requestedGameId);
      return;
    }
    if (!selectedGameId && walkup.nextGame) {
      setSelectedGameId(String(walkup.nextGame.id));
    }
  }, [searchParams, selectedGameId, walkup.nextGame]);

  useEffect(() => {
    stopClip();
    setExpandedPlayerId("");
  }, [selectedGameId, stopClip]);

  useEffect(() => {
    if (!playerFilterMenuOpen) {
      return;
    }

    function handlePointerDown(event: PointerEvent) {
      if (!playerFilterMenuRef.current?.contains(event.target as Node)) {
        setPlayerFilterMenuOpen(false);
      }
    }

    function handleKeyDown(event: KeyboardEvent) {
      if (event.key === "Escape") {
        setPlayerFilterMenuOpen(false);
      }
    }

    document.addEventListener("pointerdown", handlePointerDown);
    document.addEventListener("keydown", handleKeyDown);
    return () => {
      document.removeEventListener("pointerdown", handlePointerDown);
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [playerFilterMenuOpen]);

  const assignmentsQuery = useQuery({
    queryKey: ["assignments", resolvedSelectedGameId],
    queryFn: () => api.listAssignments(resolvedSelectedGameId),
    enabled: Boolean(resolvedSelectedGameId),
    retry: 0,
    networkMode: "always",
  });

  const preparedGame = resolvedSelectedGameId ? loadPreparedGame(resolvedSelectedGameId) : null;
  const assignmentList = assignmentsQuery.data ?? preparedGame?.assignments ?? [];

  const eventLineupQuery = useQuery({
    queryKey: teamSnapQueryKeys.eventLineup(walkup.teamSnapCacheScope, teamId, resolvedSelectedGameId),
    queryFn: () => teamsnapClient.loadEventLineupData(teamId, resolvedSelectedGameId, walkup.teamSnapCacheScope),
    enabled: Boolean(teamId && resolvedSelectedGameId),
    networkMode: "always",
    retry: 0,
  });

  const availabilityQuery = useQuery({
    queryKey: teamSnapQueryKeys.availabilities(walkup.teamSnapCacheScope, teamId, resolvedSelectedGameId),
    queryFn: () => teamsnapClient.loadAvailabilities(teamId, resolvedSelectedGameId, walkup.teamSnapCacheScope),
    enabled: Boolean(teamId && resolvedSelectedGameId),
    networkMode: "always",
    retry: 0,
  });

  const orderedMembers = useMemo(
    () =>
      orderMembersByLineupAndRsvps(
        walkup.members,
        eventLineupQuery.data?.entries ?? [],
        availabilityQuery.data ?? [],
      ),
    [availabilityQuery.data, eventLineupQuery.data?.entries, walkup.members],
  );

  const visibleMembers =
    playerFilter === "all"
      ? orderedMembers
      : orderedMembers.filter((member) => (playerFilter === "players" ? isPlayerMember(member) : !isPlayerMember(member)));

  const selectedGame = walkup.games.find((game) => String(game.id) === resolvedSelectedGameId) ?? null;
  const isLoadingTeamSnapData =
    walkup.teamsQuery.isLoading ||
    walkup.membersQuery.isLoading ||
    walkup.eventsQuery.isLoading ||
    (Boolean(teamId && resolvedSelectedGameId) && (eventLineupQuery.isLoading || availabilityQuery.isLoading));

  function selectGame(gameId: string) {
    setSelectedGameId(gameId);
    setExpandedPlayerId("");
    stopClip();
    setSearchParams({ gameId });
  }

  function fadeOutPlayback(durationMs = DEFAULT_FADE_OUT_MS) {
    fadeOutClip(durationMs);
  }

  if (!walkup.isTeamSnap) {
    return (
      <section className="page-grid">
        <div className="panel">Reconnect with TeamSnap to run gameday mode.</div>
      </section>
    );
  }

  if (isLoadingTeamSnapData) {
    return (
      <section className="page-grid gameday-page">
        <div className="panel gameday-loading-panel" role="status" aria-live="polite">
          <div className="gameday-loading-spinner spinner-border text-primary" aria-hidden="true" />
          <div className="gameday-loading-copy">
            <span className="gameday-loading-label">Waiting for TeamSnap</span>
            <strong>Loading gameday data</strong>
            <span className="muted">Teams, games, roster data, and lineup details are being fetched.</span>
          </div>
        </div>
      </section>
    );
  }

  return (
    <section className="page-grid gameday-page">
      {isPlaybackPlaying && nowPlaying ? (
        <div className="gameday-toolbar">
          <div className="gameday-toolbar-copy">
            <span className="gameday-toolbar-label">Now Playing</span>
            <strong>{nowPlaying.title ?? "Clip"}</strong>
            {nowPlaying.subtitle ? <span className="muted">{nowPlaying.subtitle}</span> : null}
          </div>
          <div className="gameday-toolbar-actions">
            <button type="button" className="btn btn-danger btn-sm" onClick={() => stopClip()}>
              Stop
            </button>
            <button
              type="button"
              className="btn btn-outline-secondary btn-sm d-inline-flex align-items-center gap-2"
              onClick={() => fadeOutPlayback()}
            >
              <i className="bi bi-volume-down-fill" aria-hidden="true" />
              Fade out
            </button>
          </div>
        </div>
      ) : null}
      <div className="panel-grid">
        <div className="panel">
          <div className="d-grid gap-2">
            <label className="form-label d-grid gap-2">
              <span>Selected game</span>
              <select className="form-select" value={selectedGameId} onChange={(event) => selectGame(event.target.value)}>
                <option value="">Select a game</option>
                {walkup.games.map((game) => (
                  <option key={String(game.id)} value={String(game.id)}>
                    {formatGameTitle(game)}
                  </option>
                ))}
              </select>
            </label>
            <div className="text-body-secondary">
              {selectedGame ? formatGameDate(selectedGame) : "Choose a game to operate."}
            </div>
            {walkup.nextGame ? <div className="text-body-secondary">Next game: {formatGameTitle(walkup.nextGame)}</div> : null}
          </div>
        </div>
        <div className="panel stack">
          <div className="d-flex align-items-center justify-content-between gap-3 flex-wrap">
            <h2 className="mb-0">Players</h2>
            <div className="position-relative" ref={playerFilterMenuRef}>
              <button
                type="button"
                className="btn btn-outline-secondary btn-sm d-inline-flex align-items-center justify-content-center"
                onClick={() => setPlayerFilterMenuOpen((current) => !current)}
                aria-label="Filter players"
                aria-haspopup="menu"
                aria-expanded={playerFilterMenuOpen}
                title="Filter players"
              >
                <i className="bi bi-funnel" aria-hidden="true" />
              </button>
              {playerFilterMenuOpen ? (
                <div
                  className="dropdown-menu show dropdown-menu-end p-1 shadow-sm"
                  role="menu"
                  aria-label="Player filter options"
                  style={{ minWidth: "12rem" }}
                >
                  <button
                    type="button"
                    className={`dropdown-item rounded-2${playerFilter === "players" ? " active" : ""}`}
                    role="menuitemradio"
                    aria-checked={playerFilter === "players"}
                    onClick={() => {
                      setPlayerFilter("players");
                      setPlayerFilterMenuOpen(false);
                    }}
                  >
                    Players
                  </button>
                  <button
                    type="button"
                    className={`dropdown-item rounded-2${playerFilter === "nonPlayers" ? " active" : ""}`}
                    role="menuitemradio"
                    aria-checked={playerFilter === "nonPlayers"}
                    onClick={() => {
                      setPlayerFilter("nonPlayers");
                      setPlayerFilterMenuOpen(false);
                    }}
                  >
                    Non-players
                  </button>
                  <button
                    type="button"
                    className={`dropdown-item rounded-2${playerFilter === "all" ? " active" : ""}`}
                    role="menuitemradio"
                    aria-checked={playerFilter === "all"}
                    onClick={() => {
                      setPlayerFilter("all");
                      setPlayerFilterMenuOpen(false);
                    }}
                  >
                    All
                  </button>
                </div>
              ) : null}
            </div>
          </div>
          <div className="list-group list-group-flush rounded-3 overflow-hidden border">
            {visibleMembers.map((member) => {
              const memberId = String(member.id);
              const jerseyNumber = formatMemberJerseyNumber(member);
              const availability = (availabilityQuery.data ?? []).find(
                (entry) => String(entry.memberId) === memberId,
              ) ?? null;
              const isExpanded = memberId === expandedPlayerId;
              const expansionId = `player-clips-${memberId}`;
              const availabilityStatusCode = availability?.statusCode ?? null;

              return (
                <div className={`list-group-item p-0${isExpanded ? " bg-body-tertiary" : ""}`} key={memberId}>
                  <button
                    type="button"
                    className={`btn w-100 d-flex align-items-center gap-3 text-start border-0 rounded-0 px-3 py-3${
                      isExpanded ? " btn-outline-primary" : " btn-light"
                    }`}
                    onClick={() => {
                      setExpandedPlayerId((current) => (current === memberId ? "" : memberId));
                    }}
                    aria-expanded={isExpanded}
                    aria-controls={expansionId}
                    id={`player-${memberId}-toggle`}
                  >
                    <div className="flex-grow-1 d-grid gap-1 text-start min-w-0">
                      <div className="d-flex align-items-center gap-2 flex-wrap">
                        <i
                          className={`bi ${getAvailabilityIconClass(availabilityStatusCode)} flex-shrink-0`}
                          aria-label={getAvailabilityDotLabel(availabilityStatusCode)}
                          title={getAvailabilityDotLabel(availabilityStatusCode)}
                        />
                        <strong className="d-inline-flex align-items-center gap-2 text-truncate">
                          {formatMemberName(member)}
                          {jerseyNumber ? ` ${jerseyNumber}` : ""}
                        </strong>
                      </div>
                    </div>
                    <i
                      className="bi bi-chevron-down ms-auto fs-5 lh-1"
                      aria-hidden="true"
                      style={{ transform: isExpanded ? "rotate(180deg)" : "none", transition: "transform 0.18s ease" }}
                    />
                  </button>
                  {isExpanded ? (
                    <div
                      className="border-top bg-white"
                      id={expansionId}
                      role="region"
                      aria-labelledby={`player-${memberId}-toggle`}
                    >
                      <div className="p-3 pb-3">
                        <div className="list-group list-group-flush">
                          <LibraryClips
                            teamId={teamId}
                            playerId={memberId}
                            playingClipKey={playingClipKey}
                            onPlayClip={(clip) =>
                              playClipPreview({
                                key: clipKey("library", clip.id),
                                url: clip.normalized_url,
                                startMs: clip.start_ms,
                                endMs: clip.end_ms,
                                title: clip.label,
                                subtitle: formatMemberName(member),
                              })
                            }
                            pinnedAssignmentsByClipId={new Map(
                              assignmentList
                                .filter((assignment) => assignment.external_player_id === memberId)
                                .map((assignment) => [String(assignment.clip_id), assignment]),
                            )}
                          />
                        </div>
                      </div>
                    </div>
                  ) : null}
                </div>
              );
            })}
            {!visibleMembers.length ? <div className="muted">No members match this filter.</div> : null}
          </div>
        </div>
      </div>
    </section>
  );
}

function LibraryClips({
  teamId,
  playerId,
  playingClipKey,
  onPlayClip,
  pinnedAssignmentsByClipId,
}: {
  teamId: string;
  playerId: string;
  playingClipKey: string | null;
  onPlayClip: (clip: AudioClip) => Promise<void>;
  pinnedAssignmentsByClipId: Map<string, GameAssignment>;
}) {
  const fallbackClipsQuery = useQuery({
    queryKey: ["clips", teamId, playerId, "visible"],
    queryFn: () => api.listClips(teamId, playerId),
    enabled: Boolean(teamId && playerId),
    networkMode: "always",
    retry: 0,
  });

  if (fallbackClipsQuery.isLoading) {
    return <div className="muted">Loading WalkUp clips...</div>;
  }

  if (!fallbackClipsQuery.data?.length) {
    return <div className="muted">No WalkUp clips available for this player.</div>;
  }

  const clips = [...fallbackClipsQuery.data].sort((a, b) => {
    const aPinned = pinnedAssignmentsByClipId.has(String(a.id));
    const bPinned = pinnedAssignmentsByClipId.has(String(b.id));
    if (aPinned !== bPinned) {
      return aPinned ? -1 : 1;
    }
    if (a.sort_order !== b.sort_order) {
      return a.sort_order - b.sort_order;
    }
    return a.label.localeCompare(b.label);
  });

  return (
    <div className="stack gameday-clip-list">
      {clips.map((clip) => {
        const key = clipKey("library", clip.id);
        const isPlaying = playingClipKey === key;
        const isPinned = pinnedAssignmentsByClipId.has(String(clip.id));
        return (
          <ClipSummaryRow
            key={clip.id}
            clip={clip}
            isPlaying={isPlaying}
            onTogglePlayback={() => void onPlayClip(clip)}
            titleExtras={
              isPinned ? (
                <span className="clip-status-icon" aria-label="Pinned" title="Pinned">
                  <i className="bi bi-pin-angle-fill" aria-hidden="true" />
                </span>
              ) : null
            }
            subtitle={isPinned ? <span className="text-body-secondary small">Pinned to this game</span> : null}
          />
        );
      })}
    </div>
  );
}
