<script setup lang="tsx">
import type { TokenInjectType, Responses } from '@/api';
import type {
	DropdownOption,
	DropdownGroupOption,
	DropdownDividerOption,
	DropdownRenderOption,
	DataTableColumn
} from 'naive-ui';
import type { SummaryCell } from 'naive-ui/es/data-table/src/interface';
import { inject, ref, nextTick, Suspense } from 'vue';
import filesize from 'filesize';
import { check_token, FS } from '@/api';
import { loadingMsgWrapper } from '@/utils';
import {
	useMessage,
	useDialog,
	NDataTable,
	NText,
	NIcon,
	NDropdown,
	NPopover,
	NSpin,
	NImageGroup,
	NButtonGroup,
	NButton,
	NModal
} from 'naive-ui';
import {
	Folder,
	FolderParent,
	DocumentBlank,
	Delete,
	Download
} from '@vicons/carbon';
import NLink from '@/components/NLink.vue';
import AsyncImage from '@/components/AsyncImage.vue';
import createZipDialog from '@/components/DirViewer/CreateZipDialog';
import DeleteModal from '@/components/DirViewer/DeleteModal.vue';

const message = useMessage();
const dialog = useDialog();

const jwt = inject<TokenInjectType>('jwt') as TokenInjectType;

const emit = defineEmits<{
	(e: 'reloadNode'): void;
}>();

type DropdownOptionsType = Array<
	| DropdownOption
	| DropdownGroupOption
	| DropdownDividerOption
	| DropdownRenderOption
>;

const props = defineProps<{
	nodes: Responses.GetNodeEntry[];
	showPreview: boolean;
}>();

const checkedRows = ref<number[]>([]);
const deleteNodes = ref<number[]>([]);
const deleteDialog = ref();
const deleteDialogShow = ref(false);

const dropdownX = ref(0);
const dropdownY = ref(0);
const dropdownShow = ref(false);
let dropdownCurrentNode: Responses.GetNodeEntry | null = null;

const dropdownOptions = ref<DropdownOptionsType>();
const dropdownOptionsFolder: DropdownOptionsType = [
	{
		label: () => <NText>Download</NText>,
		key: 'download',
		icon: () => (
			<NIcon>
				<Download />
			</NIcon>
		)
	},
	{
		label: () => <NText type="error">Delete</NText>,
		key: 'delete',
		icon: () => (
			<NIcon>
				<Delete />
			</NIcon>
		)
	}
];
const dropdownOptionsFile: DropdownOptionsType = [
	{
		label: () => <NText>Download</NText>,
		key: 'download',
		icon: () => (
			<NIcon>
				<Download />
			</NIcon>
		)
	},
	{
		label: () => <NText type="error">Delete</NText>,
		key: 'delete',
		icon: () => (
			<NIcon>
				<Delete />
			</NIcon>
		)
	}
];

const dropdownSelect = loadingMsgWrapper(message, async (key: string) => {
	dropdownShow.value = false;
	const token = await check_token(jwt);
	if (!token) return;
	if (!dropdownCurrentNode) return;
	switch (key) {
		case 'download':
			if (dropdownCurrentNode.isFile)
				await FS.download_file(token, dropdownCurrentNode.id);
			else createZipDialog([dropdownCurrentNode.id], dialog, jwt);
			break;
		case 'delete':
			dialog.warning({
				title: 'Really delete?',
				content: `Are you sure you want to delete "${dropdownCurrentNode.name}"`,
				positiveText: 'Yes',
				negativeText: 'No',
				onPositiveClick: () => {
					if (!dropdownCurrentNode) return;
					deleteNodes.value = [dropdownCurrentNode.id];
					showDeleteDialog();
				}
			});
			break;
	}
});

const columns: DataTableColumn<Responses.GetNodeEntry>[] = [
	{
		type: 'selection',
		options: [
			{
				label: 'Select all folders',
				key: 'folders',
				onSelect(data) {
					checkedRows.value = data
						.filter((node) => !node.isFile)
						.map((node) => node.id);
				}
			},
			{
				label: 'Select all files',
				key: 'files',
				onSelect(data) {
					checkedRows.value = data
						.filter((node) => node.isFile)
						.map((node) => node.id);
				}
			}
		],
		disabled(node) {
			return node.parent == null;
		}
	},
	{
		title: 'Name',
		key: 'name',
		minWidth: 720,
		render(node) {
			return (
				<NLink to={`/fs/${node.id}`}>
					<div>
						<NIcon
							size="1.2em"
							color="#111"
							component={
								node.isFile
									? DocumentBlank
									: node.name == '..'
									? FolderParent
									: Folder
							}
							style="top: 0.25em; margin-right: 0.5em"
						/>
						{node.name}
					</div>
				</NLink>
			);
		}
	},
	{
		title: 'Size',
		key: 'size',
		minWidth: 100,
		render(node) {
			return !node.isFile ? (
				''
			) : (
				<NPopover trigger="hover">
					{{
						default: () => `${node.size?.toLocaleString()} bytes`,
						trigger: () =>
							filesize(node.size ?? 0, {
								base: 2,
								standard: 'jedec'
							})
					}}
				</NPopover>
			);
		}
	}
];
const previewColumns: DataTableColumn<Responses.GetNodeEntry>[] = [
	columns[0],
	{
		title: 'Preview',
		key: 'preview',
		render(node) {
			return node.preview ? (
				<Suspense>
					{{
						default: () => (
							<AsyncImage alt={node.name} id={node.id} />
						),
						fallback: () => <NSpin size="small" />
					}}
				</Suspense>
			) : (
				''
			);
		}
	},
	...columns.slice(1)
];

const massDownload = loadingMsgWrapper(message, async () => {
	const token = await check_token(jwt);
	if (!token) return;
	const nodes = checkedRows.value;
	if (nodes.length == 1) {
		const node = props.nodes.find((n) => n.id == nodes[0]);
		if (!node) return;
		if (node.isFile) await FS.download_file(token, nodes[0]);
		else createZipDialog(nodes, dialog, jwt);
	} else createZipDialog(nodes, dialog, jwt);
	checkedRows.value = [];
});

const massDelete = loadingMsgWrapper(message, async () => {
	const token = await check_token(jwt);
	if (!token) return;
	dialog.warning({
		title: 'Really delete?',
		content: `Are you sure you want to delete "${checkedRows.value.length} folders/files"`,
		positiveText: 'Yes',
		negativeText: 'No',
		onPositiveClick: loadingMsgWrapper(message, async () => {
			deleteNodes.value = checkedRows.value;
			showDeleteDialog();
		})
	});
});

const selectionCell = (): SummaryCell => {
	return {
		value:
			checkedRows.value.length != 0 ? (
				<NButtonGroup>
					<NButton onClick={massDownload}>Download</NButton>
					<NButton onClick={massDelete} type="error">
						Delete
					</NButton>
				</NButtonGroup>
			) : (
				''
			),
		colSpan: props.showPreview ? 2 : 1
	};
};

const sizeCell = (data: Responses.GetNodeEntry[]): SummaryCell => {
	return {
		value: (
			<span>
				{filesize(
					data.reduce((cur, node) => cur + (node.size ?? 0), 0),
					{
						base: 2,
						standard: 'jedec'
					}
				)}
			</span>
		)
	};
};

function createPreviewSummary(data: Responses.GetNodeEntry[]) {
	return {
		preview: selectionCell(),
		size: sizeCell(data)
	};
}

function createSummary(data: Responses.GetNodeEntry[]) {
	return {
		name: selectionCell(),
		size: sizeCell(data)
	};
}

function rowProps(node: Responses.GetNodeEntry) {
	if (!('isFile' in node)) return {};
	return {
		onContextmenu: (e: MouseEvent) => {
			e.preventDefault();
			dropdownShow.value = false;
			dropdownCurrentNode = node;
			dropdownOptions.value = node.isFile
				? dropdownOptionsFile
				: dropdownOptionsFolder;
			nextTick().then(() => {
				dropdownShow.value = true;
				dropdownX.value = e.clientX;
				dropdownY.value = e.clientY;
			});
		}
	};
}

const rowKey = (node: Responses.GetNodeEntry): number => node.id;

function showDeleteDialog() {
	if (deleteNodes.value.length == 0) return;
	deleteDialogShow.value = true;
}

async function onShowDeleteDialog() {
	await deleteDialog.value?.startDelete();
	deleteDialogShow.value = false;
	emit('reloadNode');
}
</script>

<template>
	<n-image-group>
		<n-data-table
			:columns="showPreview ? previewColumns : columns"
			:data="nodes"
			:row-key="rowKey"
			:row-props="rowProps"
			:summary="showPreview ? createPreviewSummary : createSummary"
			v-model:checked-row-keys="checkedRows"
		/>
	</n-image-group>
	<n-dropdown
		placement="bottom-start"
		trigger="manual"
		:x="dropdownX"
		:y="dropdownY"
		:show="dropdownShow"
		:show-arrow="true"
		:options="dropdownOptions"
		:on-clickoutside="() => (dropdownShow = false)"
		@select="dropdownSelect"
	/>
	<n-modal
		v-model:show="deleteDialogShow"
		:close-on-esc="false"
		:mask-closable="false"
		:on-after-enter="onShowDeleteDialog"
	>
		<DeleteModal ref="deleteDialog" :nodes="deleteNodes" />
	</n-modal>
</template>

<style scoped></style>
