import { type KeyboardEvent, forwardRef, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { formatTagInput, formatTagTextContent, formatTagUsage } from '../../functions/Format'
import type { BackendTag } from '../../functions/BackendTypes'
import { BackendFetch } from '../../functions/Backend'
import InputLabel from './Label'
import './styles/Label.css'
import './styles/Tags.css'

const SEARCH_LIMIT = 5
const SEARCH_CACHE = new Map<string, BackendTag[]>()

export interface HandleForInputTags {
    getValue: () => BackendTag[]
}

interface PropsForInputTags {
    label: string
    allowCustom: boolean
    onChange: ((tags: BackendTag[]) => void) | undefined
}

const InputTags = forwardRef<HandleForInputTags, PropsForInputTags>(({ label, onChange, allowCustom }, ref) => {
    const componentID = useId()
    const timeoutRef = useRef<ReturnType<typeof setTimeout>>(null)
    const inputRef = useRef<HTMLInputElement>(null)

    const [tagsSelected, setTagsSelected] = useState<BackendTag[]>([])
    const [tagsAvailable, setTagsAvailable] = useState<BackendTag[]>([])
    const [inputSelect, setInputSelect] = useState(0)
    const [inputQuery, setInputQuery] = useState('')

    const indexHighlight = useMemo(
        () => ((inputSelect % tagsAvailable.length) + tagsAvailable.length) % tagsAvailable.length,
        [inputSelect],
    )

    useImperativeHandle(ref, () => ({ getValue: () => tagsSelected }))

    useEffect(() => {
        // Cleanup State
        if (timeoutRef.current) {
            clearTimeout(timeoutRef.current)
        }
        if (inputQuery.length === 0) {
            setTagsAvailable([])
            return
        }

        // Small debounce window before search begins
        timeoutRef.current = setTimeout(async () => {
            // Pull Tags from Cache
            const query = formatTagInput(inputQuery)
            if (!query) return

            if (SEARCH_CACHE.has(query)) {
                setInputSelect(0)
                setTagsAvailable(selectDedupe(SEARCH_CACHE.get(query) ?? []))
                return
            }

            // Pull Tags from API
            const resp = await BackendFetch<BackendTag[]>(
                `/tags/autocomplete?limit=${SEARCH_LIMIT - (allowCustom ? 1 : 0)}&query=${query}`,
            )
            if (!resp.success) {
                console.error('Autocomplete error:', resp)
                return
            }

            // Store Results
            if (resp.json.length) {
                SEARCH_CACHE.set(query, resp.json)
            }
            if (allowCustom && !resp.json.find((t) => t.label == query)) {
                resp.json.unshift({ id: 'CUSTOM', label: query, usage: 0 })
            }

            setTagsAvailable(selectDedupe(resp.json))
            setInputSelect(0)
        }, 200)
    }, [inputQuery])

    // Append a tag to the currently selected
    function selectAppendTag(tag: BackendTag) {
        const next = [...tagsSelected, tag]
        setTagsSelected(next)
        setTagsAvailable([])
        setInputQuery('')
        onChange?.(next)
    }
    // Remove a tag from the currently selected
    function selectRemoveTag(tag: BackendTag) {
        const next = tagsSelected.filter((t) => t.id !== tag.id)
        setTagsSelected(next)
        onChange?.(next)
    }

    // Remove currently selected tags from the available list
    function selectDedupe(list: BackendTag[]) {
        return list.filter((t) => !tagsSelected.some((s) => s.label === t.label))
    }

    function handleInputKeyDown(ev: KeyboardEvent<HTMLInputElement>) {
        // Remove latest tag
        if (ev.code === 'Backspace' && !inputQuery.length) {
            ev.preventDefault()
            const last = tagsSelected.at(-1)
            if (last) selectRemoveTag(last)
            return
        }

        // Append selected tag
        if (ev.code === 'Enter' && tagsAvailable.length && !Number.isNaN(indexHighlight)) {
            ev.preventDefault()
            selectAppendTag(tagsAvailable[indexHighlight])
            return
        }
        if (ev.code === 'Space' && inputQuery.at(-1) === ' ' && tagsAvailable.length && !Number.isNaN(indexHighlight)) {
            ev.preventDefault()
            selectAppendTag(tagsAvailable[indexHighlight])
            return
        }

        // Move Highlight
        if (ev.code === 'ArrowUp') {
            ev.preventDefault()
            setInputSelect(inputSelect - 1)
            return
        }
        if (ev.code === 'ArrowDown') {
            ev.preventDefault()
            setInputSelect(inputSelect + 1)
            return
        }
    }

    return (
        <>
            {label && <InputLabel for={componentID} label={label} />}
            <div className="input-tags" onClick={() => inputRef.current?.focus()}>
                {/* Tag Search */}
                <div className="search">
                    {tagsSelected.map((tag) => (
                        <button className="item" onClick={() => selectRemoveTag(tag)}>
                            {formatTagTextContent(tag.label)}
                        </button>
                    ))}
                    <input
                        id={componentID}
                        ref={inputRef}
                        type="text"
                        className="query"
                        placeholder={tagsSelected.length ? '' : 'Search'}
                        onKeyDown={handleInputKeyDown}
                        onChange={(e) => setInputQuery(e.currentTarget.value)}
                        value={inputQuery}
                    />
                </div>

                {/* Tag Results */}
                <div className="results">
                    {tagsAvailable.map((tag, i) => {
                        const classSelect = i === indexHighlight ? 'select' : ''
                        const classCustom = tag.id === 'CUSTOM' ? 'custom' : ''
                        return (
                            <>
                                <button
                                    className={`item ${classSelect} ${classCustom}`}
                                    onClick={() => selectAppendTag(tag)}>
                                    <span className="label animation-scroll-in">{formatTagTextContent(tag.label)}</span>
                                    <span className="usage animation-fade-in">
                                        {classCustom ? '<CREATE>' : formatTagUsage(tag.usage)}
                                    </span>
                                </button>
                            </>
                        )
                    })}
                </div>
            </div>
        </>
    )
})

export default InputTags
