import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom'

const DEFAULT_MAX_TAGS = 15
const DEFAULT_MIN_LENGTH = 2
const DEFAULT_MAX_LENGTH = 32
const DEBOUNCE_MS = 300
const MAX_SUGGESTIONS = 8

function normalizeTag(rawTag) {
  const raw = String(rawTag ?? '').trim().toLowerCase()
  if (!raw) return ''

  const noSpaces = raw.replace(/\s+/g, '-')
  const cleaned = noSpaces.replace(/[^a-z0-9_-]/g, '')
  const compact = cleaned.replace(/-+/g, '-').replace(/_+/g, '_').replace(/^[-_]+|[-_]+$/g, '')

  return compact.slice(0, DEFAULT_MAX_LENGTH)
}

function parseTagList(input) {
  if (Array.isArray(input)) return input
  if (typeof input !== 'string') return []

  return input
    .split(/[\n,]+/)
    .map((item) => item.trim())
    .filter(Boolean)
}

function analyzePastedTags(rawText, selectedTags, minLength, maxLength, maxTags) {
  const parts = parseTagList(rawText)
  const tagsToAdd = []
  const skippedDuplicates = []
  const skippedInvalid = []
  const skippedOverflow = []

  for (const part of parts) {
    const normalized = normalizeTag(part)

    if (!normalized) {
      skippedInvalid.push(String(part ?? '').trim())
      continue
    }

    if (selectedTags.includes(normalized) || tagsToAdd.includes(normalized)) {
      skippedDuplicates.push(normalized)
      continue
    }

    if (normalized.length < minLength || normalized.length > maxLength || !/^[a-z0-9_-]+$/.test(normalized)) {
      skippedInvalid.push(String(part ?? '').trim())
      continue
    }

    if (selectedTags.length + tagsToAdd.length >= maxTags) {
      skippedOverflow.push(normalized)
      continue
    }

    tagsToAdd.push(normalized)
  }

  return {
    parsedCount: parts.length,
    tagsToAdd,
    nextTags: [...selectedTags, ...tagsToAdd],
    skippedDuplicates,
    skippedInvalid,
    skippedOverflow,
  }
}

function validateTag(tag, selectedTags, minLength, maxLength, maxTags) {
  if (selectedTags.length >= maxTags) return 'Max tags reached'
  if (tag.length < minLength) return 'Tag too short'
  if (tag.length > maxLength) return 'Tag too long'
  if (!/^[a-z0-9_-]+$/.test(tag)) return 'Invalid tag format'
  if (selectedTags.includes(tag)) return 'Duplicate tag'
  return null
}

function toSuggestionItems(raw) {
  if (!Array.isArray(raw)) return []

  return raw
    .map((item) => {
      if (typeof item === 'string') {
        return { key: item, label: item, tag: item, usageCount: null, isAi: false }
      }

      const tag = item?.slug || item?.name || item?.tag || ''
      if (!tag) return null

      return {
        key: item?.id ?? tag,
        label: item?.name || item?.tag || item?.slug || tag,
        tag,
        usageCount: Number.isFinite(Number(item?.usage_count)) ? Number(item.usage_count) : null,
        recentClicks: Number.isFinite(Number(item?.recent_clicks)) ? Number(item.recent_clicks) : 0,
        isAi: Boolean(item?.is_ai || item?.source === 'ai'),
      }
    })
    .filter(Boolean)
}

function TagPillList({ tags, onRemove, disabled }) {
  return (
    <div className="min-h-[3rem] rounded-xl border border-white/10 bg-white/5 p-2">
      <div className="flex flex-wrap gap-2">
        {tags.length === 0 && (
          <span className="px-2 py-1 text-xs text-white/50">No tags selected</span>
        )}

        {tags.map((tag) => (
          <span
            key={tag}
            className="group inline-flex max-w-full items-center gap-2 rounded-full border border-white/20 bg-slate-900/80 px-3 py-1.5 text-xs text-slate-100 transition-all duration-150 ease-in-out"
            title={tag}
          >
            <span className="truncate">{tag}</span>
            <button
              type="button"
              onClick={() => onRemove(tag)}
              disabled={disabled}
              className="rounded-full p-0.5 text-slate-300 transition-colors duration-150 ease-in-out hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-50"
              aria-label={`Remove tag ${tag}`}
            >
              ✕
            </button>
          </span>
        ))}
      </div>
    </div>
  )
}

function SearchInput({
  inputValue,
  onInputChange,
  onKeyDown,
  onPaste,
  onFocus,
  disabled,
  expanded,
  listboxId,
  placeholder,
}) {
  return (
    <input
      value={inputValue}
      onChange={(event) => onInputChange(event.target.value)}
      onKeyDown={onKeyDown}
      onPaste={onPaste}
      onFocus={onFocus}
      disabled={disabled}
      type="text"
      role="combobox"
      className="w-full rounded-xl border border-white/10 bg-white/10 px-3 py-2 text-sm text-white placeholder:text-white/45 focus:border-sky-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-60"
      placeholder={placeholder}
      aria-label="Tag input"
      aria-autocomplete="list"
      aria-haspopup="listbox"
      aria-expanded={expanded}
      aria-controls={listboxId}
    />
  )
}

function HighlightedMatch({ label, query }) {
  if (!query) return <>{label}</>

  const lowerLabel = label.toLowerCase()
  const lowerQuery = query.toLowerCase()
  const start = lowerLabel.indexOf(lowerQuery)

  if (start === -1) return <>{label}</>

  const end = start + query.length
  const before = label.slice(0, start)
  const match = label.slice(start, end)
  const after = label.slice(end)

  return (
    <>
      {before}
      <span className="font-semibold text-sky-300">{match}</span>
      {after}
    </>
  )
}

function SuggestionDropdown({
  isOpen,
  loading,
  error,
  suggestions,
  highlightedIndex,
  onSelect,
  query,
  listboxId,
}) {
  if (!isOpen) return null

  return (
    <div className="overflow-hidden rounded-xl ring-1 ring-white/10 bg-slate-950/98 shadow-xl shadow-black/50">
      <ul id={listboxId} role="listbox" className="max-h-56 overflow-auto py-1">
        {loading && (
          <li className="px-3 py-2 text-xs text-white/60">Searching tags…</li>
        )}

        {!loading && error && (
          <li className="px-3 py-2 text-xs text-amber-200">Tag search unavailable</li>
        )}

        {!loading && !error && suggestions.length === 0 && (
          <li className="px-3 py-2 text-xs text-white/50">No suggestions</li>
        )}

        {!loading && !error && suggestions.map((item, index) => {
          const active = highlightedIndex === index
          const detailLabel = item.recentClicks > 0
            ? `${item.recentClicks.toLocaleString()} recent`
            : (typeof item.usageCount === 'number' ? `${item.usageCount.toLocaleString()} uses` : null)
          return (
            <li
              key={item.key}
              role="option"
              aria-selected={active}
              className={`flex cursor-pointer items-center justify-between gap-2 px-3 py-2 text-sm transition-colors duration-150 ease-in-out ${active ? 'bg-sky-500/20 text-white' : 'text-white/85 hover:bg-white/10'}`}
              onMouseDown={(event) => {
                event.preventDefault()
                onSelect(item.tag)
              }}
            >
              <div className="min-w-0">
                <span className="truncate"><HighlightedMatch label={item.label} query={query} /></span>
                {item.isAi && (
                  <span className="ml-2 rounded-full border border-purple-400/40 bg-purple-400/10 px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-purple-200">
                    AI
                  </span>
                )}
              </div>
              {detailLabel && (
                <span className="shrink-0 text-[11px] text-white/50">{detailLabel}</span>
              )}
            </li>
          )
        })}
      </ul>
    </div>
  )
}

function SuggestedTagsPanel({ items, selectedTags, onAdd, disabled }) {
  const filtered = useMemo(
    () => items.filter((item) => !selectedTags.includes(normalizeTag(item.tag))),
    [items, selectedTags]
  )

  if (filtered.length === 0) return null

  return (
    <div className="rounded-xl border border-purple-400/20 bg-purple-500/5 p-3">
      <div className="mb-2 text-xs uppercase tracking-wide text-purple-200">AI Suggested tags</div>
      <div className="flex flex-wrap gap-2">
        {filtered.map((item) => (
          <button
            key={item.key}
            type="button"
            onClick={() => onAdd(item.tag)}
            disabled={disabled}
            className="inline-flex items-center gap-1 rounded-full border border-purple-300/40 bg-purple-400/10 px-3 py-1.5 text-xs text-purple-100 transition-all duration-150 ease-in-out hover:bg-purple-400/20 disabled:cursor-not-allowed disabled:opacity-60"
          >
            {item.label}
            <span className="text-[11px]">+</span>
          </button>
        ))}
      </div>
    </div>
  )
}

function StatusHints({ error, count, maxTags }) {
  return (
    <div className="flex items-center justify-between gap-3 text-xs">
      <span className={error ? 'text-amber-200' : 'text-white/55'} role="status" aria-live="polite">
        {error || 'Type and press Enter, comma, or Tab to add. Paste a comma-separated list to review multiple tags.'}
      </span>
      <span className="text-white/50">{count}/{maxTags}</span>
    </div>
  )
}

function PastedTagsDialog({ open, preview, maxTags, onClose, onConfirm }) {
  const backdropRef = useRef(null)
  const cancelButtonRef = useRef(null)

  useEffect(() => {
    if (!open) return undefined
    const timeoutId = window.setTimeout(() => cancelButtonRef.current?.focus(), 60)
    return () => window.clearTimeout(timeoutId)
  }, [open])

  useEffect(() => {
    if (!open) return undefined

    const handleKeyDown = (event) => {
      if (event.key === 'Escape') {
        onClose?.()
      }
    }

    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [onClose, open])

  if (!open || !preview) return null

  const duplicateCount = preview.skippedDuplicates.length
  const invalidCount = preview.skippedInvalid.length
  const overflowCount = preview.skippedOverflow.length

  return createPortal(
    <div
      ref={backdropRef}
      className="fixed inset-0 z-[9999] flex items-center justify-center bg-[#04070dcc] px-4 backdrop-blur-md"
      onClick={(event) => {
        if (event.target === backdropRef.current) {
          onClose?.()
        }
      }}
      role="presentation"
    >
      <div
        role="dialog"
        aria-modal="true"
        aria-labelledby="tag-input-paste-title"
        className="w-full max-w-lg overflow-hidden rounded-3xl border border-white/10 bg-[linear-gradient(180deg,rgba(16,22,34,0.98),rgba(8,12,19,0.98))] shadow-[0_30px_80px_rgba(0,0,0,0.55)]"
      >
        <div className="border-b border-white/[0.06] bg-white/[0.02] px-6 py-5">
          <p className="text-[11px] font-semibold uppercase tracking-[0.24em] text-white/35">Tag Import</p>
          <h3 id="tag-input-paste-title" className="mt-2 text-lg font-semibold text-white">
            Add {preview.tagsToAdd.length} pasted tag{preview.tagsToAdd.length === 1 ? '' : 's'}?
          </h3>
          <p className="mt-2 text-sm leading-6 text-white/65">
            Parsed {preview.parsedCount} item{preview.parsedCount === 1 ? '' : 's'} from your paste. Confirm before adding them to the artwork.
          </p>
        </div>

        <div className="space-y-4 px-6 py-5">
          {overflowCount > 0 && (
            <div className="rounded-2xl border border-amber-300/25 bg-amber-400/10 px-4 py-3 text-sm text-amber-100">
              Only {preview.tagsToAdd.length} tag{preview.tagsToAdd.length === 1 ? '' : 's'} can be added because the limit is {maxTags}. {overflowCount} pasted tag{overflowCount === 1 ? '' : 's'} will be skipped.
            </div>
          )}

          {(duplicateCount > 0 || invalidCount > 0) && (
            <div className="rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-white/70">
              {duplicateCount > 0 ? `${duplicateCount} duplicate tag${duplicateCount === 1 ? '' : 's'} ignored.` : ''}
              {duplicateCount > 0 && invalidCount > 0 ? ' ' : ''}
              {invalidCount > 0 ? `${invalidCount} invalid tag${invalidCount === 1 ? '' : 's'} ignored.` : ''}
            </div>
          )}

          <div>
            <p className="mb-3 text-xs font-semibold uppercase tracking-[0.18em] text-white/40">Tags to add</p>
            <div className="max-h-56 overflow-auto rounded-2xl border border-white/10 bg-white/[0.03] p-3">
              <div className="flex flex-wrap gap-2">
                {preview.tagsToAdd.map((tag) => (
                  <span
                    key={tag}
                    className="inline-flex items-center rounded-full border border-sky-300/25 bg-sky-400/10 px-3 py-1.5 text-xs font-medium text-sky-100"
                  >
                    {tag}
                  </span>
                ))}
              </div>
            </div>
          </div>
        </div>

        <div className="flex items-center justify-end gap-3 border-t border-white/[0.06] px-6 py-4">
          <button
            ref={cancelButtonRef}
            type="button"
            onClick={() => onClose?.()}
            className="inline-flex items-center justify-center rounded-full border border-white/[0.08] bg-white/[0.04] px-4 py-2 text-sm font-medium text-white/70 transition hover:bg-white/[0.08] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/20"
          >
            Cancel
          </button>
          <button
            type="button"
            onClick={() => onConfirm?.()}
            className="inline-flex items-center justify-center rounded-full border border-sky-300/25 bg-sky-400/90 px-4 py-2 text-sm font-semibold text-slate-950 transition hover:brightness-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/40"
          >
            Add tags
          </button>
        </div>
      </div>
    </div>,
    document.body,
  )
}

export default function TagInput({
  value,
  onChange,
  suggestedTags = [],
  disabled = false,
  maxTags = DEFAULT_MAX_TAGS,
  minLength = DEFAULT_MIN_LENGTH,
  maxLength = DEFAULT_MAX_LENGTH,
  placeholder = 'Type tags…',
  searchEndpoint = '/api/tags/search',
  popularEndpoint = '/api/tags/popular',
}) {
  const selectedTags = useMemo(() => parseTagList(value).map(normalizeTag).filter(Boolean), [value])

  const [inputValue, setInputValue] = useState('')
  const [suggestions, setSuggestions] = useState([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  const [searchError, setSearchError] = useState(false)
  const [isOpen, setIsOpen] = useState(false)
  const [highlightedIndex, setHighlightedIndex] = useState(-1)
  const [pastePreview, setPastePreview] = useState(null)

  const queryCacheRef = useRef(new Map())
  const abortControllerRef = useRef(null)
  const debounceTimerRef = useRef(null)
  const hasFocusedRef = useRef(false)

  const listboxId = useMemo(() => `tag-input-listbox-${Math.random().toString(16).slice(2)}`, [])
  const aiSuggestedItems = useMemo(() => toSuggestionItems(suggestedTags), [suggestedTags])

  const updateTags = useCallback((nextTags) => {
    const unique = Array.from(new Set(nextTags.map(normalizeTag).filter(Boolean)))
    onChange(unique)
  }, [onChange])

  const addTag = useCallback((rawTag) => {
    const normalized = normalizeTag(rawTag)
    const validation = validateTag(normalized, selectedTags, minLength, maxLength, maxTags)
    if (validation) {
      setError(validation)
      return false
    }

    setError('')
    updateTags([...selectedTags, normalized])
    setInputValue('')
    setIsOpen(false)
    setHighlightedIndex(-1)
    return true
  }, [selectedTags, minLength, maxLength, maxTags, updateTags])

  const removeTag = useCallback((tagToRemove) => {
    setError('')
    updateTags(selectedTags.filter((tag) => tag !== tagToRemove))
  }, [selectedTags, updateTags])

  const applyPastedTags = useCallback((rawText) => {
    const preview = analyzePastedTags(rawText, selectedTags, minLength, maxLength, maxTags)

    if (preview.parsedCount === 0) return false

    if (preview.tagsToAdd.length === 0) {
      if (selectedTags.length >= maxTags || preview.skippedOverflow.length > 0) {
        setError('Max tags reached')
      } else {
        setError('No new tags found in pasted text')
      }
      return false
    }

    setError('')
    setIsOpen(false)
    setHighlightedIndex(-1)
    setPastePreview(preview)
    return true
  }, [selectedTags, minLength, maxLength, maxTags])

  const closePastePreview = useCallback(() => {
    setPastePreview(null)
  }, [])

  const confirmPastePreview = useCallback(() => {
    if (!pastePreview) return

    updateTags(pastePreview.nextTags)
    setInputValue('')
    setError('')
    setPastePreview(null)
  }, [pastePreview, updateTags])

  const runSearch = useCallback(async (query) => {
    const normalizedQuery = normalizeTag(query)

    if (abortControllerRef.current) {
      abortControllerRef.current.abort()
    }

    const cacheKey = normalizedQuery || '__popular__'
    if (queryCacheRef.current.has(cacheKey)) {
      const cached = queryCacheRef.current.get(cacheKey)
      setSuggestions(cached)
      setSearchError(false)
      setIsOpen(true)
      return
    }

    const controller = new AbortController()
    abortControllerRef.current = controller

    setLoading(true)
    setSearchError(false)

    try {
      const url = normalizedQuery
        ? `${searchEndpoint}?q=${encodeURIComponent(normalizedQuery)}`
        : popularEndpoint

      const res = await window.axios.get(url, { signal: controller.signal })
      const items = toSuggestionItems(res?.data?.data || [])
        .filter((item) => !selectedTags.includes(normalizeTag(item.tag)))
        .slice(0, MAX_SUGGESTIONS)

      queryCacheRef.current.set(cacheKey, items)
      setSuggestions(items)
      setHighlightedIndex(items.length > 0 ? 0 : -1)
      setIsOpen(true)
    } catch (requestError) {
      if (controller.signal.aborted) return
      setSuggestions([])
      setSearchError(true)
      setIsOpen(true)
    } finally {
      if (!controller.signal.aborted) {
        setLoading(false)
      }
    }
  }, [searchEndpoint, popularEndpoint, selectedTags])

  useEffect(() => {
    // Don't fire on initial mount — wait for the user to focus or type.
    if (!hasFocusedRef.current) return

    const query = inputValue.trim()

    if (debounceTimerRef.current) {
      window.clearTimeout(debounceTimerRef.current)
    }

    debounceTimerRef.current = window.setTimeout(() => {
      runSearch(query)
    }, DEBOUNCE_MS)

    return () => {
      if (debounceTimerRef.current) {
        window.clearTimeout(debounceTimerRef.current)
      }
    }
  }, [inputValue, runSearch])

  useEffect(() => {
    return () => {
      if (abortControllerRef.current) {
        abortControllerRef.current.abort()
      }
    }
  }, [])

  const handleInputKeyDown = useCallback((event) => {
    if (event.key === 'Escape') {
      setIsOpen(false)
      setHighlightedIndex(-1)
      return
    }

    if (event.key === 'Backspace' && inputValue.length === 0 && selectedTags.length > 0) {
      removeTag(selectedTags[selectedTags.length - 1])
      return
    }

    if (event.key === 'ArrowDown') {
      event.preventDefault()
      if (!isOpen || suggestions.length === 0) return
      setHighlightedIndex((prev) => {
        if (prev < 0) return 0
        return Math.min(prev + 1, suggestions.length - 1)
      })
      return
    }

    if (event.key === 'ArrowUp') {
      event.preventDefault()
      if (!isOpen || suggestions.length === 0) return
      setHighlightedIndex((prev) => {
        if (prev <= 0) return 0
        return prev - 1
      })
      return
    }

    const shouldCommit = event.key === 'Enter' || event.key === ',' || event.key === 'Tab'
    if (!shouldCommit) return

    if (event.key === 'Tab' && !isOpen && inputValue.trim() === '') {
      return
    }

    event.preventDefault()

    if ((event.key === 'Enter' || event.key === 'Tab') && isOpen && highlightedIndex >= 0 && suggestions[highlightedIndex]) {
      addTag(suggestions[highlightedIndex].tag)
      return
    }

    const candidate = inputValue.trim().replace(/,$/, '')
    if (!candidate) return
    addTag(candidate)
  }, [inputValue, selectedTags, removeTag, isOpen, suggestions, highlightedIndex, addTag])

  const handlePaste = useCallback((event) => {
    const raw = event.clipboardData?.getData('text')
    if (!raw) return

    const parts = parseTagList(raw)
    if (parts.length <= 1) return

    event.preventDefault()
    applyPastedTags(raw)
  }, [applyPastedTags])

  const handleFocus = useCallback(() => {
    hasFocusedRef.current = true
    if (inputValue.trim() !== '') return
    runSearch('')
  }, [inputValue, runSearch])

  return (
    <>
      <div className="flex h-full flex-col gap-3" data-testid="tag-input-root">
        <TagPillList tags={selectedTags} onRemove={removeTag} disabled={disabled} />

        <SearchInput
          inputValue={inputValue}
          onInputChange={(next) => {
            setInputValue(next)
            setError('')
          }}
          onKeyDown={handleInputKeyDown}
          onPaste={handlePaste}
          onFocus={handleFocus}
          disabled={disabled}
          expanded={isOpen}
          listboxId={listboxId}
          placeholder={placeholder}
        />

        <SuggestionDropdown
          isOpen={isOpen}
          loading={loading}
          error={searchError}
          suggestions={suggestions}
          highlightedIndex={highlightedIndex}
          onSelect={addTag}
          query={inputValue.trim()}
          listboxId={listboxId}
        />

        <SuggestedTagsPanel
          items={aiSuggestedItems}
          selectedTags={selectedTags}
          onAdd={addTag}
          disabled={disabled}
        />

        <StatusHints error={error} count={selectedTags.length} maxTags={maxTags} />
      </div>

      <PastedTagsDialog
        open={Boolean(pastePreview)}
        preview={pastePreview}
        maxTags={maxTags}
        onClose={closePastePreview}
        onConfirm={confirmPastePreview}
      />
    </>
  )
}
