/**
 * List Component Module
 */
globalThis.Component.register('List', container => {
	container.mode = container.getAttribute('mode') || 'normal'
	container.fast = container.hasAttribute('fast')
	container.collapsible = container.hasAttribute('collapsible')

	const padTopEl = container.fast ? container.querySelector('.vs-pad-top') : null
	const padBottomEl = container.fast ? container.querySelector('.vs-pad-bottom') : null

	const defaultSets = {
		idfield: 'id', labelfield: 'label', summaryfield: 'summary',
		groupidfield: 'id', grouplabelfield: 'label', groupsummaryfield: 'summary', groupfield: 'group',
		parentfield: 'parent', groupicon: 'folder', itemicon: 'file'
	}
	container.collapsed = globalThis.NewState({})
	container.state.renderedList = []

	container.addEventListener('bind', e => {
		container.state.selectedItem = e.detail
	})

	const matchString = (val, query) => {
		if (query === undefined || query === null || query === '') return true
		return String(val || '').toLowerCase().includes(String(query).toLowerCase())
	}

	const matchesItem = (item, filter, filterFunc) => {
		if (filterFunc && !filterFunc(item)) return false
		if (filter === 0) filter = ''

		if (filter !== undefined && filter !== null && filter !== '') {
			if (typeof filter === 'object') {
				for (const [key, qVal] of Object.entries(filter)) {
					if (qVal !== undefined && qVal !== null && qVal !== '') {
						if (!matchString(item[key], qVal)) return false
					}
				}
			} else {
				const q = String(filter).trim()
				if (q) {
					const labelMatches = matchString(item[container.labelfield], q)
					const summaryMatches = matchString(item[container.summaryfield], q)
					if (!labelMatches && !summaryMatches) return false
				}
			}
		}
		return true
	}

	const updateFlatList = () => {
		globalThis.Util.updateDefaults(container, defaultSets)
		let list = container.state.list || []
		let filter = container.state.filter
		let order = container.state.order
		const filterFunc = container._filterFunc
		const orderFunc = container._orderFunc

		if (filter === 0) filter = ''
		if (order === 0) order = ''

		if (orderFunc) {
			list = [...list].sort(orderFunc)
		} else if (order) {
			if (typeof order === 'string') {
				const parts = order.trim().split(/\s+/)
				const field = parts[0]
				const isDesc = parts[1]?.toLowerCase() === 'desc' || field.startsWith('-')
				const cleanField = field.startsWith('-') ? field.slice(1) : field
				list = [...list].sort((a, b) => {
					const valA = a[cleanField]
					const valB = b[cleanField]
					if (valA === undefined || valA === null) return isDesc ? 1 : -1
					if (valB === undefined || valB === null) return isDesc ? -1 : 1
					if (typeof valA === 'string' && typeof valB === 'string') {
						return isDesc ? valB.localeCompare(valA) : valA.localeCompare(valB)
					}
					return isDesc ? (valB > valA ? 1 : -1) : (valA > valB ? 1 : -1)
				})
			}
		}

		const flatList = []
		if (container.mode === 'group') {
			const filteredList = list.filter(item => matchesItem(item, filter, filterFunc))
			const itemMap = {}
			filteredList.forEach(item => (itemMap[item[container.groupfield]] ??= []).push(item));
			(container.state.groups || []).forEach(group => {
				const items = itemMap[group[container.groupidfield]]
				let groupMatches = false
				if (filter !== undefined && filter !== null && filter !== '') {
					if (typeof filter === 'object') {
						groupMatches = true
						for (const [key, qVal] of Object.entries(filter)) {
							if (qVal !== undefined && qVal !== null && qVal !== '') {
								if (!matchString(group[key], qVal)) {
									groupMatches = false
									break
								}
							}
						}
					} else {
						const q = String(filter).trim()
						if (q) {
							groupMatches = matchString(group[container.grouplabelfield], q) ||
								matchString(group[container.groupsummaryfield], q)
						}
					}
				}

				if ((items && items.length > 0) || groupMatches || (!filter && !filterFunc)) {
					flatList.push({ type: 'group', ...group })
					if (items) items.forEach(item => flatList.push({ type: 'item', ...item }))
				}
			})
		} else if (container.mode === 'tree') {
			const itemMap = {}
			list.forEach(item => { itemMap[item[container.idfield]] = item })

			const matches = new Set()
			list.forEach(item => {
				if (matchesItem(item, filter, filterFunc)) {
					matches.add(item[container.idfield])
				}
			})

			const keep = new Set(matches)
			if (filter || filterFunc) {
				matches.forEach(id => {
					let curr = itemMap[id]
					while (curr) {
						const parentId = curr[container.parentfield]
						if (!parentId) break
						if (keep.has(parentId)) break
						keep.add(parentId)
						curr = itemMap[parentId]
					}
				})
			}

			const childrenMap = {}
			list.forEach(item => {
				if ((!filter && !filterFunc) || keep.has(item[container.idfield])) {
					const parentId = item[container.parentfield] || ''
						; (childrenMap[parentId] ??= []).push(item)
				}
			})

			const traverse = (items, level, parents) => items.forEach(item => {
				const id = item[container.idfield]
				const hasChildren = !!childrenMap[id]?.length
				const isCollapsed = container.collapsed[id]
				flatList.push({ type: 'item', ...item, _level: level, _hasChildren: hasChildren, _parents: parents })
				if (hasChildren && !isCollapsed) traverse(childrenMap[id], level + 1, [...parents, id])
			})
			traverse(childrenMap[''] || [], 0, [])
		} else {
			const filteredList = list.filter(item => matchesItem(item, filter, filterFunc))
			filteredList.forEach(item => flatList.push({ type: 'item', ...item }))
		}
		container.state.flatList = flatList
	}

	container.state.__watch('list', updateFlatList)
	container.state.__watch('filter', updateFlatList)
	container.state.__watch('order', updateFlatList)
	const vs = container.fast ? globalThis.VirtualScroll() : null

	let refreshing = false
	container.refresh = () => {
		if (!container.fast || refreshing) return
		refreshing = true
		try {
			const res = vs.calc(container, container.state.flatList)
			if (res) {
				if (padTopEl) padTopEl.style.height = `${res.prevHeight}px`
				if (padBottomEl) padBottomEl.style.height = `${res.postHeight}px`
				container.state.listStartIndex = res.listStartIndex
				container.state.renderedList = res.renderedList
			}
		} finally {
			setTimeout(() => { refreshing = false }, 0)
		}
	}

	container.onItemUpdate = (index, node) => { if (container.fast) vs.update(index + (container.state.listStartIndex || 0), node) }

	container.state.__watch('flatList', flatList => {
		if (container.fast) {
			if (padTopEl) padTopEl.style.height = '0px'
			if (padBottomEl) padBottomEl.style.height = '0px'
			container.state.listStartIndex = 0
			container.state.renderedList = vs.reset(flatList, container) || []
			setTimeout(() => { if (container.state.flatList === flatList) vs.init(flatList, container.refresh) })
		} else container.state.renderedList = flatList
	})

	container.selectItem = (item, index) => {
		if (container.hasAttribute('auto-select')) {
			container.state.selectedItem = container.state.selectedItem === item[container.idfield] ? null : item[container.idfield]
		}
		container.dispatchEvent(new CustomEvent('change', { detail: container.state.selectedItem, bubbles: false }))
		container.dispatchEvent(new CustomEvent('itemclick', { bubbles: false, detail: { item, index: index + (container.fast ? (container.state.listStartIndex || 0) : 0) } }))
	}
	container.selectGroup = (item, index) => {
		if (container.hasAttribute('auto-select-group')) container.state.selectedGroup = container.state.selectedGroup === item[container.groupidfield] ? null : item[container.groupidfield]
		container.dispatchEvent(new CustomEvent('groupclick', { bubbles: false, detail: { item, index } }))
	}
	container.toggleCollapse = (item) => { if (container.collapsible && item._hasChildren) { container.collapsed[item[container.idfield]] = !container.collapsed[item[container.idfield]]; updateFlatList(); } }

	Object.defineProperty(container, 'filterFunc', {
		get: () => container._filterFunc,
		set: v => { container._filterFunc = v; updateFlatList(); }
	})
	Object.defineProperty(container, 'orderFunc', {
		get: () => container._orderFunc,
		set: v => { container._orderFunc = v; updateFlatList(); }
	})

	updateFlatList()
}, globalThis.Util.makeDom(/*html*/`
<div class="list-group overflow-auto" onscroll="this.refresh()" style="overflow-anchor:none">
        <div class="vs-pad-top flex-shrink-0" style="height:0px;"></div>
        <template slot-id="item" $each="this.state.renderedList">
                <div $onupdate="this.onItemUpdate(index, thisNode)" $class="list-group-item d-inline-flex align-items-center pe-2 \${item.type==='group'?'bg-body-tertiary fw-bold ps-2':'list-group-item-action ' + (this.mode==='group'?'ps-4':'ps-2')} \${item.type==='group'?(this.state?.selectedGroup===item[this.groupidfield]?'active':''):(this.state?.selectedItem===item[this.idfield]?'active':'')}" $onclick="item.type==='group'?this.selectGroup(item,index):this.selectItem(item,index)">
                        <template $if="item.type === 'group'">
                                <template $if="this.groupicon">
                                        <span $class="bi bi-\${this.groupicon} text-body"></span>
                                </template>
                                <div class="flex-shrink-0 px-1" $text="\${item[this.grouplabelfield]}"></div>
                                <div class="text-muted small flex-fill text-end" $text="\${item[this.groupsummaryfield]}"></div>
                                <div slot-id="group-actions"></div>
                        </template>
                        <template $if="item.type === 'item'">
                                <template $if="this.mode === 'tree'">
                                        <div $style="width:\${item._level * 16 + (this.collapsible ? 16 : 0)}px; cursor:\${this.collapsible ? 'pointer' : 'default'}" class="text-end text-muted flex-shrink-0" $onclick="event.stopPropagation(); this.toggleCollapse(item)">
                                                <template $if="this.collapsible && item._hasChildren">
                                                        <i $class="bi \${this.collapsed[item[this.idfield]] ? 'bi-caret-right-fill' : 'bi-caret-down-fill'}"></i>
                                                </template>
                                        </div>
                                </template>
                                <template $if="this.mode === 'tree'">
                                        <span $class="text-muted bi bi-\${item._hasChildren ? this.groupicon : this.itemicon}"></span>
                                </template>
                                <template $if="this.mode !== 'tree' && this.itemicon">
                                        <span $class="bi bi-\${this.itemicon} text-body"></span>
                                </template>
                                <div class="flex-shrink-0 px-1" $text="\${item[this.labelfield]}"></div>
                                <div class="text-muted fs-7 small ms-2 flex-fill text-truncate text-end" $text="\${item[this.summaryfield]}"></div>
                                <div slot-id="item-actions"></div>
                        </template>
                </div>
        </template>
        <div class="vs-pad-bottom flex-shrink-0" style="height:0px;"></div>
</div>
`))
