import * as React from 'react'
import cx from 'classnames'

import {
	useEffect,
	useState,
	useRef,
} from '@wordpress/element'

import {
	Button,
	Icon,
	TextHighlight,
	Popover,
} from '@wordpress/components'

import {
	plus as plusIcon,
	search as searchIcon,
} from '@wordpress/icons'

import {
	FixedSizeList as List,
	// @ts-expect-error
} from 'react-window'

import {
	ActionInput,
	ActionInputProps,
} from '@ska/components'

import './style.scss'

export interface SearchResult {
	id: string | number
	title: string
	content: string
	[key: string]: any
}

interface ResultsListProps {
	searchTerm: string
	results: SearchResult[]
	onSelect: SearchProps['onSelect']
	focusSearch: () => void
	getResultTitle: (result: SearchResult) => string
	getResultContent: (result: SearchResult) => string
	getResultIcon: (result: SearchResult) => React.JSX.Element | null
	virtualized?: boolean
}

export interface SearchProps {
	className?: string
	onSelect: (result: SearchResult) => void
	onPaste?: (e: React.ClipboardEvent<HTMLInputElement>) => void
	getResults?: (search: string) => SearchResult[]
	fetchResults?: (search: string) => Promise<SearchResult[]>
	actionInputProps?: Partial<ActionInputProps>
	getResultTitle?: ResultsListProps['getResultTitle']
	getResultContent?: ResultsListProps['getResultContent']
	getResultIcon?: ResultsListProps['getResultIcon']
	virtualized?: boolean
}

const Search: React.FC<SearchProps> = ({
	className,
	actionInputProps,
	getResults,
	fetchResults,
	onSelect,
	onPaste,
	getResultTitle = ({title}) => title,
	getResultContent = ({content}) => content,
	getResultIcon = () => plusIcon,
	virtualized = false,
}) => {

	const [searchTerm, setSearchTerm] = useState('')
	const [lastSearch, setLastSearch] = useState('')
	const [results, setResults] = useState<SearchResult[]>([])
	const [resultsVisible, setResultsVisible] = useState(false)
	const searchRef = useRef<HTMLDivElement>(null)
	const resultsRef = useRef<HTMLDivElement>(null)

	const getRef = useRef(getResults)
	const fetchRef = useRef(fetchResults)

	useEffect(() => {
		if(!searchTerm) {
			setResults([])
			return
		}
		if(getRef.current) {
			setResults(getRef.current(searchTerm))
			setResultsVisible(true)
		}
		if(fetchRef.current) {
			fetchRef.current(searchTerm).then(setResults)
		}
	}, [searchTerm])

	const focusFirstResult = () => {
		if(resultsRef.current?.querySelector('button')) {
			resultsRef.current.querySelector('button')?.focus()
			return true
		}
		return false
	}

	return (
		<div
			className={cx('ska-search', className)}
			ref={searchRef}
		>
			<ActionInput
				hideLabelFromVision
				icon={searchIcon}
				value={searchTerm}
				onChange={setSearchTerm}
				onPaste={onPaste}
				onClear={() => setSearchTerm('')}
				onSubmit={() => {
					setLastSearch(searchTerm)
					if(results[0]) {
						onSelect(results[0])
						setSearchTerm('')
					}
				}}
				onFocus={() => {
					if(searchTerm && !resultsVisible) {
						setResultsVisible(true)
					}
				}}
				onKeyDown={(e: React.KeyboardEvent<HTMLFormElement>) => {

					if(e.key === 'ArrowUp' && !searchTerm && lastSearch) {
						setSearchTerm(lastSearch)
						return
					}

					switch(e.key) {
						case 'Escape':
							if(results.length) {
								setResultsVisible(false)
							} else if(searchTerm) {
								setLastSearch(searchTerm)
								setSearchTerm('')
							}
							e.preventDefault()
						break

						case 'ArrowUp':
						case 'ArrowDown':
							if(focusFirstResult()) {
								e.preventDefault()
							}
						break

						case 'Home':
						case 'End':
							e.stopPropagation()
						break

						case 'z':
							if(e.ctrlKey) {
								if(lastSearch) {
									setSearchTerm(lastSearch)
								}
								e.preventDefault()
								e.stopPropagation() // Prevents whole block editor doing an undo
							}
						break
					}
				}}
				{...actionInputProps}
				inputProps={{
					autoComplete: 'off',
					role: 'searchbox', // type=search doesn't have same styles as type=text
					...actionInputProps.inputProps,
				}}
			/>
			{results.length > 0 && resultsVisible && <>
				<Popover
					placement='bottom-start'
					focusOnMount={false}
					offset={8}
					ref={resultsRef}
					className={cx('ska-search', className)}
				>
					<ResultsList
						searchTerm={searchTerm}
						results={results}
						onSelect={result => {
							setSearchTerm('')
							onSelect(result)
						}}
						focusSearch={() => {
							(searchRef?.current?.querySelector('input[type="text"]') as HTMLInputElement)?.focus()
						}}
						getResultTitle={getResultTitle}
						getResultContent={getResultContent}
						getResultIcon={getResultIcon}
						virtualized={virtualized}
					/>
				</Popover>
			</>}
		</div>
	)
}

const focusResult = (ref: {current: HTMLDivElement | null}, direction: number = 1) => {

	const sibling = direction > 0 ? 'nextElementSibling' : 'previousElementSibling'

	if(
		ref
		&& ref.current
		&& ref.current.ownerDocument.activeElement
		&& ref.current.ownerDocument.activeElement.matches('.ska-search__results__result')
		&& ref.current.ownerDocument.activeElement[sibling]
		&& ref.current.ownerDocument.activeElement[sibling]?.matches('.ska-search__results__result')
	) {
		(ref.current.ownerDocument.activeElement[sibling] as HTMLButtonElement).focus()
		return true
	}

	return false
}

const ResultsList: React.FC<ResultsListProps> = ({
	searchTerm,
	results,
	onSelect,
	focusSearch,
	getResultTitle,
	getResultContent,
	getResultIcon,
	virtualized = false,
}) => {

	const resultsContainerRef = useRef<HTMLDivElement>(null)

	if(!results.length) {
		return null
	}

	const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
		switch(e.key) {
			case 'Escape':
				focusSearch()
				e.preventDefault()
			break
			case 'ArrowUp':
				if(!focusResult(resultsContainerRef, -1)) {
					focusSearch()
				}
				e.preventDefault()
			break
			case 'ArrowDown':
				if(!focusResult(resultsContainerRef, 1)) {
					focusSearch()
				}
				e.preventDefault()
			break
		}
	}

	const renderResult = (result: SearchResult, props: any = {}) => {

		const icon = getResultIcon(result)
		const title = getResultTitle(result)
		const content = getResultContent(result)

		return (
			<Button
				key={result.id}
				className='ska-search__results__result'
				onClick={() => {
					onSelect(result)
				}}
				{...props}
			>
				{icon && (
					<div className='ska-search__results__result-icon'>
						{<Icon icon={icon} />}
					</div>
				)}
				<div className='ska-search__results__result__inner'>
					{title && (
						<div className='ska-search__results__result__inner-title'>
							<TextHighlight text={title} highlight={searchTerm} />
						</div>
					)}
					{content && (
						<div className='ska-search__results__result__inner-content'>
							<TextHighlight text={content} highlight={searchTerm} />
						</div>
					)}
				</div>
			</Button>
		)
	}

	if(virtualized) {

		const VirtualizedRow = ({index, style}) => {
			const result = results[index]
			return renderResult(result, {style})
		}

		return (
			<List
				className='ska-search__results'
				ref={resultsContainerRef}
				onKeyDown={onKeyDown}
				itemCount={results.length}
				width={246}
				height={225}
				itemSize={45}
			>
				{VirtualizedRow}
			</List>
		)
	}

	return (
		// eslint-disable-next-line jsx-a11y/no-static-element-interactions
		<div
			className='ska-search__results'
			ref={resultsContainerRef}
			onKeyDown={onKeyDown}
		>
			{results.map(result => renderResult(result))}
		</div>
	)
}

export default Search
