import { Plus } from "lucide-react";
import { nanoid } from "nanoid";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { DateInput } from "~/components/DateInput";
import { SendouButton } from "~/components/elements/Button";
import { SendouSwitch } from "~/components/elements/Switch";
import { FormMessage } from "~/components/FormMessage";
import { Input } from "~/components/Input";
import { Label } from "~/components/Label";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import * as Swiss from "~/features/tournament-bracket/core/engine/swiss/team-status";
import * as Progression from "~/features/tournament-bracket/core/Progression";
import { defaultBracketSettings } from "../../tournament/tournament-utils";
import styles from "./BracketProgressionSelector.module.css";

const defaultBracket = (): Progression.InputBracket => ({
	id: nanoid(),
	name: "Main Bracket",
	type: "double_elimination",
	requiresCheckIn: false,
	settings: {},
});

export function BracketProgressionSelector({
	initialBrackets,
	isInvitationalTournament,
	onChange,
	isTournamentInProgress,
}: {
	initialBrackets?: Progression.InputBracket[];
	isInvitationalTournament: boolean;
	/** Emits the validated brackets while valid, or `null` while invalid/incomplete. */
	onChange: (value: Progression.ParsedBracket[] | null) => void;
	isTournamentInProgress: boolean;
}) {
	const [brackets, setBrackets] = React.useState<Progression.InputBracket[]>(
		initialBrackets ?? [defaultBracket()],
	);

	const handleAddBracket = () => {
		setBrackets([
			...brackets,
			{
				...defaultBracket(),
				id: nanoid(),
				name: "",
				sources: [
					{
						bracketId: brackets[0].id,
						placements: "",
					},
				],
			},
		]);
	};

	const handleDeleteBracket = (idx: number) => {
		const newBrackets = brackets.filter((_, i) => i !== idx);
		const newBracketIds = new Set(newBrackets.map((b) => b.id));

		const updatedBrackets = newBrackets.map((b) => ({
			...b,
			sources:
				newBrackets.length === 1
					? undefined
					: b.sources?.map((source) => ({
							...source,
							bracketId: newBracketIds.has(source.bracketId)
								? source.bracketId
								: newBrackets[0].id,
						})),
		}));

		setBrackets(updatedBrackets);
	};

	const validated = Progression.validatedBrackets(brackets);
	// `validatedBrackets` returns a fresh array each render, so emit only when the
	// serialized result actually changes — otherwise `onChange` would loop the form store.
	const serialized = Progression.isBrackets(validated)
		? JSON.stringify(validated)
		: null;
	const lastSerialized = React.useRef<string | null | undefined>(undefined);

	React.useEffect(() => {
		if (lastSerialized.current === serialized) return;
		lastSerialized.current = serialized;
		onChange(serialized ? JSON.parse(serialized) : null);
	}, [serialized, onChange]);

	return (
		<div className="stack lg items-start">
			<div className="stack lg">
				{brackets.map((bracket, i) => (
					<TournamentFormatBracketSelector
						key={bracket.id}
						bracket={bracket}
						brackets={brackets}
						onChange={(newBracket) => {
							const newBrackets = structuredClone(brackets);
							newBrackets[i] = newBracket;

							if (newBracket.settings.advanceThreshold) {
								const destinationIdx = newBrackets.findIndex((b) =>
									b.sources?.some(
										(source) => source.bracketId === newBracket.id,
									),
								);

								if (destinationIdx !== -1) {
									newBrackets[destinationIdx].sources = newBrackets[
										destinationIdx
									].sources?.map((source) => ({
										...source,
										placements: "",
									}));
								}
							}

							setBrackets(newBrackets);
						}}
						onDelete={
							i !== 0 && !bracket.disabled
								? () => handleDeleteBracket(i)
								: undefined
						}
						count={i + 1}
						isInvitationalTournament={isInvitationalTournament}
						isTournamentInProgress={isTournamentInProgress}
					/>
				))}
			</div>
			<SendouButton
				icon={<Plus />}
				size="small"
				variant="outlined"
				onPress={handleAddBracket}
				isDisabled={brackets.length >= TOURNAMENT.MAX_BRACKETS_PER_TOURNAMENT}
				data-testid="add-bracket-button"
			>
				Add bracket
			</SendouButton>
			{Progression.isError(validated) ? (
				<ErrorMessage error={validated} />
			) : null}
		</div>
	);
}

function TournamentFormatBracketSelector({
	bracket,
	brackets,
	onChange,
	onDelete,
	count,
	isInvitationalTournament,
	isTournamentInProgress,
}: {
	bracket: Progression.InputBracket;
	brackets: Progression.InputBracket[];
	onChange: (newBracket: Progression.InputBracket) => void;
	onDelete?: () => void;
	count: number;
	isInvitationalTournament: boolean;
	isTournamentInProgress: boolean;
}) {
	const id = React.useId();

	const createId = (name: string) => {
		return `${id}-${name}`;
	};

	const isFirstBracket = count === 1;

	const updateBracket = (newProps: Partial<Progression.InputBracket>) => {
		const defaultSettings = newProps.type
			? defaultBracketSettings(newProps.type)
			: undefined;

		onChange({
			...bracket,
			...newProps,
			settings: newProps.settings ?? defaultSettings ?? bracket.settings,
		});
	};

	return (
		<div className="stack horizontal md items-center">
			<div>
				<div className={styles.count}>Bracket #{count}</div>
				{onDelete ? (
					<SendouButton
						size="small"
						variant="minimal-destructive"
						onPress={onDelete}
						className="mx-auto"
						data-testid="delete-bracket-button"
					>
						Delete
					</SendouButton>
				) : null}
			</div>
			<div className={styles.divider} />
			<div className="stack md items-start">
				<div>
					<Label htmlFor={createId("name")}>Bracket's name</Label>
					<Input
						id={createId("name")}
						value={bracket.name}
						onChange={(e) => updateBracket({ name: e.target.value })}
						maxLength={TOURNAMENT.BRACKET_NAME_MAX_LENGTH}
						readOnly={bracket.disabled}
					/>
				</div>

				{bracket.sources ? (
					<div>
						<Label htmlFor={createId("startTime")}>Start time</Label>
						<DateInput
							id={createId("startTime")}
							defaultValue={bracket.startTime ?? undefined}
							onChange={(newDate) =>
								updateBracket({ startTime: newDate ?? undefined })
							}
							readOnly={bracket.disabled}
						/>
						<FormMessage type="info">
							If missing, bracket can be started when the previous brackets have
							finished
						</FormMessage>
					</div>
				) : null}

				{bracket.sources ? (
					<div>
						<Label htmlFor={createId("checkIn")}>Check-in required</Label>
						<SendouSwitch
							id={createId("checkIn")}
							isSelected={bracket.requiresCheckIn}
							onChange={(isSelected) =>
								updateBracket({ requiresCheckIn: isSelected })
							}
							isDisabled={bracket.disabled}
						/>
						<FormMessage type="info">
							Check-in starts 1 hour before start time or right after the
							previous bracket finishes if no start time is set
						</FormMessage>
					</div>
				) : null}

				<div>
					<Label htmlFor={createId("format")}>Format</Label>
					<select
						value={bracket.type}
						onChange={(e) =>
							updateBracket({
								type: e.target.value as Progression.InputBracket["type"],
							})
						}
						className="w-max"
						name="format"
						id={createId("format")}
						disabled={bracket.disabled}
					>
						<option value="single_elimination">Single-elimination</option>
						<option value="double_elimination">Double-elimination</option>
						<option value="round_robin">Round robin</option>
						<option value="swiss">Swiss</option>
					</select>
				</div>

				{bracket.type === "single_elimination" ? (
					<div>
						<Label htmlFor={createId("thirdPlaceMatch")}>
							Third place match
						</Label>
						<SendouSwitch
							id={createId("thirdPlaceMatch")}
							isSelected={Boolean(
								bracket.settings.thirdPlaceMatch ??
									TOURNAMENT.SE_DEFAULT_HAS_THIRD_PLACE_MATCH,
							)}
							onChange={(isSelected) =>
								updateBracket({
									settings: {
										...bracket.settings,
										thirdPlaceMatch: isSelected,
									},
								})
							}
							isDisabled={bracket.disabled}
						/>
					</div>
				) : null}

				{bracket.type === "round_robin" ? (
					<div>
						<Label htmlFor="teamsPerGroup">Max participants per group</Label>
						<select
							value={
								bracket.settings.teamsPerGroup ??
								TOURNAMENT.RR_DEFAULT_TEAM_COUNT_PER_GROUP
							}
							onChange={(e) =>
								updateBracket({
									settings: {
										...bracket.settings,
										teamsPerGroup: Number(e.target.value),
									},
								})
							}
							className="w-max"
							name="teamsPerGroup"
							id="teamsPerGroup"
							disabled={bracket.disabled}
						>
							{(bracket.settings.hasAbDivisions
								? TOURNAMENT.RR_AB_DIVISIONS_TEAMS_PER_GROUP_OPTIONS
								: TOURNAMENT.RR_TEAMS_PER_GROUP_OPTIONS
							).map((n) => (
								<option key={n} value={n}>
									{n}
								</option>
							))}
						</select>
						<FormMessage type="info">
							Participants are distributed equally, so groups may have fewer
							than selected
						</FormMessage>
					</div>
				) : null}

				{bracket.type === "round_robin" && !bracket.sources ? (
					<div>
						<Label htmlFor={createId("abDivisions")}>A/B divisions</Label>
						<SendouSwitch
							id={createId("abDivisions")}
							isSelected={Boolean(bracket.settings.hasAbDivisions)}
							onChange={(isSelected) => {
								const currentTeamsPerGroup =
									bracket.settings.teamsPerGroup ??
									TOURNAMENT.RR_DEFAULT_TEAM_COUNT_PER_GROUP;

								const maxWithoutAb = Math.max(
									...TOURNAMENT.RR_TEAMS_PER_GROUP_OPTIONS,
								);

								let nextTeamsPerGroup = currentTeamsPerGroup;
								if (isSelected && currentTeamsPerGroup % 2 !== 0) {
									nextTeamsPerGroup = currentTeamsPerGroup + 1;
								} else if (!isSelected && currentTeamsPerGroup > maxWithoutAb) {
									nextTeamsPerGroup = maxWithoutAb;
								}

								updateBracket({
									settings: {
										...bracket.settings,
										hasAbDivisions: isSelected,
										teamsPerGroup: nextTeamsPerGroup,
									},
								});
							}}
							isDisabled={bracket.disabled}
						/>
						<FormMessage type="info">
							Teams split into A and B pools; every A plays every B once
						</FormMessage>
					</div>
				) : null}

				{bracket.type === "swiss" ? (
					<div>
						<Label htmlFor="swissGroupCount">Groups count</Label>
						<select
							value={
								bracket.settings.groupCount ??
								TOURNAMENT.SWISS_DEFAULT_GROUP_COUNT
							}
							onChange={(e) =>
								updateBracket({
									settings: {
										...bracket.settings,
										groupCount: Number(e.target.value),
									},
								})
							}
							className="w-max"
							name="swissGroupCount"
							id="swissGroupCount"
							disabled={bracket.disabled}
						>
							<option value="1">1</option>
							<option value="2">2</option>
							<option value="3">3</option>
							<option value="4">4</option>
							<option value="5">5</option>
							<option value="6">6</option>
						</select>
					</div>
				) : null}

				{bracket.type === "swiss" ? (
					<div>
						<Label htmlFor="swissRoundCount">Round count</Label>
						<select
							value={
								bracket.settings.roundCount ??
								TOURNAMENT.SWISS_DEFAULT_ROUND_COUNT
							}
							onChange={(e) => {
								const newRoundCount = Number(e.target.value);
								const currentAdvanceThreshold =
									bracket.settings.advanceThreshold;

								updateBracket({
									settings: {
										...bracket.settings,
										roundCount: newRoundCount,
										advanceThreshold:
											currentAdvanceThreshold &&
											!Swiss.isValidAdvanceThreshold({
												roundCount: newRoundCount,
												advanceThreshold: currentAdvanceThreshold,
											})
												? 3
												: currentAdvanceThreshold,
									},
								});
							}}
							className="w-max"
							name="swissRoundCount"
							id="swissRoundCount"
							disabled={bracket.disabled}
						>
							<option value="3">3</option>
							<option value="4">4</option>
							<option value="5">5</option>
							<option value="6">6</option>
							<option value="7">7</option>
							<option value="8">8</option>
						</select>
					</div>
				) : null}

				{bracket.type === "swiss" ? (
					<div>
						<Label htmlFor={createId("earlyAdvance")}>
							Early advance/elimination
						</Label>
						<SendouSwitch
							id={createId("earlyAdvance")}
							isSelected={Boolean(bracket.settings.advanceThreshold)}
							onChange={(isSelected) =>
								updateBracket({
									settings: {
										...bracket.settings,
										advanceThreshold: isSelected ? 3 : undefined,
									},
								})
							}
							isDisabled={bracket.disabled}
						/>
						<FormMessage type="info">
							Teams stop playing once they reach required wins or exceed maximum
							losses
						</FormMessage>
					</div>
				) : null}

				{bracket.type === "swiss" && bracket.settings.advanceThreshold ? (
					<div>
						<Label htmlFor={createId("advanceThreshold")}>
							Wins needed to advance
						</Label>
						<select
							value={bracket.settings.advanceThreshold}
							onChange={(e) => {
								const newThreshold = Number(e.target.value);
								updateBracket({
									settings: {
										...bracket.settings,
										advanceThreshold: newThreshold,
									},
								});
							}}
							className="w-max"
							name="advanceThreshold"
							id={createId("advanceThreshold")}
							disabled={bracket.disabled}
						>
							{Swiss.validAdvanceThresholdOptions({
								roundCount:
									bracket.settings.roundCount ??
									TOURNAMENT.SWISS_DEFAULT_ROUND_COUNT,
							}).map((threshold) => (
								<option key={threshold} value={threshold}>
									{threshold}
								</option>
							))}
						</select>
						<FormMessage type="info">
							Maximum losses allowed:{" "}
							{Swiss.eliminationThreshold({
								roundCount:
									bracket.settings.roundCount ??
									TOURNAMENT.SWISS_DEFAULT_ROUND_COUNT,
								advanceThreshold: bracket.settings.advanceThreshold,
							}) - 1}
						</FormMessage>
					</div>
				) : null}

				<div>
					<div className="stack horizontal sm">
						<Label htmlFor={createId("source")}>Source</Label>{" "}
					</div>
					{!isFirstBracket ? (
						<div className="stack sm horizontal mt-1 mb-2">
							<SendouSwitch
								id={createId("follow-up-bracket")}
								isSelected={Boolean(bracket.sources)}
								onChange={(isSelected) =>
									updateBracket({
										sources: isSelected ? [] : undefined,
										requiresCheckIn: false,
										startTime: undefined,
									})
								}
								isDisabled={bracket.disabled || isTournamentInProgress}
								data-testid="follow-up-bracket-switch"
							/>
							<Label htmlFor={createId("follow-up-bracket")} spaced={false}>
								Is follow-up bracket
							</Label>
						</div>
					) : null}
					{!bracket.sources ? (
						<FormMessage type="info">
							{isInvitationalTournament
								? "Participants added by the organizer"
								: "Participants join from sign-up"}
						</FormMessage>
					) : (
						<SourcesSelector
							brackets={brackets.filter(
								(bracket2) => bracket.id !== bracket2.id && bracket2.name,
							)}
							source={bracket.sources?.[0] ?? null}
							onChange={(source) => updateBracket({ sources: [source] })}
						/>
					)}
				</div>
			</div>
		</div>
	);
}

function SourcesSelector({
	brackets,
	source,
	onChange,
}: {
	brackets: Progression.InputBracket[];
	source: Progression.EditableSource | null;
	onChange: (sources: Progression.EditableSource) => void;
}) {
	const id = React.useId();

	const createId = (label: string) => {
		return `${id}-${label}`;
	};

	const inputBracket = brackets.find((b) => b.id === source?.bracketId);

	return (
		<div>
			<div className="stack horizontal sm items-end">
				<div>
					<Label htmlFor={createId("bracket")}>Bracket</Label>
					<select
						id={createId("bracket")}
						value={source?.bracketId ?? brackets[0].id}
						onChange={(e) =>
							onChange({ placements: "", ...source, bracketId: e.target.value })
						}
					>
						{brackets.map((bracket) => (
							<option key={bracket.id} value={bracket.id}>
								{bracket.name}
							</option>
						))}
					</select>
				</div>
				{!inputBracket?.settings.advanceThreshold ? (
					<div>
						<Label htmlFor={createId("placements")}>Placements</Label>
						<Input
							id={createId("placements")}
							placeholder="1,2,3"
							value={source?.placements ?? ""}
							testId="placements-input"
							onChange={(e) =>
								onChange({
									bracketId: brackets[0].id,
									...source,
									placements: e.target.value,
								})
							}
						/>
					</div>
				) : null}
			</div>
			{!inputBracket?.settings.advanceThreshold ? (
				<FormMessage type="info">
					Use N+ for Nth place and every placement after
				</FormMessage>
			) : null}
		</div>
	);
}

function ErrorMessage({ error }: { error: Progression.ValidationError }) {
	const { t } = useTranslation(["tournament"]);

	const bracketIdxsArr = (() => {
		if (typeof (error as { bracketIdx: number }).bracketIdx === "number") {
			return [(error as { bracketIdx: number }).bracketIdx];
		}
		if ((error as { bracketIdxs: number[] }).bracketIdxs) {
			return (error as { bracketIdxs: number[] }).bracketIdxs;
		}

		return null;
	})();

	return (
		<FormMessage type="error">
			Problems with the bracket progression
			{bracketIdxsArr ? (
				<> (Bracket {bracketIdxsArr.map((idx) => `#${idx + 1}`).join(", ")})</>
			) : null}
			:{" "}
			{t(`tournament:progression.error.${error.type}`, {
				max: TOURNAMENT.PLACEMENT_MAX,
			})}
		</FormMessage>
	);
}
