<template>
	<div class="flex flex-col gap-4">
		<div class="flex flex-col justify-between gap-3 lg:flex-row">
			<StyledInput
				v-model="query"
				:icon="SearchIcon"
				type="text"
				autocomplete="off"
				:placeholder="formatMessage(commonMessages.searchPlaceholder)"
				clearable
				wrapper-class="flex-1 lg:max-w-52"
				input-class="h-[40px]"
				@input="goToPage(1)"
			/>

			<div v-if="totalPages > 1" class="hidden flex-1 justify-center lg:flex">
				<Pagination :page="currentPage" :count="totalPages" @switch-page="goToPage" />
				<ConfettiExplosion v-if="visible" />
			</div>

			<div class="flex flex-col justify-end gap-2 sm:flex-row lg:flex-shrink-0">
				<div class="flex flex-col gap-2 sm:flex-row">
					<Combobox
						v-model="currentFilterType"
						class="!w-full flex-grow sm:!w-[280px] sm:flex-grow-0 lg:!w-[280px]"
						:options="filterTypes"
						:placeholder="formatMessage(commonMessages.filterByLabel)"
						@select="goToPage(1)"
					>
						<template #selected>
							<span class="flex flex-row gap-2 align-middle font-semibold">
								<ListFilterIcon class="size-5 flex-shrink-0 text-secondary" />
								<span class="truncate text-contrast"
									>{{ currentFilterType }} ({{ filteredProjects.length }})</span
								>
							</span>
						</template>
					</Combobox>

					<Combobox
						v-model="currentSortType"
						class="!w-full flex-grow sm:!w-[150px] sm:flex-grow-0 lg:!w-[150px]"
						:options="sortTypes"
						:placeholder="formatMessage(commonMessages.sortByLabel)"
						@select="goToPage(1)"
					>
						<template #selected>
							<span class="flex flex-row gap-2 align-middle font-semibold">
								<SortAscIcon
									v-if="currentSortType === 'Oldest'"
									class="size-5 flex-shrink-0 text-secondary"
								/>
								<SortDescIcon v-else class="size-5 flex-shrink-0 text-secondary" />
								<span class="truncate text-contrast">{{ currentSortType }}</span>
							</span>
						</template>
					</Combobox>
				</div>

				<ButtonStyled color="orange" class="w-full sm:w-auto">
					<button
						class="flex !h-[40px] w-full items-center justify-center gap-2 sm:w-auto"
						:disabled="paginatedProjects?.length === 0"
						@click="moderateAllInFilter()"
					>
						<ScaleIcon class="flex-shrink-0" />
						<span class="hidden sm:inline">{{ formatMessage(messages.moderate) }}</span>
						<span class="sm:hidden">Moderate</span>
					</button>
				</ButtonStyled>
			</div>
		</div>

		<div v-if="totalPages > 1" class="flex justify-center lg:hidden">
			<Pagination :page="currentPage" :count="totalPages" @switch-page="goToPage" />
			<ConfettiExplosion v-if="visible" />
		</div>

		<div class="flex flex-col gap-4">
			<div v-if="paginatedProjects.length === 0" class="universal-card h-24 animate-pulse"></div>
			<ModerationQueueCard
				v-for="item in paginatedProjects"
				v-else
				:key="item.project.id"
				:queue-entry="item"
				:owner="item.owner"
				:org="item.org"
				@start-from-project="startFromProject"
			/>
		</div>

		<div v-if="totalPages > 1" class="mt-4 flex justify-center">
			<Pagination :page="currentPage" :count="totalPages" @switch-page="goToPage" />
		</div>
	</div>
</template>
<script setup lang="ts">
import { ListFilterIcon, ScaleIcon, SearchIcon, SortAscIcon, SortDescIcon } from '@modrinth/assets'
import {
	ButtonStyled,
	Combobox,
	type ComboboxOption,
	commonMessages,
	defineMessages,
	injectNotificationManager,
	Pagination,
	StyledInput,
	useVIntl,
} from '@modrinth/ui'
import Fuse from 'fuse.js'
import ConfettiExplosion from 'vue-confetti-explosion'

import ModerationQueueCard from '~/components/ui/moderation/ModerationQueueCard.vue'
import { enrichProjectBatch, type ModerationProject } from '~/helpers/moderation.ts'
import { useModerationStore } from '~/store/moderation.ts'

useHead({ title: 'Projects queue - Modrinth' })

const { formatMessage } = useVIntl()
const { addNotification } = injectNotificationManager()
const moderationStore = useModerationStore()
const route = useRoute()
const router = useRouter()

const visible = ref(false)
if (import.meta.client && history && history.state && history.state.confetti) {
	setTimeout(async () => {
		history.state.confetti = false
		visible.value = true
		await nextTick()
		setTimeout(() => {
			visible.value = false
		}, 5000)
	}, 1000)
}

const messages = defineMessages({
	moderate: {
		id: 'moderation.moderate',
		defaultMessage: 'Moderate',
	},
})

const { data: allProjects } = await useLazyAsyncData('moderation-projects', async () => {
	const startTime = performance.now()
	let currentOffset = 0
	const PROJECT_ENDPOINT_COUNT = 350
	const allProjects: ModerationProject[] = []

	const enrichmentPromises: Promise<ModerationProject[]>[] = []

	let projects: any[] = []
	do {
		projects = (await useBaseFetch(
			`moderation/projects?count=${PROJECT_ENDPOINT_COUNT}&offset=${currentOffset}`,
			{ internal: true },
		)) as any[]

		if (projects.length === 0) break

		const enrichmentPromise = enrichProjectBatch(projects)
		enrichmentPromises.push(enrichmentPromise)

		currentOffset += projects.length

		if (enrichmentPromises.length >= 3) {
			const completed = await Promise.all(enrichmentPromises.splice(0, 2))
			allProjects.push(...completed.flat())
		}
	} while (projects.length === PROJECT_ENDPOINT_COUNT)

	const remainingBatches = await Promise.all(enrichmentPromises)
	allProjects.push(...remainingBatches.flat())

	const endTime = performance.now()
	const duration = endTime - startTime

	console.debug(
		`Projects fetched and processed in ${duration.toFixed(2)}ms (${(duration / 1000).toFixed(2)}s)`,
	)

	return allProjects
})

const query = ref(route.query.q?.toString() || '')

watch(
	query,
	(newQuery) => {
		const currentQuery = { ...route.query }
		if (newQuery) {
			currentQuery.q = newQuery
		} else {
			delete currentQuery.q
		}

		router.replace({
			path: route.path,
			query: currentQuery,
		})
	},
	{ immediate: false },
)

watch(
	() => route.query.q,
	(newQueryParam) => {
		const newValue = newQueryParam?.toString() || ''
		if (query.value !== newValue) {
			query.value = newValue
		}
	},
)

const currentFilterType = ref('All projects')
const filterTypes: ComboboxOption<string>[] = [
	{ value: 'All projects', label: 'All projects' },
	{ value: 'Modpacks', label: 'Modpacks' },
	{ value: 'Mods', label: 'Mods' },
	{ value: 'Resource Packs', label: 'Resource Packs' },
	{ value: 'Data Packs', label: 'Data Packs' },
	{ value: 'Plugins', label: 'Plugins' },
	{ value: 'Shaders', label: 'Shaders' },
]

const currentSortType = ref('Oldest')
const sortTypes: ComboboxOption<string>[] = [
	{ value: 'Oldest', label: 'Oldest' },
	{ value: 'Newest', label: 'Newest' },
]

const currentPage = ref(1)
const itemsPerPage = 15
const totalPages = computed(() => Math.ceil((filteredProjects.value?.length || 0) / itemsPerPage))

const fuse = computed(() => {
	if (!allProjects.value || allProjects.value.length === 0) return null
	return new Fuse(allProjects.value, {
		keys: [
			{
				name: 'project.title',
				weight: 3,
			},
			{
				name: 'project.slug',
				weight: 2,
			},
			{
				name: 'project.description',
				weight: 2,
			},
			{
				name: 'project.project_type',
				weight: 1,
			},
			'owner.user.username',
			'org.name',
			'org.slug',
		],
		includeScore: true,
		threshold: 0.4,
	})
})

const searchResults = computed(() => {
	if (!query.value || !fuse.value) return null
	return fuse.value.search(query.value).map((result) => result.item)
})

const baseFiltered = computed(() => {
	if (!allProjects.value) return []
	return query.value && searchResults.value ? searchResults.value : [...allProjects.value]
})

const typeFiltered = computed(() => {
	if (currentFilterType.value === 'All projects') return baseFiltered.value

	const filterMap: Record<string, string> = {
		Modpacks: 'modpack',
		Mods: 'mod',
		'Resource Packs': 'resourcepack',
		'Data Packs': 'datapack',
		Plugins: 'plugin',
		Shaders: 'shader',
	}

	const projectType = filterMap[currentFilterType.value]
	if (!projectType) return baseFiltered.value

	return baseFiltered.value.filter(
		(queueItem) =>
			queueItem.project.project_types.length > 0 &&
			queueItem.project.project_types[0] === projectType,
	)
})

const filteredProjects = computed(() => {
	const filtered = [...typeFiltered.value]

	if (currentSortType.value === 'Oldest') {
		filtered.sort((a, b) => {
			const dateA = new Date(a.project.queued || a.project.published || 0).getTime()
			const dateB = new Date(b.project.queued || b.project.published || 0).getTime()
			return dateA - dateB
		})
	} else {
		filtered.sort((a, b) => {
			const dateA = new Date(a.project.queued || a.project.published || 0).getTime()
			const dateB = new Date(b.project.queued || b.project.published || 0).getTime()
			return dateB - dateA
		})
	}

	return filtered
})

const paginatedProjects = computed(() => {
	if (!filteredProjects.value) return []
	const start = (currentPage.value - 1) * itemsPerPage
	const end = start + itemsPerPage
	return filteredProjects.value.slice(start, end)
})

function goToPage(page: number) {
	currentPage.value = page
}

async function findFirstUnlockedProject(): Promise<ModerationProject | null> {
	let skippedCount = 0

	while (moderationStore.hasItems) {
		const currentId = moderationStore.getCurrentProjectId()
		if (!currentId) return null

		const project = filteredProjects.value.find((p) => p.project.id === currentId)
		if (!project) {
			moderationStore.completeCurrentProject(currentId, 'skipped')
			continue
		}

		try {
			const lockStatus = await moderationStore.checkLock(currentId)

			if (!lockStatus.locked || lockStatus.expired) {
				if (skippedCount > 0) {
					addNotification({
						title: 'Skipped locked projects',
						text: `Skipped ${skippedCount} project(s) being moderated by others.`,
						type: 'info',
					})
				}
				return project
			}

			// Project is locked, skip it
			moderationStore.completeCurrentProject(currentId, 'skipped')
			skippedCount++
		} catch {
			return project
		}
	}

	return null
}

async function moderateAllInFilter() {
	// Start from the current page - get projects from current page onwards
	const startIndex = (currentPage.value - 1) * itemsPerPage
	const projectsFromCurrentPage = filteredProjects.value.slice(startIndex)
	const projectIds = projectsFromCurrentPage.map((queueItem) => queueItem.project.id)
	moderationStore.setQueue(projectIds)

	// Find first unlocked project
	const targetProject = await findFirstUnlockedProject()

	if (!targetProject) {
		addNotification({
			title: 'All projects locked',
			text: 'All projects in queue are currently being moderated by others.',
			type: 'warning',
		})
		return
	}

	navigateTo({
		name: 'type-id',
		params: {
			type: 'project',
			id: targetProject.project.slug,
		},
		state: {
			showChecklist: true,
		},
	})
}

async function startFromProject(projectId: string) {
	// Find the index of the clicked project in the filtered list
	const projectIndex = filteredProjects.value.findIndex((p) => p.project.id === projectId)
	if (projectIndex === -1) {
		// Project not found in filtered list, just moderate it alone
		moderationStore.setSingleProject(projectId)
	} else {
		// Start queue from this project onwards
		const projectsFromHere = filteredProjects.value.slice(projectIndex)
		const projectIds = projectsFromHere.map((queueItem) => queueItem.project.id)
		moderationStore.setQueue(projectIds)
	}

	// Find first unlocked project
	const targetProject = await findFirstUnlockedProject()

	if (!targetProject) {
		addNotification({
			title: 'All projects locked',
			text: 'All projects in queue are currently being moderated by others.',
			type: 'warning',
		})
		return
	}

	navigateTo({
		name: 'type-id',
		params: {
			type: 'project',
			id: targetProject.project.slug,
		},
		state: {
			showChecklist: true,
		},
	})
}
</script>
