import { ConvexError, v } from "convex/values"
import {
	apiKeyAuthenticatedQuery,
	authenticatedMutation,
	authenticatedQuery,
	authorizedGet,
} from "./functions"
import * as Directories from "./model/directories"
import * as Files from "./model/files"
import * as FileSystem from "./model/filesystem"
import {
	deleteItemsPermanently,
	emptyTrash as emptyTrashImpl,
	fetchFileUrl as fetchFileUrlImpl,
	restoreItems as restoreItemsImpl,
	VDirectoryHandle,
	VFileSystemHandle,
} from "./model/filesystem"
import { createErrorData, ErrorCode, error } from "./shared/error"
import type {
	DirectoryHandle,
	FileHandle,
	FileSystemHandle,
	FileSystemItem,
} from "./shared/filesystem"
import { FileType } from "./shared/filesystem"

export const moveItems = authenticatedMutation({
	args: {
		targetDirectory: VDirectoryHandle,
		items: v.array(VFileSystemHandle),
	},
	handler: async (ctx, { targetDirectory: targetDirectoryHandle, items }) => {
		const targetDirectory = await authorizedGet(
			ctx,
			targetDirectoryHandle.id,
		)
		if (!targetDirectory) {
			error({
				code: ErrorCode.NotFound,
				message: `Directory ${targetDirectoryHandle.id} not found`,
			})
		}

		const directoryHandles: DirectoryHandle[] = []
		const fileHandles: FileHandle[] = []
		for (const item of items) {
			switch (item.kind) {
				case FileType.Directory:
					directoryHandles.push(item)
					break
				case FileType.File:
					fileHandles.push(item)
					break
			}
		}

		const [fileMoveResult, directoryMoveResult] = await Promise.all([
			Files.move(ctx, {
				targetDirectory: targetDirectoryHandle,
				items: fileHandles,
			}),
			Directories.move(ctx, {
				targetDirectory: targetDirectoryHandle,
				sourceDirectories: directoryHandles,
			}),
		])

		return {
			moved: [...directoryMoveResult.moved, ...fileMoveResult.moved],
			errors: [...fileMoveResult.errors, ...directoryMoveResult.errors],
		}
	},
})

export const moveToTrash = authenticatedMutation({
	args: {
		handles: v.array(VFileSystemHandle),
	},
	handler: async (ctx, { handles }) => {
		for (const handle of handles) {
			const item = await authorizedGet(ctx, handle.id)
			if (!item) {
				error({
					code: ErrorCode.NotFound,
					message: `Item ${handle.id} not found`,
				})
			}
		}

		// biome-ignore lint/suspicious/useIterableCallbackReturn: switch statement is exhaustive
		const promises = handles.map((handle) => {
			switch (handle.kind) {
				case FileType.File:
					return ctx.db
						.patch(handle.id, {
							deletedAt: Date.now(),
						})
						.then(() => handle)
				case FileType.Directory:
					return Directories.moveToTrashRecursive(ctx, handle).then(
						() => handle,
					)
			}
		})

		const results = await Promise.allSettled(promises)
		const errors = []
		const okHandles: FileSystemHandle[] = []
		for (const result of results) {
			switch (result.status) {
				case "fulfilled":
					okHandles.push(result.value)
					break
				case "rejected":
					errors.push(createErrorData(ErrorCode.Internal))
					break
			}
		}

		return {
			deleted: okHandles,
			errors,
		}
	},
})

export const fetchDirectoryContent = authenticatedQuery({
	args: {
		directoryId: v.optional(v.id("directories")),
		trashed: v.boolean(),
	},
	handler: async (
		ctx,
		{ directoryId, trashed },
	): Promise<FileSystemItem[]> => {
		return await Directories.fetchContent(ctx, { directoryId, trashed })
	},
})

export const permanentlyDeleteItems = authenticatedMutation({
	args: {
		handles: v.array(VFileSystemHandle),
	},
	handler: async (ctx, { handles }) => {
		return await deleteItemsPermanently(ctx, { handles })
	},
})

export const emptyTrash = authenticatedMutation({
	handler: async (ctx) => {
		return await emptyTrashImpl(ctx)
	},
})

export const restoreItems = authenticatedMutation({
	args: {
		handles: v.array(VFileSystemHandle),
	},
	handler: async (ctx, { handles }) => {
		return await restoreItemsImpl(ctx, { handles })
	},
})

export const getStorageUrl = apiKeyAuthenticatedQuery({
	args: {
		storageId: v.id("_storage"),
	},
	handler: async (ctx, { storageId }) => {
		return await ctx.storage.getUrl(storageId)
	},
})

export const fetchFileUrl = authenticatedQuery({
	args: {
		fileId: v.id("files"),
	},
	handler: async (ctx, { fileId }) => {
		return await fetchFileUrlImpl(ctx, { fileId })
	},
})

export const openFile = authenticatedMutation({
	args: {
		fileId: v.id("files"),
	},
	handler: async (ctx, { fileId }) => {
		return await FileSystem.openFile(ctx, { fileId })
	},
})

export const saveFile = authenticatedMutation({
	args: {
		name: v.string(),
		directoryId: v.id("directories"),
		storageId: v.id("_storage"),
	},
	handler: async (ctx, { name, directoryId, storageId }) => {
		return await FileSystem.saveFile(ctx, { name, directoryId, storageId })
	},
})

export const fetchRecentFiles = authenticatedQuery({
	args: {
		limit: v.number(),
	},
	handler: async (ctx, { limit }) => {
		return await FileSystem.fetchRecentFiles(ctx, { limit })
	},
})
