'use client'

/**
 * Chip-style tag editor with autocomplete from values already in use.
 *
 * Suggestions come from `GET /collections/:collection/field-values?field=...`
 * (distinct values across the collection, most-used first) and are fetched
 * once on first focus, then filtered client-side as the user types.
 *
 * Deduplication is case-insensitive and resolves to the existing canonical
 * casing: typing "ai" when the collection already uses "AI" adds "AI", so a
 * vocabulary never splinters into `AI` / `ai` / `Ai` variants.
 */
import { useCallback, useEffect, useRef, useState } from 'react'
import { X } from 'lucide-react'
import { cmsApi } from '../lib/api.js'

interface TagInputProps {
  id: string
  value: string[]
  onChange: (tags: string[]) => void
  /** Collection slug used to fetch suggestions. Omit to disable autocomplete. */
  collection?: string
  /** Field to aggregate for suggestions (default `tags`). */
  field?: string
  disabled?: boolean
  placeholder?: string
  /** Hard cap on the number of tags (mirrors the field's `maxRows`). */
  maxTags?: number
}

interface FieldValuesResponse {
  field: string
  values: Array<{ value: string; count: number }>
}

const INPUT =
  'border-input bg-input-background text-foreground focus-visible:ring-ring w-full rounded-md border px-2.5 py-1.5 text-sm focus-visible:ring-2 focus-visible:outline-none'

export function TagInput({
  id,
  value,
  onChange,
  collection,
  field = 'tags',
  disabled,
  placeholder,
  maxTags,
}: TagInputProps) {
  const [draft, setDraft] = useState('')
  const [known, setKnown] = useState<string[] | null>(null)
  const [open, setOpen] = useState(false)
  const [activeIndex, setActiveIndex] = useState(-1)
  const rootRef = useRef<HTMLDivElement>(null)

  const atCap = typeof maxTags === 'number' && value.length >= maxTags

  // Fetch the in-use vocabulary once, on first focus.
  const ensureSuggestions = useCallback(() => {
    if (known !== null || !collection) return
    setKnown([]) // sentinel: fetch in flight, don't refetch
    cmsApi<FieldValuesResponse>(
      `/collections/${encodeURIComponent(collection)}/field-values?field=${encodeURIComponent(field)}`,
    ).then((res) => {
      if (res.data?.values) setKnown(res.data.values.map((v) => v.value))
    })
  }, [known, collection, field])

  // Close the suggestion list on outside click.
  useEffect(() => {
    if (!open) return
    function onPointerDown(e: PointerEvent) {
      if (rootRef.current && !rootRef.current.contains(e.target as Node)) setOpen(false)
    }
    document.addEventListener('pointerdown', onPointerDown)
    return () => document.removeEventListener('pointerdown', onPointerDown)
  }, [open])

  const lowerExisting = new Set(value.map((t) => t.toLowerCase()))
  const matches = (known ?? [])
    .filter((s) => !lowerExisting.has(s.toLowerCase()))
    .filter((s) => !draft.trim() || s.toLowerCase().includes(draft.trim().toLowerCase()))
    .slice(0, 8)

  function addTag(raw: string) {
    const tag = raw.trim()
    if (!tag || atCap) return
    const lower = tag.toLowerCase()
    if (lowerExisting.has(lower)) {
      setDraft('')
      return
    }
    // Prefer the canonical casing already used elsewhere in the collection.
    const canonical = (known ?? []).find((s) => s.toLowerCase() === lower) ?? tag
    onChange([...value, canonical])
    setDraft('')
    setActiveIndex(-1)
  }

  function removeTag(index: number) {
    onChange(value.filter((_, i) => i !== index))
  }

  function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
    if (e.key === 'Enter' || e.key === ',') {
      e.preventDefault()
      if (activeIndex >= 0 && activeIndex < matches.length) addTag(matches[activeIndex]!)
      else addTag(draft)
    } else if (e.key === 'ArrowDown') {
      e.preventDefault()
      setOpen(true)
      setActiveIndex((i) => Math.min(i + 1, matches.length - 1))
    } else if (e.key === 'ArrowUp') {
      e.preventDefault()
      setActiveIndex((i) => Math.max(i - 1, -1))
    } else if (e.key === 'Escape') {
      setOpen(false)
      setActiveIndex(-1)
    } else if (e.key === 'Backspace' && !draft && value.length > 0) {
      removeTag(value.length - 1)
    }
  }

  return (
    <div ref={rootRef} className="relative">
      {value.length > 0 && (
        <div className="mb-1.5 flex flex-wrap gap-1.5">
          {value.map((tag, i) => (
            <span
              key={`${tag}-${i}`}
              className="bg-accent text-foreground inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-xs"
            >
              {tag}
              {!disabled && (
                <button
                  type="button"
                  aria-label={`Remove tag ${tag}`}
                  onClick={() => removeTag(i)}
                  className="text-muted-foreground hover:text-destructive rounded p-0.5"
                >
                  <X className="h-3 w-3" aria-hidden />
                </button>
              )}
            </span>
          ))}
        </div>
      )}
      <input
        id={id}
        className={INPUT}
        role="combobox"
        aria-expanded={open && matches.length > 0}
        aria-autocomplete="list"
        aria-controls={`${id}-listbox`}
        value={draft}
        disabled={disabled || atCap}
        placeholder={atCap ? `Maximum of ${maxTags} tags` : (placeholder ?? 'Add a tag…')}
        onFocus={() => {
          ensureSuggestions()
          setOpen(true)
        }}
        onChange={(e) => {
          setDraft(e.target.value)
          setOpen(true)
          setActiveIndex(-1)
        }}
        onKeyDown={handleKeyDown}
      />
      {open && matches.length > 0 && !disabled && (
        <ul
          id={`${id}-listbox`}
          role="listbox"
          aria-label="Tag suggestions"
          className="border-border bg-card absolute z-20 mt-1 max-h-44 w-full overflow-y-auto rounded-md border py-1 shadow-md"
        >
          {matches.map((s, i) => (
            <li key={s} role="option" aria-selected={i === activeIndex}>
              <button
                type="button"
                className={`w-full px-2.5 py-1.5 text-left text-sm ${
                  i === activeIndex
                    ? 'bg-accent text-foreground'
                    : 'text-foreground hover:bg-accent'
                }`}
                onMouseEnter={() => setActiveIndex(i)}
                onClick={() => addTag(s)}
              >
                {s}
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}
