<template>
	<div class="page">
		<section v-if="currentItems.length" class="content">
			<file-explorer-list
				v-if="displayMode === 'list'"
				:items="currentItems"
				:pending-path="pendingFolderPath"
				@open="openItem"
				@queue="queueItem"
				@play="playItem"
			/>
			<file-explorer-grid
				v-else
				:items="currentItems"
				:pending-path="pendingFolderPath"
				@open="openItem"
			/>
		</section>
		<div v-else-if="pageLoading" class="loading-wrap">
			<t-loading text="Loading directory" />
		</div>
		<t-empty v-else description="Directory is empty" />
	</div>
</template>

<script setup lang="ts">
import { Toast } from "tdesign-mobile-vue";
import FileAPI from "@/api/FileAPI";
import FileExplorerGrid from "./FileExplorerGrid.vue";
import FileExplorerList from "./FileExplorerList.vue";
import { useNavBarStore } from "@/store/navBarStore";
import {
	type DisplayMode,
	type ExplorerItem,
	mapServerFileToExplorerItem,
	sortExplorerItems
} from "./fileExplorer.shared";

const props = withDefaults(defineProps<{
	mode?: DisplayMode;
	path?: string | string[];
	useRoutePath?: boolean;
}>(), {
	mode: "list",
	path: "",
	useRoutePath: true
});

const emit = defineEmits([
	"update:path",
	"open-folder",
	"open-file",
	"queue-file",
	"play-file",
	"update:mode"
]);

const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const navBarRightOwner = `file-explorer-page-${Math.random().toString(36).slice(2)}`;

const displayMode = ref<DisplayMode>(props.mode);
const localSegments = ref<string[]>(normalizePath(props.path));
const directoryCache = ref<Record<string, ExplorerItem[]>>({});
const pageLoading = ref(false);
const pendingFolderPath = ref("");
let latestLoadToken = 0;

const shouldUseRoutePath = computed(() => {
	if (!props.useRoutePath) {
		return false;
	}

	return route.name === "FileTab" || route.name === "FileExplorerPage";
});

const routeSegments = computed(() => normalizePath(route.params.pathMatch));
const currentSegments = computed(() => {
	if (shouldUseRoutePath.value) {
		return routeSegments.value;
	}

	return localSegments.value;
});

const currentItems = computed<ExplorerItem[]>(() => {
	return directoryCache.value[getDirectoryKey(currentSegments.value)] || [];
});

const currentTitle = computed(() => {
	if (!currentSegments.value.length) {
		return "文件";
	}

	return currentSegments.value[currentSegments.value.length - 1];
});

const displayModeActionText = computed(() => {
	return displayMode.value === "list" ? "平铺" : "列表";
});

const navBarRightRenderer = defineComponent({
	name: "file-explorer-nav-right",
	setup() {
		const buttonComponent = resolveComponent("t-button");
		const iconComponent = resolveComponent("t-icon");

		return () => h(buttonComponent, {
			size: "small",
			variant: "text",
			theme: "primary",
			class: "nav-switch",
			shape: "square",
			"aria-label": displayModeActionText.value,
			onClick: toggleDisplayMode
		}, {
			icon: () => h(iconComponent, {
				name: displayMode.value === "list" ? "grid-view" : "view-list",
				size: "1.5rem"
			})
		});
	}
});

watch(
	() => props.mode,
	(value) => {
		displayMode.value = value;
	},
	{ immediate: true }
);

watch(
	currentTitle,
	(value) => {
		navBarStore.setTitle(value);
	},
	{ immediate: true }
);

watch(
	displayModeActionText,
	() => {
		navBarStore.setRightRenderer(navBarRightRenderer, navBarRightOwner);
	},
	{ immediate: true }
);

watch(
	() => props.path,
	(value) => {
		if (shouldUseRoutePath.value) {
			return;
		}

		localSegments.value = normalizePath(value);
	},
	{ immediate: true }
);

watch(
	currentSegments,
	(segments) => {
		void hydrateCurrentDirectory(segments);
	},
	{ immediate: true }
);

function normalizePath(source?: string | string[]): string[] {
	if (Array.isArray(source)) {
		return source.filter((segment) => !!segment);
	}

	if (!source) {
		return [];
	}

	return source.split("/").filter((segment) => !!segment);
}

function setDisplayMode(mode: DisplayMode): void {
	if (displayMode.value === mode) {
		return;
	}

	displayMode.value = mode;
	emit("update:mode", mode);
}

function toggleDisplayMode(): void {
	setDisplayMode(displayMode.value === "list" ? "grid" : "list");
}

onUnmounted(() => {
	navBarStore.clearRight(navBarRightOwner);
});

async function openItem(item: ExplorerItem): Promise<void> {
	if (pendingFolderPath.value) {
		return;
	}

	if (item.type === "dir") {
		const nextSegments = normalizePath(item.path);
		pendingFolderPath.value = item.path;

		try {
			await ensureDirectoryLoaded(nextSegments, false);
			emit("open-folder", nextSegments);
			await syncPath(nextSegments);
		} catch (error) {
			Toast({
				theme: "error",
				message: "操作失败"
			});
		} finally {
			pendingFolderPath.value = "";
		}

		return;
	}

	emit("open-file", item);
}

function queueItem(item: ExplorerItem): void {
	emit("queue-file", item);
}

function playItem(item: ExplorerItem): void {
	console.log("play-item", item);
	emit("play-file", item);
}

async function syncPath(nextSegments: string[]): Promise<void> {
	if (shouldUseRoutePath.value) {
		if (!nextSegments.length) {
			await router.push("/");
			return;
		}

		await router.push({
			name: "FileExplorerPage",
			params: {
				pathMatch: nextSegments
			}
		});
		return;
	}

	localSegments.value = nextSegments;
	emit("update:path", nextSegments);
}

async function hydrateCurrentDirectory(pathSegments: string[]): Promise<void> {
	try {
		await ensureDirectoryLoaded(pathSegments, true);
	} catch (error) {
		Toast({
			theme: "error",
			message: "操作失败"
		});
	}
}

async function ensureDirectoryLoaded(pathSegments: string[], updatePageLoading: boolean): Promise<ExplorerItem[]> {
	const cacheKey = getDirectoryKey(pathSegments);
	const cachedItems = directoryCache.value[cacheKey];

	if (cachedItems) {
		return cachedItems;
	}

	const requestToken = latestLoadToken + 1;
	latestLoadToken = requestToken;

	if (updatePageLoading) {
		pageLoading.value = true;
	}

	try {
		const files = await FileAPI.list(pathSegments);
		const items = sortExplorerItems(files.map((item) => mapServerFileToExplorerItem(item)));
		directoryCache.value = {
			...directoryCache.value,
			[cacheKey]: items
		};
		return items;
	} finally {
		if (updatePageLoading && requestToken === latestLoadToken) {
			pageLoading.value = false;
		}
	}
}

function getDirectoryKey(pathSegments: string[]): string {
	return pathSegments.join("/");
}
</script>

<style scoped lang="less">
.page {
	--page-top-gap: calc(var(--app-nav-offset, 0px) + 1rem);

	gap: 1rem;
	height: 100%;
	box-sizing: border-box;
	display: flex;
	overflow: hidden;
	flex-direction: column;

	.nav-switch {
		padding: 0;
		width: 2rem;
		height: 2rem;
	}

	.go-up {
		border: 1px solid var(--app-line);
		background: var(--app-card);
		box-shadow: 0 .35rem 1rem rgba(17, 32, 56, .05);
	}

	.go-up {
		gap: .875rem;
		display: flex;
		padding: .875rem 1rem;
		border-radius: 1rem;
		align-items: center;
		cursor: pointer;
	}

	.content,
	.loading-wrap {
		min-height: 0;
		flex: 1 1 auto;
	}

	.content {
		display: flex;
		overflow: hidden;

		> * {
			width: 100%;
			flex: 1 1 auto;
			min-height: 0;
		}
	}

	.loading-wrap {
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.icon {
		width: 2.75rem;
		height: 2.75rem;
		display: flex;
		border-radius: .9rem;
		align-items: center;
		justify-content: center;
		color: #fff;
		background: linear-gradient(135deg, #4d8dff, #76a9ff);

		&.dir {
			background: linear-gradient(135deg, #ff9c3d, #ffbf69);
		}
	}

	.meta {
		gap: .3rem;
		min-width: 0;
		display: flex;
		flex: 1 1 auto;
		flex-direction: column;
	}

	.name,
	.desc {
		margin: 0;
		word-break: break-all;
	}

	.name {
		color: var(--app-text);
		font-size: 1rem;
	}

	.desc {
		color: var(--app-sub);
		font-size: .8125rem;
	}
}
</style>
