import type {
	ShTangledRepoLanguages,
	ShTangledRepoTree,
} from "@atcute/tangled";
import { type Params, useParams } from "@solidjs/router";
import {
	createMemo,
	createResource,
	createSignal,
	For,
	Match,
	Show,
	Switch,
} from "solid-js";
import { SolidMarkdown } from "solid-markdown";
import { IconWithText } from "../../elements/icon_with_text";
import { languageColors } from "../../util/get_language";
import type { RepoLog } from "../../util/types";
import { useDid } from "./context";
import { Header } from "./main";
import {
	getRepoBranches,
	getRepoDefaultBranch,
	getRepoLanguages,
	getRepoLog,
	getRepoTree,
} from "./main.data";
import "../../styles/markdown.css";
import { figureOutDid } from "../../util/handle";

export async function preloadRepoTree({ params }: { params: Params }) {
	const did = await figureOutDid(params.user);
	if (!did) return;
	getRepoTree(did, params.repo, params.ref, params.path);
}

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

	const [defaultBranch] = createResource(did, async (did) => {
		const res = await getRepoDefaultBranch(did, params.repo);
		if (!res.ok) return;
		return res.data.name;
	});

	const [tree] = createResource(
		() => {
			const d = did();
			return d && ([d, params.repo, params.ref, params.path] as const);
		},
		async ([d, repo, ref, path]) => {
			const res = await getRepoTree(d, repo, ref, path);
			if (!res.ok) return;
			return res.data;
		},
	);

	const [languages] = createResource(did, async (did) => {
		const res = await getRepoLanguages(did, params.repo, params.ref);
		if (!res.ok) return;
		return res.data.languages.sort((a, b) => b.percentage - a.percentage);
	});

	const [logs] = createResource(
		() => {
			const d = did();
			return d && ([d, params.repo, params.ref, params.path] as const);
		},
		async ([d, repo, ref, path]) => {
			const res = await getRepoLog(d, repo, ref, path);
			if (!res.ok) return;
			return res.data as RepoLog;
		},
	);

	const [branchesAndTags] = createResource(
		() => {
			const d = did();
			return d && ([d, params.repo, params.ref] as const);
		},
		async ([d, repo, ref]) => {
			const resBranches = await getRepoBranches(d, repo, ref);
			if (!resBranches.ok) return;
			return resBranches.data as RepoLog;
		},
	);

	const [readme] = createResource(tree, async (tree) => {
		if (!tree.readme) return;
		return {
			contents: tree.readme.contents,
			type: tree.readme.filename.toLowerCase().endsWith(".md")
				? "markdown"
				: "plaintext",
		} as const;
	});

	const [filesInOrder] = createResource(tree, (tree) => {
		if (!tree.files) return;
		return tree.files.sort((a, b) => {
			if (!a.is_file === b.is_file) return !a.is_file ? -1 : 1;

			const aDot = a.name.startsWith(".");
			const bDot = b.name.startsWith(".");
			if (aDot !== bDot) return aDot ? -1 : 1;

			return a.name.localeCompare(b.name, undefined, { numeric: true });
		});
	});

	const repoData = createMemo(() => {
		const db = defaultBranch();
		const l = languages();
		if (!(db && l)) return;
		return [db, l] as const;
	});

	const pathData = createMemo(() => {
		const t = tree();
		const f = filesInOrder();
		const r = readme();
		const l = logs();
		if (!(t && f && r && l)) return;
		return [t, f, r, l] as const;
	});

	return (
		<div class="mx-auto max-w-5xl">
			<Show when={repoData()} keyed>
				{([defaultBranch, languages]) => (
					<Show when={pathData()} keyed>
						{([tree, files, readme, logs]) => (
							<div>
								<Header user={params.user} repo={params.repo} />
								<div class="mb-4 flex flex-col rounded bg-white dark:bg-gray-800">
									<LanguageLine languages={languages} />
									<div class="flex flex-row">
										<div class="mr-1 flex w-1/2 flex-col border-gray-300 border-r p-2 pt-1 dark:border-gray-700">
											<FileDirectory
												user={params.user}
												repo={params.repo}
												files={files}
												tree={tree}
												defaultBranch={defaultBranch}
											/>
										</div>
										<div class="ml-1 flex w-1/2 flex-col p-2 pt-1">
											<LogData
												user={params.user}
												repo={params.repo}
												defaultBranch={defaultBranch}
												files={files}
												logs={logs}
											/>
										</div>
									</div>
								</div>
								<Show when={readme.contents}>
									<ReadmeCard
										path={`/${params.user}/${params.repo}/blob/${tree.ref || defaultBranch}`}
										readme={readme}
									/>
								</Show>
							</div>
						)}
					</Show>
				)}
			</Show>
		</div>
	);
}

function ReadmeCard(props: {
	path: string;
	readme: {
		type: "markdown" | "plaintext";
		contents: string;
	};
}) {
	return (
		<div class="mb-4 rounded bg-white p-4 dark:bg-gray-800">
			<Switch>
				<Match when={props.readme.type === "markdown"}>
					<SolidMarkdown
						class="markdown"
						transformImageUri={(href) => `${props.path}${href}`}
						transformLinkUri={(href) => `${props.path}${href}`}
						renderingStrategy="memo"
						children={props.readme.contents}
					/>
				</Match>
				<Match when={props.readme.type === "plaintext"}>
					<div class="font-mono"> {props.readme.contents}</div>
				</Match>
			</Switch>
		</div>
	);
}

function LanguageLine(props: { languages: ShTangledRepoLanguages.Language[] }) {
	const [languageLineState, setLanguageLineState] = createSignal(false);
	const toggleLanguageLineState = () =>
		setLanguageLineState(!languageLineState());

	return (
		<div class={languageLineState() ? "h-full" : "h-4"}>
			<div
				class={`flex w-full flex-row overflow-hidden rounded-t duration-75 hover:h-4 ${languageLineState() ? "h-4" : "h-2"}`}
				onclick={toggleLanguageLineState}
			>
				<For each={props.languages}>
					{(language) => (
						<div
							class="h-full border-gray-50 border-r duration-75 hover:brightness-90 dark:border-gray-950 dark:hover:brightness-110"
							style={`width: ${language.percentage}%; background-color: ${languageColors.get(language.name.toLowerCase().replaceAll(" ", ""))}`}
							title={`${language.name} ${language.percentage}%`}
						></div>
					)}
				</For>
			</div>
			<div
				class={`flex h-4 flex-row gap-3 border-gray-300 border-r border-b px-6 py-3.5 text-xs dark:border-gray-700 ${languageLineState() ? "" : "hidden"}`}
			>
				<For each={props.languages}>
					{(language) => (
						<div class="flex flex-row items-center gap-2">
							<div
								class="h-2 w-2 rounded-full"
								style={`background-color: ${languageColors.get(language.name.toLowerCase().replaceAll(" ", ""))}`}
							/>
							<span>
								<span>{language.name}</span>{" "}
								<span class="text-gray-600 dark:text-gray-400">
									{language.percentage}%
								</span>
							</span>
						</div>
					)}
				</For>
			</div>
		</div>
	);
}

function FileDirectory(props: {
	user: string;
	repo: string;
	files: ShTangledRepoTree.TreeEntry[];
	tree: ShTangledRepoTree.$output;
	defaultBranch: string;
}) {
	return (
		<div class="flex w-full flex-col">
			<Show when={props.tree.parent}>
				<a
					href={`/${props.user}/${props.repo}/tree/${props.tree.ref}/${props.tree.dotdot ? `/${props.tree.dotdot}` : ""}`}
					class="flex flex-row items-center gap-1 p-1 hover:underline"
				>
					<div class="iconify gravity-ui--folder-open-fill" />
					<span>..</span>
				</a>
			</Show>

			<For each={props.files}>
				{(file) => (
					<a
						href={`/${props.user}/${props.repo}/${file.is_file ? "blob" : "tree"}/${props.tree.ref || props.defaultBranch}/${props.tree.parent ? `${props.tree.parent}/` : ""}${file.name}`}
						class="flex flex-row items-center gap-1 p-1 hover:underline"
					>
						<div
							class={`iconify ${file.is_file ? "gravity-ui--file" : "gravity-ui--folder-fill"}`}
						/>
						<span>{file.name}</span>
					</a>
				)}
			</For>
		</div>
	);
}

function LogData(props: {
	user: string;
	repo: string;
	defaultBranch: string;
	files: ShTangledRepoTree.TreeEntry[];
	logs: RepoLog;
}) {
	return (
		<>
			<a
				class="mb-2 flex flex-row items-center gap-2 text-black hover:text-gray-600 dark:text-white hover:dark:text-gray-300"
				href={`/${props.user}/${props.repo}/commits/${props.logs.ref || props.defaultBranch}`}
			>
				<IconWithText
					icon="gravity-ui--code-commit"
					text="commits"
					style="font-bold"
				/>
				<div class="rounded bg-gray-300 px-1 text-xs dark:bg-gray-700">
					{props.logs.total}
				</div>
			</a>
			<div class="mb-3 flex flex-col gap-4">
				<For
					each={props.logs.commits.slice(
						0,
						Math.max(3, Math.floor(props.files.length / 2)),
					)}
				>
					{(commit) => {
						const hash = commit.Hash.map((num) =>
							num.toString(16).padStart(2, "0"),
						).join("");
						return (
							<div class="flex flex-col gap-1">
								<a
									class="text-black hover:text-gray-600 hover:underline dark:text-white hover:dark:text-gray-300"
									href={`/${props.user}/${props.repo}/commit/${hash}`}
								>
									{commit.Message.slice(0, commit.Message.indexOf("\n"))}
								</a>
								<span class="text-gray-500 text-xs dark:text-gray-300">
									<span class="rounded bg-gray-100 p-1 font-mono dark:bg-gray-900">
										{hash.slice(0, 8)}
									</span>
									<span class="select-none px-1 before:content-['\00B7']" />
									<span>{commit.Author.Name}</span>
									<span class="select-none px-1 before:content-['\00B7']" />
									<span>{`${new Date(commit.Author.When).toLocaleDateString(undefined, { dateStyle: "short" })} at ${new Date(commit.Author.When).toLocaleTimeString()}`}</span>
								</span>
							</div>
						);
					}}
				</For>
			</div>
		</>
	);
}
