import { Button } from "@cap/ui-solid";
import { trackDeep } from "@solid-primitives/deep";
import { throttle } from "@solid-primitives/scheduled";
import { makePersisted } from "@solid-primitives/storage";
import { createMutation } from "@tanstack/solid-query";
import { convertFileSrc } from "@tauri-apps/api/core";
import { cx } from "cva";
import {
	createEffect,
	createMemo,
	createSignal,
	Match,
	on,
	onMount,
	Show,
	Switch,
} from "solid-js";
import { createStore } from "solid-js/store";

import Cropper, { cropToFloor } from "~/components/Cropper";
import { Toggle } from "~/components/Toggle";
import Tooltip from "~/components/Tooltip";
import { createTauriEventListener } from "~/utils/createEventListener";
import { type Crop, events } from "~/utils/tauri";
import { ConfigSidebar } from "./ConfigSidebar";
import {
	EditorContextProvider,
	EditorInstanceContextProvider,
	FPS,
	OUTPUT_SIZE,
	useEditorContext,
	useEditorInstanceContext,
} from "./context";
import { ExportDialog } from "./ExportDialog";
import { Header } from "./Header";
import { Player } from "./Player";
import { Timeline } from "./Timeline";
import { Dialog, DialogContent, EditorButton, Input, Subfield } from "./ui";

export function Editor() {
	return (
		<EditorInstanceContextProvider>
			<Show
				when={(() => {
					const ctx = useEditorInstanceContext();
					const editorInstance = ctx.editorInstance();

					if (!editorInstance || !ctx.metaQuery.data) return;

					return {
						editorInstance,
						meta() {
							const d = ctx.metaQuery.data;
							if (!d)
								throw new Error(
									"metaQuery.data is undefined - how did this happen?",
								);
							return d;
						},
						refetchMeta: async () => {
							await ctx.metaQuery.refetch();
						},
					};
				})()}
			>
				{(values) => (
					<EditorContextProvider {...values()}>
						<Inner />
					</EditorContextProvider>
				)}
			</Show>
		</EditorInstanceContextProvider>
	);
}

function Inner() {
	const { project, editorState, setEditorState } = useEditorContext();

	createTauriEventListener(events.editorStateChanged, (payload) => {
		renderFrame.clear();
		setEditorState("playbackTime", payload.playhead_position / FPS);
	});

	const renderFrame = throttle((time: number) => {
		if (!editorState.playing) {
			events.renderFrameEvent.emit({
				frame_number: Math.max(Math.floor(time * FPS), 0),
				fps: FPS,
				resolution_base: OUTPUT_SIZE,
			});
		}
	}, 1000 / FPS);

	const frameNumberToRender = createMemo(() => {
		const preview = editorState.previewTime;
		if (preview !== null) return preview;
		return editorState.playbackTime;
	});

	createEffect(
		on(frameNumberToRender, (number) => {
			if (editorState.playing) return;
			renderFrame(number);
		}),
	);

	createEffect(
		on(
			() => trackDeep(project),
			() => renderFrame(editorState.playbackTime),
		),
	);

	return (
		<>
			<Header />
			<div
				class="flex overflow-y-hidden flex-col flex-1 gap-2 pb-4 w-full leading-5 animate-in fade-in"
				data-tauri-drag-region
			>
				<div class="flex overflow-hidden flex-col flex-1">
					<div class="flex overflow-y-hidden flex-row flex-1 gap-2 px-2 pb-0.5">
						<Player />
						<ConfigSidebar />
					</div>
					<Timeline />
				</div>
				<Dialogs />
			</div>
		</>
	);
}

function Dialogs() {
	const { dialog, setDialog, presets, project } = useEditorContext();

	return (
		<Dialog.Root
			size={(() => {
				const d = dialog();
				if ("type" in d && d.type === "crop") return "lg";
				return "sm";
			})()}
			contentClass={(() => {
				const d = dialog();
				if ("type" in d && d.type === "export") return "max-w-[740px]";
				return "";
			})()}
			open={dialog().open}
			onOpenChange={(o) => {
				if (!o) setDialog((d) => ({ ...d, open: false }));
			}}
		>
			<Show
				when={(() => {
					const d = dialog();
					if ("type" in d) return d;
				})()}
			>
				{(dialog) => (
					<Switch>
						<Match when={dialog().type === "export"}>
							<ExportDialog />
						</Match>
						<Match when={dialog().type === "createPreset"}>
							{(_) => {
								const [form, setForm] = createStore({
									name: "",
									default: false,
								});

								const createPreset = createMutation(() => ({
									mutationFn: async () => {
										await presets.createPreset({ ...form, config: project });
									},
									onSuccess: () => {
										setDialog((d) => ({ ...d, open: false }));
									},
								}));

								return (
									<DialogContent
										title="Create Preset"
										confirm={
											<Dialog.ConfirmButton
												disabled={createPreset.isPending}
												onClick={() => createPreset.mutate()}
											>
												Create
											</Dialog.ConfirmButton>
										}
									>
										<Subfield name="Name" required />
										<Input
											class="mt-2"
											value={form.name}
											placeholder="Enter preset name..."
											onInput={(e) => setForm("name", e.currentTarget.value)}
										/>
										<Subfield name="Set as default" class="mt-4">
											<Toggle
												checked={form.default}
												onChange={(checked) => setForm("default", checked)}
											/>
										</Subfield>
									</DialogContent>
								);
							}}
						</Match>
						<Match
							when={(() => {
								const d = dialog();
								if (d.type === "renamePreset") return d;
							})()}
						>
							{(dialog) => {
								const [name, setName] = createSignal(
									presets.query.data?.presets[dialog().presetIndex].name!,
								);

								const renamePreset = createMutation(() => ({
									mutationFn: async () =>
										presets.renamePreset(dialog().presetIndex, name()),
									onSuccess: () => {
										setDialog((d) => ({ ...d, open: false }));
									},
								}));

								return (
									<DialogContent
										title="Rename Preset"
										confirm={
											<Dialog.ConfirmButton
												disabled={renamePreset.isPending}
												onClick={() => renamePreset.mutate()}
											>
												Rename
											</Dialog.ConfirmButton>
										}
									>
										<Subfield name="Name" required />
										<Input
											class="mt-2"
											value={name()}
											onInput={(e) => setName(e.currentTarget.value)}
										/>
									</DialogContent>
								);
							}}
						</Match>
						<Match
							when={(() => {
								const d = dialog();
								if (d.type === "deletePreset") return d;
							})()}
						>
							{(dialog) => {
								const deletePreset = createMutation(() => ({
									mutationFn: async () => {
										await presets.deletePreset(dialog().presetIndex);
										await presets.query.refetch();
									},
									onSuccess: () => {
										setDialog((d) => ({ ...d, open: false }));
									},
								}));

								return (
									<DialogContent
										title="Delete Preset"
										confirm={
											<Dialog.ConfirmButton
												variant="destructive"
												onClick={() => deletePreset.mutate()}
												disabled={deletePreset.isPending}
											>
												Delete
											</Dialog.ConfirmButton>
										}
									>
										<p class="text-gray-11">
											Are you sure you want to delete this preset?
										</p>
									</DialogContent>
								);
							}}
						</Match>
						<Match
							when={(() => {
								const d = dialog();
								if (d.type === "crop") return d;
							})()}
						>
							{(dialog) => {
								const { setProject: setState, editorInstance } =
									useEditorContext();
								const [crop, setCrop] = createStore<Crop>({
									position: dialog().position,
									size: dialog().size,
								});
								const [cropOptions, setCropOptions] = makePersisted(
									createStore({
										showGrid: false,
									}),
									{ name: "cropOptionsState" },
								);

								const display = editorInstance.recordings.segments[0].display;

								const adjustedCrop = createMemo(() => cropToFloor(crop));

								return (
									<>
										<Dialog.Header>
											<div class="flex flex-row space-x-[2rem]">
												<div class="flex flex-row items-center space-x-[0.75rem] text-gray-11">
													<span>Size</span>
													<div class="w-[3.25rem]">
														<Input
															class="bg-transparent dark:!text-[#ababab]"
															value={adjustedCrop().size.x}
															onChange={(e) =>
																setCrop((c) => ({
																	...c,
																	size: {
																		...c.size,
																		x: Number(e.currentTarget.value),
																	},
																}))
															}
														/>
													</div>
													<span>x</span>
													<div class="w-[3.25rem]">
														<Input
															class="bg-transparent dark:!text-[#ababab]"
															value={adjustedCrop().size.y}
															onChange={(e) =>
																setCrop((c) => ({
																	...c,
																	size: {
																		...c.size,
																		y: Number(e.currentTarget.value),
																	},
																}))
															}
														/>
													</div>
												</div>
												<div class="flex flex-row items-center space-x-[0.75rem] text-gray-11">
													<span>Position</span>
													<div class="w-[3.25rem]">
														<Input
															class="bg-transparent dark:!text-[#ababab]"
															value={adjustedCrop().position.x}
															onChange={(e) =>
																setCrop((c) => ({
																	...c,
																	position: {
																		...c.position,
																		x: Number(e.currentTarget.value),
																	},
																}))
															}
														/>
													</div>
													<span>x</span>
													<div class="w-[3.25rem]">
														<Input
															class="w-[3.25rem] bg-transparent dark:!text-[#ababab]"
															value={adjustedCrop().position.y}
															onChange={(e) =>
																setCrop((c) => ({
																	...c,
																	position: {
																		...c.position,
																		y: Number(e.currentTarget.value),
																	},
																}))
															}
														/>
													</div>
												</div>
											</div>
											<div class="flex flex-row gap-3 justify-end items-center w-full">
												<div class="flex flex-row items-center space-x-[0.5rem] text-gray-11">
													<Tooltip content="Rule of Thirds">
														<Button
															variant="secondary"
															size="xs"
															class={cx(
																"flex items-center justify-center text-center rounded-full h-[2rem] w-[2rem] border text-[0.875rem] focus:border-blue-9",
																cropOptions.showGrid
																	? "border-blue-9"
																	: "border-transparent",
															)}
															onClick={() =>
																setCropOptions("showGrid", (s) => !s)
															}
														>
															<IconCapPadding
																class={cx(
																	"w-4",
																	cropOptions.showGrid
																		? "text-blue-9"
																		: "text-gray-12",
																)}
															/>
														</Button>
													</Tooltip>
												</div>
												<EditorButton
													leftIcon={<IconCapCircleX />}
													onClick={() =>
														setCrop({
															position: { x: 0, y: 0 },
															size: {
																x: display.width,
																y: display.height,
															},
														})
													}
												>
													Reset
												</EditorButton>
											</div>
										</Dialog.Header>
										<Dialog.Content>
											<div class="flex flex-row justify-center">
												<div class="overflow-hidden rounded divide-black-transparent-10">
													<Cropper
														value={crop}
														onCropChange={setCrop}
														mappedSize={{
															x: display.width,
															y: display.height,
														}}
														showGuideLines={cropOptions.showGrid}
													>
														<img
															class="shadow pointer-events-none max-h-[70vh]"
															alt="screenshot"
															src={convertFileSrc(
																`${editorInstance.path}/screenshots/display.jpg`,
															)}
														/>
													</Cropper>
												</div>
											</div>
										</Dialog.Content>
										<Dialog.Footer>
											<Button
												onClick={() => {
													setState("background", "crop", adjustedCrop());
													setDialog((d) => ({ ...d, open: false }));
												}}
											>
												Save
											</Button>
										</Dialog.Footer>
									</>
								);
							}}
						</Match>
					</Switch>
				)}
			</Show>
		</Dialog.Root>
	);
}
