<svelte:options runes={true} />

<script lang="ts">
	import { createInfiniteQuery, createQuery } from '@tanstack/svelte-query'
	import { onDestroy } from 'svelte'
	import type { SearchResult } from '$lib/api/adapters/search.adapter'
	import { searchQueries, type SearchFilters } from '$lib/api/queries/search.queries'
	import { collectionQueries } from '$lib/api/queries/collection.queries'
	import Button from '../ui/Button.svelte'
	import Icon from '../Icon.svelte'
	import Input from '../ui/Input.svelte'
	import CharacterTags from '$lib/components/tags/CharacterTags.svelte'
	import ElementPicker from '../ui/element-picker/ElementPicker.svelte'
	import { useInfiniteLoader } from '$lib/stores/loaderState.svelte'
	import { getCharacterImage, getWeaponImage, getSummonImage } from '$lib/features/database/detail/image'
	import type { AddItemResult, SearchMode } from '$lib/types/api/search'
	import type { CollectionCharacter, CollectionWeapon, CollectionSummon } from '$lib/types/api/collection'

	interface Props {
		type: 'weapon' | 'character' | 'summon'
		onAddItems?: (items: AddItemResult[]) => void
		canAddMore?: boolean
		/** User ID to enable collection search mode */
		authUserId?: string
		/** Required proficiencies for mainhand weapon selection */
		requiredProficiencies?: number[]
	}

	let {
		type = 'weapon',
		onAddItems = () => {},
		canAddMore = true,
		authUserId,
		requiredProficiencies
	}: Props = $props()

	// Search state (local UI state)
	let searchQuery = $state('')
	let debouncedSearchQuery = $state('')
	let debounceTimer: ReturnType<typeof setTimeout> | undefined

	// Filter state
	let elementFilters = $state<number[]>([])
	let rarityFilters = $state<number[]>([])
	let proficiencyFilters = $state<number[]>([])

	// Search mode state (only available when authUserId is provided)
	let searchMode = $state<SearchMode>('all')

	// Refs
	let sentinelEl = $state<HTMLElement>()

	// Constants
	const elements = [
		{ value: 0, label: 'Null', color: 'var(--grey-50)' },
		{ value: 1, label: 'Wind', color: 'var(--wind-bg)' },
		{ value: 2, label: 'Fire', color: 'var(--fire-bg)' },
		{ value: 3, label: 'Water', color: 'var(--water-bg)' },
		{ value: 4, label: 'Earth', color: 'var(--earth-bg)' },
		{ value: 5, label: 'Dark', color: 'var(--dark-bg)' },
		{ value: 6, label: 'Light', color: 'var(--light-bg)' }
	]

	const rarities = [
		{ value: 1, label: 'R' },
		{ value: 2, label: 'SR' },
		{ value: 3, label: 'SSR' }
	]

	const proficiencies = [
		{ value: 1, label: 'Sabre' },
		{ value: 2, label: 'Dagger' },
		{ value: 3, label: 'Spear' },
		{ value: 4, label: 'Axe' },
		{ value: 5, label: 'Staff' },
		{ value: 6, label: 'Gun' },
		{ value: 7, label: 'Melee' },
		{ value: 8, label: 'Bow' },
		{ value: 9, label: 'Harp' },
		{ value: 10, label: 'Katana' }
	]

	// Debounce search query changes
	$effect(() => {
		const query = searchQuery

		if (debounceTimer) {
			clearTimeout(debounceTimer)
		}

		debounceTimer = setTimeout(() => {
			debouncedSearchQuery = query
		}, 300)

		return () => {
			if (debounceTimer) {
				clearTimeout(debounceTimer)
			}
		}
	})

	// Build filters object for query
	// Use requiredProficiencies for mainhand selection if set, otherwise use user-selected filters
	const effectiveProficiencies = $derived(
		requiredProficiencies ?? (proficiencyFilters.length > 0 ? proficiencyFilters : undefined)
	)

	const filters = $derived<SearchFilters>({
		element: elementFilters.length > 0 ? elementFilters : undefined,
		rarity: rarityFilters.length > 0 ? rarityFilters : undefined,
		proficiency: type === 'weapon' && effectiveProficiencies ? effectiveProficiencies : undefined
	})

	// Helper to map collection items to search result format with collectionId
	function mapCollectionToSearchResult(
		item: CollectionCharacter | CollectionWeapon | CollectionSummon
	): AddItemResult {
		const entity = 'character' in item ? item.character : 'weapon' in item ? item.weapon : item.summon
		return {
			id: entity.id,
			granblueId: entity.granblueId,
			name: entity.name,
			element: entity.element,
			rarity: entity.rarity,
			collectionId: item.id
		}
	}

	// Filter collection items by search query (client-side)
	function filterCollectionByQuery<T extends CollectionCharacter | CollectionWeapon | CollectionSummon>(
		items: T[],
		query: string
	): T[] {
		if (!query.trim()) return items
		const lowerQuery = query.toLowerCase()
		return items.filter((item) => {
			const entity = 'character' in item ? item.character : 'weapon' in item ? item.weapon : item.summon
			const name = entity.name
			const nameEn = typeof name === 'string' ? name : name?.en || ''
			const nameJa = typeof name === 'string' ? '' : name?.ja || ''
			return nameEn.toLowerCase().includes(lowerQuery) || nameJa.toLowerCase().includes(lowerQuery)
		})
	}

	// TanStack Query v6: Use createInfiniteQuery with thunk pattern for reactivity
	// Query automatically updates when type, debouncedSearchQuery, or filters change
	// Note: Type assertion needed because different search types have different query keys
	// but share the same SearchPageResult structure
	const searchQueryResult = createInfiniteQuery(() => {
		const query = debouncedSearchQuery
		const currentFilters = filters

		// Select the appropriate query based on type
		// All query types return the same SearchPageResult structure
		switch (type) {
			case 'weapon':
				return searchQueries.weapons(query, currentFilters)
			case 'character':
				return searchQueries.characters(query, currentFilters) as unknown as ReturnType<typeof searchQueries.weapons>
			case 'summon':
				return searchQueries.summons(query, currentFilters) as unknown as ReturnType<typeof searchQueries.weapons>
		}
	})

	// Collection query - enabled when authUserId is provided
	// Used both for collection mode AND for highlighting owned items in "all" mode
	// Type assertion needed because different types have different query result types
	// but they all share the same structure with different content types
	const collectionQueryResult = createInfiniteQuery(() => {
		if (!authUserId) {
			// Return a disabled query config
			return {
				...collectionQueries.characters(authUserId ?? '', {}, false),
				enabled: false
			} as ReturnType<typeof collectionQueries.characters>
		}

		// For collection mode, apply filters; for "all" mode, fetch all to build owned set
		const currentFilters = searchMode === 'collection' ? {
			element: elementFilters.length > 0 ? elementFilters : undefined,
			rarity: rarityFilters.length > 0 ? rarityFilters : undefined
		} : {}

		switch (type) {
			case 'weapon':
				return {
					...collectionQueries.weapons(authUserId, currentFilters),
					enabled: true // Always enabled when authUserId exists
				} as unknown as ReturnType<typeof collectionQueries.characters>
			case 'character':
				return {
					...collectionQueries.characters(authUserId, currentFilters),
					enabled: true
				}
			case 'summon':
				return {
					...collectionQueries.summons(authUserId, currentFilters),
					enabled: true
				} as unknown as ReturnType<typeof collectionQueries.characters>
		}
	})

	// Flatten all pages into a single items array
	const rawResults = $derived(
		searchQueryResult.data?.pages.flatMap((page) => page.results) ?? []
	)

	// Collection results (filtered client-side by search query)
	const rawCollectionResults = $derived.by(() => {
		const pages = collectionQueryResult.data?.pages ?? []
		const allItems = pages.flatMap((page) => page.results)
		return filterCollectionByQuery(allItems, debouncedSearchQuery)
	})

	// Set of owned item granblue IDs for fast lookup (used in "all" mode to highlight owned items)
	const ownedItemIds = $derived.by(() => {
		const pages = collectionQueryResult.data?.pages ?? []
		const allItems = pages.flatMap((page) => page.results)
		const ids = new Set<string>()
		for (const item of allItems) {
			// Type assertion needed because the query result type doesn't capture all variants
			const anyItem = item as unknown as Record<string, unknown>
			const entity = (anyItem.character ?? anyItem.weapon ?? anyItem.summon) as
				| { granblueId?: string }
				| undefined
			if (entity?.granblueId) {
				ids.add(String(entity.granblueId))
			}
		}
		return ids
	})

	// Helper to check if an item is owned (in user's collection)
	function isOwned(item: AddItemResult): boolean {
		return ownedItemIds.has(String(item.granblueId))
	}

	// Deduplicate by id - needed because the API may return the same item across pages
	// (e.g., due to items being added/removed between page fetches)
	const searchResults = $derived.by<AddItemResult[]>(() => {
		if (searchMode === 'collection' && authUserId) {
			// Map collection items to AddItemResult format
			return rawCollectionResults.map(mapCollectionToSearchResult)
		}
		// Regular search results - cast to AddItemResult[] since they're compatible
		const deduped = Array.from(new Map(rawResults.map((item) => [item.id, item])).values())
		return deduped as AddItemResult[]
	})

	// Get the active query based on search mode
	const activeQuery = $derived(
		searchMode === 'collection' && authUserId ? collectionQueryResult : searchQueryResult
	)

	// State-gated infinite scroll
	// Type assertion needed because activeQuery is a union of different query types
	// eslint-disable-next-line @typescript-eslint/no-explicit-any
	const loader = useInfiniteLoader(() => activeQuery as any, () => sentinelEl, { rootMargin: '200px' })

	// Reset loader when search mode or filters change
	$effect(() => {
		void searchMode
		void filters
		loader.reset()
	})

	// Cleanup on destroy
	onDestroy(() => loader.destroy())

	// Computed states
	const isEmpty = $derived(
		searchResults.length === 0 && !activeQuery.isLoading && !activeQuery.isError
	)

	function handleItemClick(item: AddItemResult) {
		if (canAddMore) {
			onAddItems([item])
		}
	}

	function handleElementChange(value: number | number[]) {
		elementFilters = Array.isArray(value) ? value : value !== undefined ? [value] : []
	}

	function toggleRarityFilter(rarity: number) {
		if (rarityFilters.includes(rarity)) {
			rarityFilters = rarityFilters.filter(r => r !== rarity)
		} else {
			rarityFilters = [...rarityFilters, rarity]
		}
	}

	function toggleProficiencyFilter(prof: number) {
		if (proficiencyFilters.includes(prof)) {
			proficiencyFilters = proficiencyFilters.filter(p => p !== prof)
		} else {
			proficiencyFilters = [...proficiencyFilters, prof]
		}
	}

	function getImageUrl(item: AddItemResult): string {
		const id = item.granblueId
		if (!id) return `/images/placeholders/placeholder-${type}-square.png`

		switch (type) {
			case 'character':
				return getCharacterImage(id, 'square', '01')
			case 'weapon':
				return getWeaponImage(id, 'square')
			case 'summon':
				return getSummonImage(id, 'square')
			default:
				return ''
		}
	}

	function getItemName(item: AddItemResult): string {
		const name = item.name
		if (typeof name === 'string') return name
		return name?.en || name?.ja || 'Unknown'
	}
</script>

<div class="search-content">
	<div class="search-section">
		<Input
			bind:value={searchQuery}
			type="text"
			placeholder="Search by name..."
			leftIcon="search"
			contained
			fullWidth
			class="search-input"
		/>
	</div>

	{#if authUserId}
		<div class="mode-toggle">
			<button
				class="mode-btn"
				class:active={searchMode === 'all'}
				onclick={() => searchMode = 'all'}
			>
				All Items
			</button>
			<button
				class="mode-btn"
				class:active={searchMode === 'collection'}
				onclick={() => searchMode = 'collection'}
			>
				My Collection
			</button>
		</div>
	{/if}

	<div class="filters-section">
		<!-- Element filters -->
		<div class="filter-group">
			<label class="filter-label">Element</label>
			<ElementPicker
				value={elementFilters}
				onValueChange={handleElementChange}
				multiple={true}
				includeAny={true}
				contained={true}
			/>
		</div>

		<!-- Rarity filters -->
		<div class="filter-group">
			<label class="filter-label">Rarity</label>
			<div class="filter-buttons">
				{#each rarities as rarity}
					<button
						class="filter-btn rarity-btn"
						class:active={rarityFilters.includes(rarity.value)}
						onclick={() => toggleRarityFilter(rarity.value)}
						aria-pressed={rarityFilters.includes(rarity.value)}
					>
						{rarity.label}
					</button>
				{/each}
			</div>
		</div>

		<!-- Proficiency filters (weapons only, hidden when required proficiencies set for mainhand) -->
		{#if type === 'weapon' && !requiredProficiencies}
			<div class="filter-group">
				<label class="filter-label">Proficiency</label>
				<div class="filter-buttons proficiency-grid">
					{#each proficiencies as prof}
						<button
							class="filter-btn prof-btn"
							class:active={proficiencyFilters.includes(prof.value)}
							onclick={() => toggleProficiencyFilter(prof.value)}
							aria-pressed={proficiencyFilters.includes(prof.value)}
						>
							{prof.label}
						</button>
					{/each}
				</div>
			</div>
		{/if}
	</div>

	<!-- Results -->
	<div class="results-section">
		{#if activeQuery.isLoading}
			<div class="loading">
				<Icon name="loader-2" size={24} />
				<span>Searching...</span>
			</div>
		{:else if activeQuery.isError}
			<div class="error-state">
				<Icon name="alert-circle" size={24} />
				<p>{activeQuery.error?.message || 'Search failed'}</p>
				<Button size="small" onclick={() => activeQuery.refetch()}>Retry</Button>
			</div>
		{:else if searchResults.length > 0}
			<ul class="results-list">
				{#each searchResults as item (item.id)}
					{@const owned = searchMode === 'all' && authUserId && isOwned(item)}
					<li class="result-item">
						<button
							class="result-button"
							class:disabled={!canAddMore}
							class:from-collection={item.collectionId}
							class:owned={owned}
							onclick={() => handleItemClick(item)}
							aria-label="{canAddMore ? 'Add' : 'Grid full - cannot add'} {getItemName(item)}"
							disabled={!canAddMore}
						>
							<img
								src={getImageUrl(item)}
								alt={getItemName(item)}
								class="result-image"
								loading="lazy"
							/>
							<span class="result-name">{getItemName(item)}</span>
							{#if type === 'character'}
								<CharacterTags character={item} />
							{/if}
							{#if item.collectionId}
								<Icon name="bookmark" size={14} class="collection-indicator" />
							{:else if owned}
								<Icon name="check" size={14} class="owned-indicator" />
							{/if}
							{#if item.element !== undefined}
								<span
									class="result-element"
									style:color={elements.find(e => e.value === item.element)?.color}
								>
									{elements.find(e => e.value === item.element)?.label}
								</span>
							{/if}
						</button>
					</li>
				{/each}
			</ul>

			<div
				class="load-more-sentinel"
				bind:this={sentinelEl}
				class:hidden={!activeQuery.hasNextPage}
			></div>

			{#if activeQuery.isFetchingNextPage}
				<div class="loading-more">
					<Icon name="loader-2" size={20} />
					<span>Loading more...</span>
				</div>
			{/if}
		{:else if isEmpty}
			<div class="no-results">
				{#if searchMode === 'collection'}
					{#if searchQuery.length > 0}
						No items match your search
					{:else}
						Your collection is empty
					{/if}
				{:else if searchQuery.length > 0}
					No results found
				{:else}
					Start typing to search
				{/if}
			</div>
		{/if}
	</div>
</div>

<style lang="scss">
	@use '$src/themes/spacing' as *;
	@use '$src/themes/colors' as *;
	@use '$src/themes/typography' as *;
	@use '$src/themes/layout' as *;

	.search-content {
		display: flex;
		flex-direction: column;
		height: calc(100vh - 60px); // Account for sidebar header
		overflow: hidden;
	}

	.search-section {
		padding: 0 0 $unit-2x 0;
		flex-shrink: 0;

		:global(.search-input) {
			border-radius: $card-corner;
		}
	}

	.mode-toggle {
		display: flex;
		gap: $unit-half;
		padding-bottom: $unit-2x;
		flex-shrink: 0;

		.mode-btn {
			flex: 1;
			padding: $unit calc($unit * 1.5);
			border: 1px solid var(--border-primary);
			background: var(--bg-secondary);
			border-radius: $input-corner;
			font-size: $font-small;
			font-weight: $medium;
			cursor: pointer;
			transition: all 0.2s;
			color: var(--text-secondary);

			&:hover {
				background: var(--bg-tertiary);
				border-color: var(--border-secondary);
			}

			&.active {
				background: var(--accent-blue);
				color: white;
				border-color: var(--accent-blue);
			}
		}
	}

	.filters-section {
		padding-bottom: $unit-2x;
		border-bottom: 1px solid var(--border-primary);
		flex-shrink: 0;

		.filter-group {
			margin-bottom: calc($unit * 1.5);

			&:last-child {
				margin-bottom: 0;
			}
		}

		.filter-label {
			display: block;
			font-size: $font-tiny;
			font-weight: $bold;
			text-transform: uppercase;
			color: var(--text-secondary);
			margin-bottom: $unit;
			letter-spacing: 0.5px;
		}

		.filter-buttons {
			display: flex;
			flex-wrap: wrap;
			gap: $unit-half;
		}

		.proficiency-grid {
			display: grid;
			grid-template-columns: repeat(3, 1fr);
			gap: $unit-half;
		}

		.filter-btn {
			padding: $unit-half $unit;
			border: 1px solid var(--border-primary);
			background: var(--bg-secondary);
			border-radius: $input-corner;
			font-size: $font-small;
			cursor: pointer;
			transition: all 0.2s;
			color: var(--text-primary);

			&:hover {
				background: var(--bg-tertiary);
				border-color: var(--border-secondary);
			}

			&.active {
				background: var(--accent-blue);
				color: white;
				border-color: var(--accent-blue);
			}
		}
	}

	.results-section {
		flex: 1;
		overflow-y: auto;
		padding: $unit-2x 0;
		min-height: 0;

		.loading,
		.no-results {
			text-align: center;
			padding: $unit-3x;
			color: var(--text-secondary);
			font-size: $font-regular;
		}

		.results-list {
			list-style: none;
			padding: 0;
			margin: 0;
		}

		.result-item {
			margin-bottom: $unit-half;

			.result-button {
				width: 100%;
				display: flex;
				align-items: center;
				gap: $unit;
				padding: $unit;
				border: 1px solid transparent;
				border-radius: $input-corner;
				background: var(--bg-secondary);
				cursor: pointer;
				transition: all 0.2s;
				text-align: left;

				&:hover {
					background: var(--bg-tertiary);
					border-color: var(--accent-blue);
				}

				&:active:not(:disabled) {
					transform: scale(0.99);
				}

				&.disabled,
				&:disabled {
					opacity: 0.5;
					cursor: not-allowed;
					background: var(--bg-disabled);

					&:hover {
						background: var(--bg-disabled);
						border-color: transparent;
					}
				}
			}

			.result-image {
				width: 48px;
				height: 48px;
				object-fit: cover;
				border-radius: 4px;
				border: 1px solid var(--border-primary);
				flex-shrink: 0;
			}

			.result-name {
				flex: 1;
				font-size: $font-regular;
				color: var(--text-primary);
			}

			.result-element {
				font-size: $font-small;
				font-weight: $bold;
				flex-shrink: 0;
			}

			:global(.collection-indicator) {
				color: var(--accent-blue);
				flex-shrink: 0;
			}

			:global(.owned-indicator) {
				color: var(--success, #4caf50);
				flex-shrink: 0;
				opacity: 0.7;
			}

			// Subtle highlight for owned items in "all" mode
			.result-button.owned {
				background: var(--owned-bg, rgba(76, 175, 80, 0.08));

				&:hover {
					background: var(--owned-bg-hover, rgba(76, 175, 80, 0.15));
				}
			}
		}

		.loading {
			display: flex;
			flex-direction: column;
			align-items: center;
			gap: $unit;

			:global(svg) {
				animation: spin 1s linear infinite;
			}
		}

		.error-state {
			display: flex;
			flex-direction: column;
			align-items: center;
			justify-content: center;
			gap: $unit;
			padding: $unit-3x;
			color: var(--text-secondary);

			:global(svg) {
				color: var(--text-tertiary);
			}

			p {
				margin: 0;
				font-size: $font-regular;
			}
		}

		.load-more-sentinel {
			height: 1px;
			margin-top: $unit;

			&.hidden {
				display: none;
			}
		}

		.loading-more {
			display: flex;
			align-items: center;
			justify-content: center;
			gap: $unit;
			padding: $unit-2x;
			color: var(--text-secondary);
			font-size: $font-regular;

			:global(svg) {
				animation: spin 1s linear infinite;
			}
		}
	}

	@keyframes spin {
		from { transform: rotate(0deg); }
		to { transform: rotate(360deg); }
	}
</style>
