import clsx from "clsx";
import { useFetcher } from "react-router";
import { SendouButton } from "~/components/elements/Button";
import { FormWithConfirm } from "~/components/FormWithConfirm";
import { SubmitButton } from "~/components/SubmitButton";
import { useUser } from "~/features/auth/core/user";
import {
	useBracketExpanded,
	useTournament,
} from "~/features/tournament/routes/to.$id";
import type { Match as MatchType } from "~/features/tournament-bracket/core/engine/types";
import { useSearchParamState } from "~/hooks/useSearchParamState";
import type { Bracket as BracketType } from "../../core/Bracket";
import styles from "../../tournament-bracket.module.css";
import { groupNumberToLetters } from "../../tournament-bracket-utils";
import { Match } from "./Match";
import { PlacementsTable } from "./PlacementsTable";
import { RoundHeader } from "./RoundHeader";
import { useBracketSpoilerCensor } from "./useBracketSpoilerCensor";

export function SwissBracket({
	bracket,
	bracketIdx,
}: {
	bracket: BracketType;
	bracketIdx: number;
}) {
	const user = useUser();
	const tournament = useTournament();
	const { bracketExpanded } = useBracketExpanded();
	const { censored, matchCensorLevel } = useBracketSpoilerCensor();

	const groups = getGroups(bracket);
	const [selectedGroupId, setSelectedGroupId] = useSearchParamState({
		defaultValue: groups[0].groupId,
		name: "group",
		revive: (id) =>
			groups.find((g) => g.groupId === Number(id))
				? Number(id)
				: groups[0].groupId,
	});
	const fetcher = useFetcher();

	const selectedGroup = groups.find((g) => g.groupId === selectedGroupId)!;

	const rounds = bracket.data.round.filter(
		(r) => r.group_id === selectedGroupId,
	);

	// when bracket starts we go from "virtual id" to a real one
	// which would cause the admin to see empty group after starting
	// bracket
	if (!groups.some((g) => g.groupId === selectedGroupId)) {
		setSelectedGroupId(groups[0].groupId);
	}

	const someMatchOngoing = (matches: MatchType[]) =>
		matches.some(
			(match) =>
				match.opponent1 &&
				match.opponent2 &&
				match.opponent1.result !== "win" &&
				match.opponent2.result !== "win",
		);

	const allRoundsFinished = () => {
		for (const round of rounds) {
			const matches = bracket.data.match.filter(
				(match) =>
					match.round_id === round.id && match.group_id === selectedGroupId,
			);

			if (matches.length === 0 || someMatchOngoing(matches)) {
				return false;
			}
		}

		return true;
	};

	const roundThatCanBeStartedId = () => {
		if (!tournament.isOrganizer(user) || bracket.preview) return undefined;

		for (const round of rounds) {
			const matches = bracket.data.match.filter(
				(match) =>
					match.round_id === round.id && match.group_id === selectedGroupId,
			);

			if (someMatchOngoing(matches) && matches.length > 0) {
				return undefined;
			}

			if (matches.length === 0) {
				return round.id;
			}
		}

		return;
	};

	return (
		<div className="stack xl">
			<div className="stack lg">
				{groups.length > 1 && (
					<div className="stack horizontal">
						{groups.map((g) => (
							<SendouButton
								key={g.groupId}
								onPress={() => setSelectedGroupId(g.groupId)}
								className={clsx(
									styles.bracketNavLink,
									styles.bracketNavLinkBig,
									{
										[styles.bracketNavLinkSelected]:
											selectedGroupId === g.groupId,
									},
								)}
								data-testid={`group-${g.groupName.split(" ")[1]}-button`}
							>
								{g.groupName.split(" ")[1]}
							</SendouButton>
						))}
					</div>
				)}
				<div className="stack lg">
					{rounds.map((round, roundI) => {
						const matches = bracket.data.match.filter(
							(match) =>
								match.round_id === round.id &&
								match.group_id === selectedGroupId,
						);

						if (
							matches.length > 0 &&
							!bracketExpanded &&
							!someMatchOngoing(matches) &&
							roundI !== rounds.length - 1
						) {
							return null;
						}

						const bestOf = round.maps?.count;

						const ongoingMatches = matches.filter(
							(m) =>
								m.opponent1 &&
								m.opponent2 &&
								!m.opponent1.result &&
								!m.opponent2.result,
						);
						const startedAtValues = ongoingMatches
							.map((m) => m.startedAt)
							.filter((t): t is number => typeof t === "number");
						const roundStartedAt =
							startedAtValues.length > 0 ? Math.min(...startedAtValues) : null;

						const teamWithByeId = matches.find((m) => !m.opponent2)?.opponent1
							?.id;
						const teamWithBye = teamWithByeId
							? tournament.teamById(teamWithByeId)
							: null;

						return (
							<div
								key={round.id}
								className={matches.length > 0 ? "stack md-plus" : "stack"}
							>
								<div className="stack sm horizontal">
									<RoundHeader
										roundId={round.id}
										name={`Round ${round.number}`}
										bestOf={bestOf}
										showInfos={someMatchOngoing(matches)}
										maps={round.maps}
										roundStartedAt={roundStartedAt}
										matches={ongoingMatches}
									/>
									{roundThatCanBeStartedId() === round.id ? (
										<fetcher.Form method="post">
											<input
												type="hidden"
												name="groupId"
												value={selectedGroupId}
											/>
											<input
												type="hidden"
												name="bracketIdx"
												value={bracketIdx}
											/>
											<SubmitButton
												_action="ADVANCE_BRACKET"
												state={fetcher.state}
												testId="start-round-button"
											>
												Start round
											</SubmitButton>
										</fetcher.Form>
									) : null}
									{someMatchOngoing(matches) &&
									tournament.isOrganizer(user) &&
									roundI > 0 ? (
										<FormWithConfirm
											dialogHeading={`Delete all matches of round ${round.number}?`}
											fields={[
												["groupId", selectedGroupId],
												["roundId", round.id],
												["bracketIdx", bracketIdx],
												["_action", "UNADVANCE_BRACKET"],
											]}
										>
											<SendouButton
												variant="minimal-destructive"
												type="submit"
												className="small-text mb-4"
												size="small"
												data-testid="reset-round-button"
											>
												Reset round
											</SendouButton>
										</FormWithConfirm>
									) : null}
								</div>
								<div className="stack horizontal md lg-row flex-wrap">
									{matches.length === 0 ? (
										<div className="text-lighter text-md font-bold">
											Waiting for the previous round to finish
										</div>
									) : null}
									{matches.map((match) => {
										if (!match.opponent1 || !match.opponent2) {
											return null;
										}

										return (
											<Match
												key={match.id}
												match={match}
												roundNumber={round.number}
												isPreview={bracket.preview}
												showSimulation={false}
												bracket={bracket}
												type="groups"
												group={selectedGroup.groupName.split(" ")[1]}
												hideMatchTimer
												spoilerCensor={matchCensorLevel({
													bracketType: "swiss",
													roundNumber: round.number,
													roundIdx: roundI,
													matchType: "groups",
												})}
											/>
										);
									})}
								</div>
								{teamWithBye && !(censored && round.number > 1) ? (
									<div
										className="text-xs text-lighter font-semi-bold"
										data-testid="bye-team"
									>
										BYE: {teamWithBye.name}
									</div>
								) : null}
							</div>
						);
					})}
				</div>
				{censored ? null : (
					<PlacementsTable
						bracket={bracket}
						groupId={selectedGroupId}
						allMatchesFinished={allRoundsFinished()}
					/>
				)}
			</div>
		</div>
	);
}

function getGroups(bracket: BracketType) {
	const result: Array<{
		groupName: string;
		matches: MatchType[];
		groupId: number;
	}> = [];

	for (const group of bracket.data.group) {
		const matches = bracket.data.match.filter(
			(match) => match.group_id === group.id,
		);

		result.push({
			groupName: `Group ${groupNumberToLetters(group.number)}`,
			matches,
			groupId: group.id,
		});
	}

	return result;
}
