import { useParams } from "@solidjs/router";
import {
	createMemo,
	createResource,
	createSignal,
	For,
	Match,
	onMount,
	Show,
	Suspense,
	Switch,
} from "solid-js";
import type { Commit, DID, DiffTextFragment } from "../../../util/types";
import { useDid } from "../context";
import { Header } from "../main";
import { getRepoCommit } from "../main.data";
import { buildTree, type TreeNode } from "./commit.data";

function RenderTree(props: { tree: TreeNode; skip?: boolean }) {
	if (props.skip)
		return (
			<For each={props.tree.children}>
				{(node) => <RenderTree tree={node} />}
			</For>
		);
	const [displayChildren, setDisplayChildren] = createSignal(true);
	return (
		<Switch>
			<Match when={props.tree.type === "file"}>
				<a
					class="flex cursor-default select-none flex-row items-center gap-1 rounded p-1 text-xs hover:bg-gray-100 hover:dark:bg-gray-700"
					href={`#file-${props.tree.fullPath}`}
				>
					<div class="iconify gravity-ui--file" />
					<span class="select-text">{props.tree.name}</span>
				</a>
			</Match>
			<Match when={props.tree.type === "directory"}>
				<div
					class="flex select-none flex-row items-center gap-1 rounded p-1 text-xs hover:bg-gray-100 hover:dark:bg-gray-700"
					onclick={() => setDisplayChildren(!displayChildren())}
				>
					<div class="iconify gravity-ui--folder-fill" />
					<span class="select-text">{props.tree.name}</span>
				</div>
				<div
					class={`ml-1 flex flex-col border-gray-200 border-l pl-1 dark:border-gray-700 ${displayChildren() ? "" : "hidden"}`}
				>
					<For each={props.tree.children}>
						{(node) => <RenderTree tree={node} />}
					</For>
				</div>
			</Match>
		</Switch>
	);
}

function Fragment(props: {
	file: string;
	data: DiffTextFragment;
	index: number;
	numberSize: number;
}) {
	let lineNumber = props.data.NewPosition;
	let iOld = props.data.OldPosition;
	let iNew = props.data.NewPosition;

	return (
		<Show when={!props.data.is_binary} fallback={<div>binary data</div>}>
			<Show when={props.index !== 0}>
				<div class="h-5 w-full select-none bg-gray-100 text-center font-mono text-gray-700 dark:bg-gray-700 dark:text-gray-300">
					···
				</div>
			</Show>
			<div class="w-full whitespace-pre font-mono">
				<For each={props.data.Lines}>
					{(line) => {
						const lineNumberOld = line.Op === 2 ? "" : (iOld++).toString();
						const lineNumberNew = line.Op === 1 ? "" : (iNew++).toString();
						const fillerOld = " ".repeat(
							props.numberSize - lineNumberOld.length,
						);
						const fillerNew = " ".repeat(
							props.numberSize - lineNumberNew.length,
						);
						return (
							<Line
								file={props.file}
								index={props.index}
								line={line}
								lineNumber={lineNumber++}
								lineNumberNew={lineNumberNew}
								lineNumberOld={lineNumberOld}
								fillerOld={fillerOld}
								fillerNew={fillerNew}
							/>
						);
					}}
				</For>
			</div>
		</Show>
	);
}

function Line(props: {
	file: string;
	index: number;
	line: { Op: number; Line: string };
	lineNumber: number;
	lineNumberOld: string;
	lineNumberNew: string;
	fillerOld: string;
	fillerNew: string;
}) {
	const id = `line-${props.file}-${props.index}-${props.lineNumber.toString()}`;
	return (
		<div
			class="flex scroll-mt-10 flex-row text-gray-400 *:flex *:flex-row dark:text-gray-500"
			id={id}
		>
			<div class="sticky left-0 select-none border-gray-200 border-r bg-white px-1 *:flex dark:border-gray-700 dark:bg-gray-800">
				<span class="float-right mr-1 w-1/2 justify-end">
					<span>{props.fillerOld}</span>
					<Show when={props.lineNumberOld}>
						<a
							class="hover:text-gray-700 hover:underline hover:dark:text-gray-200"
							href={`#${id}`}
						>
							{props.lineNumberOld}
						</a>
					</Show>
				</span>
				<span class="float-right mr-1 w-1/2 justify-end">
					{props.fillerNew}
					<Show when={props.lineNumberNew}>
						<a
							class="hover:text-gray-700 hover:underline hover:dark:text-gray-200"
							href={`#${id}`}
						>
							{props.lineNumberNew}
						</a>
					</Show>
				</span>
			</div>
			<Switch>
				<Match when={props.line.Op === 0}>
					<div class="w-full text-gray-500 dark:text-gray-500">
						<div class="select-none">{"   "}</div>
						{props.line.Line}
					</div>
				</Match>
				<Match when={props.line.Op === 2}>
					<div class="w-full bg-green-100 text-green-700 dark:bg-green-800/30 dark:text-green-400">
						<div class="select-none">{" + "}</div>
						{props.line.Line}
					</div>
				</Match>
				<Match when={props.line.Op === 1}>
					<div class="w-full bg-red-100 text-red-700 dark:bg-red-800/30 dark:text-red-400">
						<div class="select-none">{" - "}</div>
						{props.line.Line}
					</div>
				</Match>
			</Switch>
		</div>
	);
}

function DiffView(props: { commit: Commit }) {
	return (
		<For each={props.commit.diff.diff}>
			{(diff) => {
				const [show, setShow] = createSignal(true);
				const [addedLines, removedLines] = diff.text_fragments.reduce(
					(acc, v) => [acc[0] + v.NewLines, acc[1] + v.LinesDeleted],
					[0, 0],
				);

				const lastFrag = diff.text_fragments[diff.text_fragments.length - 1];
				const numberSize = Math.max(
					2,
					(
						Math.max(lastFrag.NewPosition, lastFrag.OldPosition) +
						lastFrag.Lines.length
					).toString().length,
				);

				return (
					<div
						id={`file-${diff.name.new}`}
						class="not-last:mb-1 flex w-full flex-col rounded border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800"
					>
						<div
							class={`sticky top-0 z-10 flex cursor-default select-none flex-row items-center gap-2 bg-white p-2 hover:bg-gray-100 dark:bg-gray-800 hover:dark:bg-gray-700 ${show() ? "rounded-t border-gray-200 border-b dark:border-gray-700" : "rounded"}`}
							onclick={() => setShow(!show())}
						>
							<div
								class={`iconify ${show() ? "gravity-ui--chevron-down" : "gravity-ui--chevron-right"}`}
							/>
							<div class="flex h-6 select-text flex-row items-center overflow-hidden rounded font-mono text-xs *:h-full *:content-center *:px-1">
								<Show when={addedLines > 0}>
									<div class="bg-green-100 text-green-700 dark:bg-green-800/30 dark:text-green-400">{`+${addedLines}`}</div>
								</Show>
								<Show when={removedLines > 0}>
									<div class="bg-red-100 text-red-700 dark:bg-red-700/30 dark:text-red-400">{`-${removedLines}`}</div>
								</Show>
							</div>
							<Show
								when={diff.name.old !== "" && diff.name.new !== diff.name.old}
							>
								<div class="select-text">{diff.name.old}</div>
								<div class="iconify gravity-ui--arrow-right" />
							</Show>
							<div class="select-text">{diff.name.new}</div>
						</div>
						<div
							class={`select-text overflow-x-auto rounded-b bg-white dark:bg-gray-800 ${show() ? "" : "hidden"}`}
						>
							<div class="min-w-max">
								<For each={diff.text_fragments}>
									{(frag, i) => (
										<Fragment
											file={diff.name.new}
											data={frag}
											index={i()}
											numberSize={numberSize}
										/>
									)}
								</For>
							</div>
						</div>
					</div>
				);
			}}
		</For>
	);
}

function CommitHeader(props: {
	user: string;
	repo: string;
	message: { title: string; content: string };
	commit: Commit;
}) {
	return (
		<div>
			<Header user={props.user} repo={props.repo} />
			<div class="mx-1 flex flex-col gap-2 rounded bg-white p-4 dark:bg-gray-800">
				<div>{props.message.title}</div>
				<Show when={props.message.content}>
					<div class="text-xs">{props.message.content}</div>
				</Show>
				<div class="text-gray-500 text-xs dark:text-gray-300">
					<span>{`${new Date(props.commit.diff.commit.author.When).toLocaleDateString(undefined, { dateStyle: "long" })} at ${new Date(props.commit.diff.commit.author.When).toLocaleTimeString()}`}</span>
					<span class="select-none px-1 before:content-['\00B7']" />
					<span>{`${props.commit.diff.commit.author.Name} <${props.commit.diff.commit.author.Email}>`}</span>
					<span class="select-none px-1 before:content-['\00B7']" />
					<a
						class="hover:text-gray-600 hover:underline hover:dark:text-gray-200"
						href={`/${props.user}/${props.repo}/commit/${props.commit.ref}`}
					>
						{props.commit.ref.slice(0, 8)}
					</a>
					<Show when={props.commit.diff.commit.parent}>
						<div class="iconify gravity-ui--arrow-left mx-1 text-[0.6rem]" />
						<a
							class="hover:text-gray-600 hover:underline hover:dark:text-gray-200"
							href={`/${props.user}/${props.repo}/commit/${props.commit.diff.commit.parent}`}
						>
							{props.commit.diff.commit.parent.slice(0, 8)}
						</a>
					</Show>
				</div>
			</div>
		</div>
	);
}

export default function RepoCommit() {
	const params = useParams();
	const did = useDid();

	const [commit] = createResource(
		() => {
			const d = did();
			if (!d) return;
			return [d, params.repo, params.ref];
		},
		async ([did, repo, ref]) => {
			const res = await getRepoCommit(did as DID, repo, ref);
			if (!res.ok) return;
			return res.data as Commit;
		},
	);

	const [sidebar] = createResource(commit, async (commit) => {
		if (!commit.diff.diff)
			return { name: "", fullPath: "", type: "directory" } as TreeNode;
		return buildTree(commit.diff.diff.map((v) => v.name.new));
	});

	const allData = createMemo(() => {
		const s = sidebar();
		const c = commit();
		if (!(s && c)) return;

		return [s, c] as const;
	});

	const headerData = createMemo(() => {
		const c = commit();
		if (!c) return;

		const titleEnd = c.diff.commit.message.indexOf("\n");
		const message = {
			title: c.diff.commit.message.slice(0, titleEnd),
			content: c.diff.commit.message.slice(titleEnd + 1),
		};

		return [c, message] as const;
	});

	onMount(() => {
		if (window.location.hash) {
			const element = document.getElementById(window.location.hash.slice(1));
			if (element)
				element.scrollIntoView({ behavior: "instant", block: "start" });
		}
	});

	return (
		<div class="mx-auto max-w-10xl">
			<Suspense>
				<Show when={headerData()} keyed>
					{([commit, message]) => (
						<CommitHeader
							user={params.user}
							repo={params.repo}
							message={message}
							commit={commit}
						/>
					)}
				</Show>
				<Show when={allData()} keyed>
					{([sidebar, commit]) => (
						<>
							<div class="flex flex-row gap-1">
								<div class="sticky top-0 flex max-h-screen min-w-50 overflow-auto p-1 pr-0">
									<Show when={sidebar.children}>
										<div class="flex min-h-max w-full grow cursor-default flex-col rounded border border-gray-200 bg-white p-1 dark:border-gray-700 dark:bg-gray-800">
											<div class="flex flex-row items-center justify-between gap-1 p-1">
												<div class="font-bold">CHANGED FILES</div>
												<div class="flex h-6 select-text flex-row items-center overflow-hidden rounded font-mono text-xs *:h-full *:content-center *:px-1">
													<Show when={commit.diff.stat.insertions > 0}>
														<div class="bg-green-100 text-green-700 dark:bg-green-800/30 dark:text-green-400">{`+${commit.diff.stat.insertions}`}</div>
													</Show>
													<Show when={commit.diff.stat.deletions > 0}>
														<div class="bg-red-100 text-red-700 dark:bg-red-700/30 dark:text-red-400">{`-${commit.diff.stat.deletions}`}</div>
													</Show>
												</div>
											</div>
											<RenderTree tree={sidebar} skip={true} />
										</div>
									</Show>
								</div>
								<div class="min-w-0 flex-1 flex-col gap-1 p-1 pl-0">
									<DiffView commit={commit} />
								</div>
							</div>
						</>
					)}
				</Show>
			</Suspense>
		</div>
	);
}
