import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "~/components/ui/table";
import { Trophy, Star } from "lucide-react";
import { TeamOwnerBadge } from "~/components/ui/team-owner-badge";
import { GradientIcon } from "~/components/ui/GradientIcon";
import { RankingsRow } from "./RankingsRow";
import { BracketTreeView, type BracketMatch, type BracketOwnership } from "./BracketTreeView";
import { BracketTreePaginated } from "./BracketTreePaginated";
import { getBracketTemplate } from "~/lib/bracket-templates";
import { NbaBracketLayout } from "./NbaBracketLayout";
import { TabbedBracketLayout } from "./TabbedBracketLayout";

interface Participant {
  id: string;
  name: string;
}

export interface Match {
  id: string;
  round: string;
  matchNumber: number;
  participant1Id: string | null;
  participant2Id: string | null;
  winnerId: string | null;
  loserId: string | null;
  isComplete: boolean;
  participant1Score: string | null;
  participant2Score: string | null;
  isScoring?: boolean;
  participant1?: Participant | null;
  participant2?: Participant | null;
  winner?: Participant | null;
  loser?: Participant | null;
}

export interface TeamOwnership {
  participantId: string;
  teamName: string;
  teamId: string;
  ownerName?: string;
}

interface PlayoffBracketProps {
  matches: Match[];
  rounds: string[]; // Ordered list of round names (earliest first)
  bracketTemplateId?: string | null;
  preEliminatedParticipants?: { id: string; name: string }[];
  participantPoints?: { participantId: string; points: number }[];
  partialScoreParticipantIds?: string[];
  teamOwnerships?: TeamOwnership[];
  userParticipantIds?: string[];
  showOwnership?: boolean;
  title?: string;
  description?: string;
  /** "bracket" = full bracket + tables (default); "rankings" = final rankings table only */
  mode?: "bracket" | "rankings";
}

/** Group matches by round, sorted by matchNumber, in one pass. */
export function groupMatchesByRound(matches: Match[]): Map<string, Match[]> {
  const byRound = new Map<string, Match[]>();
  for (const match of matches) {
    if (!byRound.has(match.round)) byRound.set(match.round, []);
    byRound.get(match.round)?.push(match);
  }
  for (const group of byRound.values()) {
    group.sort((a, b) => a.matchNumber - b.matchNumber);
  }
  return byRound;
}

/**
 * For a standard single-elimination bracket, slot p1 of match N in round R
 * comes from match (2N-1) in the previous round, and slot p2 from match 2N.
 */
export function buildFeederMap(
  matchesByRound: Map<string, Match[]>,
  orderedRounds: string[]
): Map<string, { round: string; matchNumber: number }> {
  const feederMap = new Map<string, { round: string; matchNumber: number }>();

  for (let ri = 1; ri < orderedRounds.length; ri++) {
    const currentRound = orderedRounds[ri];
    const prevRound = orderedRounds[ri - 1];
    const prevMatchNums = new Set(
      (matchesByRound.get(prevRound) || []).map((m) => m.matchNumber)
    );
    for (const match of matchesByRound.get(currentRound) || []) {
      const p1Src = 2 * (match.matchNumber - 1) + 1;
      const p2Src = 2 * (match.matchNumber - 1) + 2;
      if (prevMatchNums.has(p1Src)) {
        feederMap.set(`${currentRound}:${match.matchNumber}:p1`, {
          round: prevRound,
          matchNumber: p1Src,
        });
      }
      if (prevMatchNums.has(p2Src)) {
        feederMap.set(`${currentRound}:${match.matchNumber}:p2`, {
          round: prevRound,
          matchNumber: p2Src,
        });
      }
    }
  }

  return feederMap;
}

interface EliminatedEntry {
  participant: Participant;
  score: string | null;
  rankLabel: string;
  ownership: TeamOwnership | null;
}

/**
 * For each complete match, determine which losers should be shown as eliminated
 * in that round. In double-chance brackets (e.g. AFL), a participant who lost
 * round X but then won a later round Y is NOT eliminated at round X — only their
 * final loss counts.
 *
 * Returns a map of round name → array of eliminated participant IDs.
 * Exported for unit testing.
 */
export function computeEliminatedByRound(
  matches: Array<{
    round: string;
    isComplete: boolean;
    winnerId: string | null;
    loser?: { id: string; name: string } | null;
    participant1Id?: string | null;
    participant2Id?: string | null;
  }>,
  rounds: string[]
): Map<string, string[]> {
  const roundIndex = new Map(rounds.map((r, i) => [r, i]));

  // Track the latest round (by index) each participant has APPEARED IN.
  // We track winnerId/loserId for completed matches AND participant1Id/participant2Id
  // for ALL matches (even incomplete ones). This correctly handles:
  //  - AFL double-chance: a QF loser who appears in a later Semi-Finals match
  //  - NBA play-in: a 7v8 loser placed into a PIR2 slot (match not yet complete)
  // If a participant appears in a later round slot, they are not yet eliminated.
  const participantLatestRound = new Map<string, number>();
  const updateLatest = (id: string, ri: number) => {
    const prev = participantLatestRound.get(id) ?? -1;
    if (ri > prev) participantLatestRound.set(id, ri);
  };
  for (const match of matches) {
    const ri = roundIndex.get(match.round) ?? -1;
    if (match.winnerId) updateLatest(match.winnerId, ri);
    if (match.loser?.id) updateLatest(match.loser.id, ri);
    if (match.participant1Id) updateLatest(match.participant1Id, ri);
    if (match.participant2Id) updateLatest(match.participant2Id, ri);
  }

  const result = new Map<string, string[]>();
  for (const match of matches) {
    if (!match.isComplete || !match.loser) continue;
    const lossRoundIndex = roundIndex.get(match.round) ?? -1;
    const loserLatestAppear = participantLatestRound.get(match.loser.id) ?? -1;
    // Double-chance survivor: participant appeared in a LATER round — not yet eliminated here.
    if (loserLatestAppear > lossRoundIndex) continue;
    if (!result.has(match.round)) result.set(match.round, []);
    result.get(match.round)?.push(match.loser.id);
  }
  return result;
}

/** Find the index of the first round that has scoring matches. */
function firstScoringRoundIdx(matchesByRound: Map<string, Match[]>, rounds: string[]): number {
  for (let i = 0; i < rounds.length; i++) {
    if (matchesByRound.get(rounds[i])?.some((m) => m.isScoring)) return i;
  }
  return 0;
}

const RANKING_ROW_CLASSES: Record<number, string> = {
  1: "bg-yellow-500/10",
  2: "bg-white/[0.14]",
  3: "bg-orange-600/[0.08]",
};

function rankingRowCls(currentRank: number | undefined): string {
  const podium = currentRank !== undefined ? RANKING_ROW_CLASSES[currentRank] : undefined;
  return `flex flex-col sm:flex-row sm:items-center gap-0 rounded-lg px-3 py-3 sm:px-5 sm:py-4 transition-colors ${podium ?? "bg-white/[0.04]"}`;
}

export function PlayoffBracket({
  matches,
  rounds,
  bracketTemplateId = null,
  preEliminatedParticipants = [],
  participantPoints = [],
  partialScoreParticipantIds: _partialScoreParticipantIds = [],
  teamOwnerships = [],
  userParticipantIds = [],
  showOwnership = true,
  title = "Playoff Bracket",
  description,
  mode = "bracket",
}: PlayoffBracketProps) {
  const userParticipantSet = new Set(userParticipantIds);
  const ownershipMap = new Map<string, TeamOwnership>();
  teamOwnerships.forEach((o) => ownershipMap.set(o.participantId, o));
  const pointsMap = new Map(participantPoints.map((p) => [p.participantId, p.points]));

  const matchesByRound = groupMatchesByRound(matches);
  const scoringRoundIdx = firstScoringRoundIdx(matchesByRound, rounds);
  const template = bracketTemplateId ? getBracketTemplate(bracketTemplateId) : undefined;

  const thirdPlaceRound = template?.rounds
    .find((r) => template.rounds.some((other) => other.loserFeedsInto === r.name))
    ?.name;

  // Build elimination rankings
  const losersByRound = new Map<string, Array<{ participant: Participant; score: string | null; ownership: TeamOwnership | null }>>();
  let bracketWinner: Participant | null = null;

  const lastRound = rounds[rounds.length - 1];
  const finalMatch = lastRound
    ? (matchesByRound.get(lastRound) || []).find((m) => m.matchNumber === 1)
    : null;
  if (finalMatch?.winner) bracketWinner = finalMatch.winner;

  const eliminatedByRound = computeEliminatedByRound(matches, rounds);

  for (const match of matches) {
    if (!match.isComplete || !match.loser) continue;
    const eliminatedInRound = eliminatedByRound.get(match.round);
    if (!eliminatedInRound?.includes(match.loser.id)) continue;
    const loserScore =
      match.loserId === match.participant1Id
        ? match.participant1Score
        : match.participant2Score;
    if (!losersByRound.has(match.round)) losersByRound.set(match.round, []);
    losersByRound.get(match.round)?.push({
      participant: match.loser,
      score: loserScore,
      ownership: ownershipMap.get(match.loser.id) || null,
    });
  }

  const allBracketParticipantIds = new Set<string>();
  for (const match of matches) {
    if (match.participant1Id) allBracketParticipantIds.add(match.participant1Id);
    if (match.participant2Id) allBracketParticipantIds.add(match.participant2Id);
  }
  const rankedEntries: EliminatedEntry[] = [];
  let nextRank = 2;
  for (let ri = rounds.length - 1; ri >= 0; ri--) {
    const roundName = rounds[ri];
    const roundLosers = losersByRound.get(roundName) || [];
    const totalMatchesInRound = matchesByRound.get(roundName)?.length ?? 0;
    if (roundLosers.length > 0) {
      const rankLabel = `T${nextRank}`;
      for (const loser of roundLosers) {
        rankedEntries.push({ ...loser, rankLabel });
      }
    }
    nextRank += totalMatchesInRound;
  }

  const rankedParticipantIds = new Set(rankedEntries.map((e) => e.participant.id));
  if (bracketWinner) rankedParticipantIds.add(bracketWinner.id);
  const filteredPreEliminated = preEliminatedParticipants.filter(
    (p) => !rankedParticipantIds.has(p.id)
  );

  const showRankings = rankedEntries.length > 0 || bracketWinner !== null || filteredPreEliminated.length > 0;

  const participantMap = new Map<string, Participant>();
  for (const match of matches) {
    if (match.participant1) participantMap.set(match.participant1.id, match.participant1);
    if (match.participant2) participantMap.set(match.participant2.id, match.participant2);
  }
  const activeParticipants = [...allBracketParticipantIds]
    .filter((id) => !rankedParticipantIds.has(id))
    .map((id) => participantMap.get(id))
    .filter((p): p is Participant => p !== undefined);

  const isDraftedOrScoring = (participantId: string) =>
    ownershipMap.has(participantId) || (pointsMap.get(participantId) ?? 0) > 0;

  const winnerIsOwned = bracketWinner ? userParticipantSet.has(bracketWinner.id) : false;
  const winnerOwnership = bracketWinner ? ownershipMap.get(bracketWinner.id) : undefined;
  const winnerPts = bracketWinner ? pointsMap.get(bracketWinner.id) : undefined;

  return (
    <div className="space-y-6">
      {/* Header */}
      <div>
        <h2 className="text-xl font-semibold">{title}</h2>
        {description && (
          <p className="text-sm text-muted-foreground mt-1">{description}</p>
        )}
      </div>

      {matches.length === 0 ? (
        <div className="text-center py-8 text-muted-foreground">
          <p className="text-sm">No bracket matches available yet.</p>
          <p className="text-xs mt-1">
            Matches will appear here once the bracket is set up.
          </p>
        </div>
      ) : (
        <div className="space-y-8">
          {mode === "bracket" && (template?.phases ? (
            <TabbedBracketLayout
              rounds={rounds}
              matchesByRound={matchesByRound as Map<string, BracketMatch[]>}
              ownershipMap={ownershipMap as Map<string, BracketOwnership>}
              userParticipantIds={userParticipantSet}
              phases={template.phases}
              scoringRoundIdx={scoringRoundIdx}
            />
          ) : template?.conferenceGroups ? (
            <NbaBracketLayout
              matches={matches}
              rounds={rounds}
              matchesByRound={matchesByRound as Map<string, BracketMatch[]>}
              ownershipMap={ownershipMap as Map<string, BracketOwnership>}
              userParticipantIds={userParticipantSet}
              conferenceGroups={template.conferenceGroups}
              scoringRoundIdx={scoringRoundIdx}
            />
          ) : (
            <>
              {/* ── Desktop ── */}
              <div className="hidden md:block">
                <BracketTreeView
                  rounds={rounds}
                  matchesByRound={matchesByRound as Map<string, BracketMatch[]>}
                  ownershipMap={ownershipMap as Map<string, BracketOwnership>}
                  userParticipantIds={userParticipantSet}
                  thirdPlaceRound={thirdPlaceRound}
                />
              </div>

              {/* ── Mobile ── */}
              <div className="md:hidden">
                <BracketTreePaginated
                  rounds={rounds}
                  matchesByRound={matchesByRound as Map<string, BracketMatch[]>}
                  ownershipMap={ownershipMap as Map<string, BracketOwnership>}
                  userParticipantIds={userParticipantSet}
                  firstScoringRoundIdx={scoringRoundIdx}
                  thirdPlaceRound={thirdPlaceRound}
                />
              </div>
            </>
          ))}

          {/* ── In Contention ── */}
          {mode === "bracket" && activeParticipants.length > 0 && (
            <Card className="border-green-500/30">
              <CardHeader>
                <CardTitle className="text-green-600 dark:text-green-400 flex items-center gap-2">
                  <Star className="h-5 w-5" />
                  In Contention
                </CardTitle>
              </CardHeader>
              <CardContent>
                <Table>
                  <TableHeader>
                    <TableRow>
                      <TableHead>Participant</TableHead>
                      {showOwnership && (
                        <TableHead className="w-40 pl-6">Drafted By</TableHead>
                      )}
                      {pointsMap.size > 0 && (
                        <TableHead className="text-right w-16">Pts</TableHead>
                      )}
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {activeParticipants.map((p) => {
                      const isOwned = userParticipantSet.has(p.id);
                      const ownership = showOwnership ? ownershipMap.get(p.id) || null : null;
                      const pts = pointsMap.get(p.id);
                      return (
                        <TableRow
                          key={p.id}
                          className={isOwned ? "bg-electric/5 border-l-2 border-l-electric" : ""}
                        >
                          <TableCell className={isOwned ? "text-electric font-medium" : ""}>
                            {p.name}
                            {isOwned && (
                              <Star className="inline ml-1.5 h-3 w-3 fill-current text-electric" />
                            )}
                          </TableCell>
                          {showOwnership && (
                            <TableCell className="pl-6">
                              {ownership ? (
                                <TeamOwnerBadge teamName={ownership.teamName} ownerName={ownership.ownerName} />
                              ) : (
                                <span className="text-xs text-muted-foreground">-</span>
                              )}
                            </TableCell>
                          )}
                          {pointsMap.size > 0 && (
                            <TableCell className="text-right font-mono text-sm">
                              {pts !== undefined ? pts : "–"}
                            </TableCell>
                          )}
                        </TableRow>
                      );
                    })}
                  </TableBody>
                </Table>
              </CardContent>
            </Card>
          )}

          {/* ── Final Rankings ── */}
          {mode === "rankings" && showRankings && (
            <Card className="gap-2">
              <CardHeader className="px-3 sm:px-6 pb-2">
                <div className="flex items-center gap-2">
                  <GradientIcon icon={Trophy} className="h-5 w-5 shrink-0" />
                  <h2 className="text-xl font-bold leading-none tracking-tight">
                    {bracketWinner ? "Final Rankings" : "Eliminated Teams"}
                  </h2>
                </div>
              </CardHeader>
              <CardContent className="px-3 sm:px-6">
                <div className="space-y-3">
                  {bracketWinner && (
                    <RankingsRow
                      teamId={winnerOwnership?.teamId ?? bracketWinner.id}
                      teamName={winnerOwnership?.teamName ?? bracketWinner.name}
                      participantName={bracketWinner.name}
                      ownerName={winnerOwnership?.ownerName}
                      isOwned={winnerIsOwned}
                      rankLabel={1}
                      points={winnerPts}
                      showPoints={pointsMap.size > 0}
                      rowClass={rankingRowCls(1)}
                    />
                  )}

                  {rankedEntries.filter((entry) => isDraftedOrScoring(entry.participant.id)).map((entry) => {
                    const isOwned = userParticipantSet.has(entry.participant.id);
                    const pts = pointsMap.get(entry.participant.id);
                    const numRank = parseInt(entry.rankLabel.replace("T", ""), 10);
                    return (
                      <RankingsRow
                        key={entry.participant.id}
                        teamId={entry.ownership?.teamId ?? entry.participant.id}
                        teamName={entry.ownership?.teamName ?? entry.participant.name}
                        participantName={entry.participant.name}
                        ownerName={entry.ownership?.ownerName}
                        isOwned={isOwned}
                        rankLabel={entry.rankLabel}
                        points={pts}
                        showPoints={pointsMap.size > 0}
                        rowClass={rankingRowCls(numRank)}
                      />
                    );
                  })}

                  {filteredPreEliminated.filter((p) => isDraftedOrScoring(p.id)).map((p) => {
                    const isOwned = userParticipantSet.has(p.id);
                    const ownership = ownershipMap.get(p.id);
                    const pts = pointsMap.get(p.id);
                    return (
                      <RankingsRow
                        key={p.id}
                        teamId={ownership?.teamId ?? p.id}
                        teamName={ownership?.teamName ?? p.name}
                        participantName={p.name}
                        ownerName={ownership?.ownerName}
                        isOwned={isOwned}
                        points={pts}
                        showPoints={pointsMap.size > 0}
                        rowClass={rankingRowCls(undefined)}
                      />
                    );
                  })}
                </div>
              </CardContent>
            </Card>
          )}
        </div>
      )}
    </div>
  );
}

