import clsx from "clsx";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Form, useLoaderData } from "react-router";
import { SendouButton } from "~/components/elements/Button";
import { UserSearch } from "~/components/elements/UserSearch";
import { FormErrors } from "~/components/FormErrors";
import { FormMessage } from "~/components/FormMessage";
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import type { SerializeFrom } from "~/utils/remix";
import type { SendouRouteHandle } from "~/utils/remix.server";
import type { Unpacked } from "~/utils/types";
import { action } from "../actions/calendar.$id.report-winners.server";
import { CALENDAR_EVENT_RESULT } from "../calendar-constants";
import { loader } from "../loaders/calendar.$id.report-winners.server";

export { action, loader };

export const handle: SendouRouteHandle = {
	i18n: "calendar",
};

export default function ReportWinnersPage() {
	const { t } = useTranslation(["common", "calendar"]);
	const data = useLoaderData<typeof loader>();

	return (
		<Main halfWidth>
			<Form method="post" className="stack md-plus items-start">
				<h1 className="text-lg">
					{t("calendar:forms.reportResultsHeader", { eventName: data.name })}
				</h1>
				<ParticipantsCountInput />
				<FormMessage type="info">
					{t("calendar:forms.reportResultsInfo")}
				</FormMessage>
				<TeamInputs />
				<SendouButton type="submit" className="mt-4">
					{t("common:actions.submit")}
				</SendouButton>
				<FormErrors namespace="calendar" />
			</Form>
		</Main>
	);
}

function ParticipantsCountInput() {
	const { t } = useTranslation("calendar");
	const data = useLoaderData<typeof loader>();

	return (
		<div>
			<Label htmlFor="name" required>
				{t("forms.participantCount")}
			</Label>
			<input
				name="participantCount"
				type="number"
				required
				min={1}
				max={CALENDAR_EVENT_RESULT.MAX_PARTICIPANTS_COUNT}
				defaultValue={data.participantCount ?? undefined}
				className="w-24"
			/>
		</div>
	);
}

function TeamInputs() {
	const { t } = useTranslation("calendar");
	const data = useLoaderData<typeof loader>();
	const [amountOfTeams, setAmountOfTeams] = React.useState(
		Math.max(data.winners.length, 1),
	);

	const handleTeamDelete = () => {
		setAmountOfTeams(amountOfTeams - 1);
	};

	return (
		<>
			<hr className="w-full" />
			{new Array(amountOfTeams + 1).fill(null).map((_, i) => {
				// last team is hidden so we can save its state even if user removes a filled team
				const hidden = i === amountOfTeams;

				return (
					<React.Fragment key={i}>
						<Team
							onRemoveTeam={
								i === amountOfTeams - 1 && amountOfTeams > 1
									? handleTeamDelete
									: undefined
							}
							hidden={hidden}
							initialPlacement={String(i + 1)}
							initialValues={data.winners[i]}
						/>
						{!hidden && <hr className="w-full" />}
					</React.Fragment>
				);
			})}
			<SendouButton
				onPress={() => setAmountOfTeams((amountOfTeams) => amountOfTeams + 1)}
				size="small"
			>
				{t("forms.team.add")}
			</SendouButton>
		</>
	);
}

const NEW_PLAYER = { id: 0 } as const;

interface TeamResults {
	teamName: string;
	placement: string;
	players: Array<
		| {
				id: number;
		  }
		| string
	>;
}

function Team({
	onRemoveTeam,
	hidden,
	initialPlacement,
	initialValues,
}: {
	onRemoveTeam?: () => void;
	hidden: boolean;
	initialPlacement: string;
	initialValues?: Unpacked<SerializeFrom<typeof loader>["winners"]>;
}) {
	const { t } = useTranslation("calendar");
	const teamNameId = React.useId();
	const placementId = React.useId();

	const [results, setResults] = React.useState<TeamResults>({
		teamName: initialValues?.teamName ?? "",
		placement: String(initialValues?.placement ?? initialPlacement),
		players: initialValues?.players
			? (initialValues.players.map((player) =>
					player.name ? player.name : player,
				) as TeamResults["players"])
			: [NEW_PLAYER, NEW_PLAYER, NEW_PLAYER, NEW_PLAYER],
	});

	const handleTeamNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
		setResults({ ...results, teamName: e.target.value });
	};

	const handlePlacementChange = (e: React.ChangeEvent<HTMLInputElement>) => {
		setResults({ ...results, placement: e.target.value });
	};

	const handleSetPlayers = React.useCallback(
		(action: React.SetStateAction<TeamResults["players"]>) => {
			setResults((prev) => ({
				...prev,
				players: typeof action === "function" ? action(prev.players) : action,
			}));
		},
		[],
	);

	if (hidden) return null;

	return (
		<div className={clsx("stack md items-start")}>
			<input
				type="hidden"
				name="team"
				value={JSON.stringify({
					...results,
					players: results.players.filter(
						(player) =>
							(typeof player === "string" && player !== "") ||
							(typeof player === "object" && player.id !== 0),
					),
				})}
			/>
			<div className="stack horizontal md flex-wrap">
				<div>
					<Label htmlFor={teamNameId}>{t("forms.team.name")}</Label>
					<input
						id={teamNameId}
						value={results.teamName}
						onChange={handleTeamNameChange}
						required
						maxLength={CALENDAR_EVENT_RESULT.MAX_TEAM_NAME_LENGTH}
					/>
				</div>
				<div>
					<Label htmlFor={placementId}>{t("forms.team.placing")}</Label>
					<input
						id={placementId}
						value={results.placement}
						type="number"
						onChange={handlePlacementChange}
						required
						max={CALENDAR_EVENT_RESULT.MAX_TEAM_PLACEMENT}
						className="w-24"
					/>
				</div>
			</div>
			<Players players={results.players} setPlayers={handleSetPlayers} />
			{onRemoveTeam && (
				<SendouButton
					onPress={onRemoveTeam}
					size="small"
					variant="minimal-destructive"
					className="mt-4"
				>
					{t("forms.team.remove")}
				</SendouButton>
			)}
		</div>
	);
}

function Players({
	players,
	setPlayers,
}: {
	players: TeamResults["players"];
	setPlayers: React.Dispatch<React.SetStateAction<TeamResults["players"]>>;
}) {
	const { t } = useTranslation("calendar");
	const handleAddPlayer = () => {
		setPlayers([...players, NEW_PLAYER]);
	};

	const handleRemovePlayer = () => {
		setPlayers(players.slice(0, -1));
	};

	const handlePlayerInputTypeChange = (index: number) => {
		const newPlayers = [...players];
		newPlayers[index] = typeof newPlayers[index] === "string" ? NEW_PLAYER : "";
		setPlayers(newPlayers);
	};

	const handleInputChange = React.useCallback(
		(index: number, newValue: string | number) => {
			setPlayers((prev) => {
				const newPlayers = [...prev];
				newPlayers[index] =
					typeof newValue === "string" ? newValue : { id: newValue };
				return newPlayers;
			});
		},
		[setPlayers],
	);

	return (
		<div className="stack md">
			{players.map((player, i) => {
				const formId = `player-${i + 1}`;
				const asPlainInput = typeof player === "string";

				return (
					<div key={i}>
						<div className="stack horizontal md items-center mb-1">
							<label htmlFor={formId} className="mb-0">
								{t("forms.team.player.header", { number: i + 1 })}
							</label>
							<SendouButton
								size="small"
								variant="minimal"
								onPress={() => handlePlayerInputTypeChange(i)}
							>
								{asPlainInput
									? t("forms.team.player.addAsUser")
									: t("forms.team.player.addAsText")}
							</SendouButton>
						</div>
						<PlayerInput
							formId={formId}
							player={player}
							index={i}
							asPlainInput={asPlainInput}
							onInputChange={handleInputChange}
						/>
					</div>
				);
			})}
			<div className="stack horizontal sm mt-2">
				<SendouButton
					size="small"
					onPress={handleAddPlayer}
					isDisabled={
						players.length === CALENDAR_EVENT_RESULT.MAX_PLAYERS_LENGTH
					}
					variant="outlined"
				>
					{t("forms.team.player.add")}
				</SendouButton>{" "}
				<SendouButton
					size="small"
					variant="destructive"
					onPress={handleRemovePlayer}
					isDisabled={players.length === 1}
				>
					{t("forms.team.player.remove")}
				</SendouButton>
			</div>
		</div>
	);
}

function PlayerInput({
	formId,
	player,
	index,
	asPlainInput,
	onInputChange,
}: {
	formId: string;
	player: TeamResults["players"][number];
	index: number;
	asPlainInput: boolean;
	onInputChange: (index: number, newValue: string | number) => void;
}) {
	const handlePlainChange = React.useCallback(
		(e: React.ChangeEvent<HTMLInputElement>) => {
			onInputChange(index, e.target.value);
		},
		[index, onInputChange],
	);

	const handleUserChange = React.useCallback(
		(newUser: { id: number } | null) => {
			if (!newUser) return;
			onInputChange(index, newUser.id);
		},
		[index, onInputChange],
	);

	if (asPlainInput) {
		return (
			<input
				id={formId}
				value={player as string}
				onChange={handlePlainChange}
				max={CALENDAR_EVENT_RESULT.MAX_PLAYER_NAME_LENGTH}
			/>
		);
	}

	return (
		<UserSearch
			id={formId}
			name="team-player"
			initialUserId={(player as { id: number }).id}
			onChange={handleUserChange}
		/>
	);
}
