/**
 * TagPicker – studio-style list-based tag selector
 *
 *  - Loads popular tags on mount
 *  - Debounced live search; shows "Add '<query>'" row for custom tags
 *  - Enter / comma / Tab commits a custom tag from the input
 *  - Scrollable list with circle / circle-check toggles
 *  - Selected chips shown above the list
 *  - AI-suggested tags shown with a purple badge
 *  - Search icon on the right side of the input
 *  - Counter footer: X/max tags selected
 *
 * Value format: string[] of tag slugs
 */
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { getMaxUserTags } from '../../config/tags'

const DEBOUNCE_MS = 250
const MAX_RESULTS = 30
const MIN_LENGTH = 2
const MAX_LENGTH = 32

// ─── helpers ─────────────────────────────────────────────────────────────────

function normalizeSlug(raw) {
  return String(raw ?? '')
    .trim()
    .toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/[^a-z0-9_-]/g, '')
    .replace(/-+/g, '-')
    .replace(/^[-_]+|[-_]+$/g, '')
    .slice(0, 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, selectedSlugs, maxTags) {
  const parts = parseTagList(rawText)
  const itemsToAdd = []
  const skippedDuplicates = []
  const skippedInvalid = []
  const skippedOverflow = []

  for (const part of parts) {
    const name = String(part ?? '').trim()
    const slug = normalizeSlug(name)

    if (!slug || slug.length < MIN_LENGTH || slug.length > MAX_LENGTH) {
      skippedInvalid.push(name)
      continue
    }

    if (selectedSlugs.includes(slug) || itemsToAdd.some((item) => item.slug === slug)) {
      skippedDuplicates.push(slug)
      continue
    }

    if (selectedSlugs.length + itemsToAdd.length >= maxTags) {
      skippedOverflow.push(slug)
      continue
    }

    itemsToAdd.push({ slug, name })
  }

  return {
    parsedCount: parts.length,
    itemsToAdd,
    nextSlugs: [...selectedSlugs, ...itemsToAdd.map((item) => item.slug)],
    skippedDuplicates,
    skippedInvalid,
    skippedOverflow,
  }
}

function toListItem(item) {
  if (!item) return null
  if (typeof item === 'string') {
    const slug = normalizeSlug(item)
    return slug ? { key: slug, slug, name: slug, usageCount: null, recentClicks: 0, isAi: false } : null
  }
  const slug = normalizeSlug(item.slug || item.tag || item.name || '')
  if (!slug) return null
  const usageCount = Number(item.usage_count)
  const recentClicks = Number(item.recent_clicks)
  return {
    key: String(item.id ?? slug),
    slug,
    name: item.name || item.tag || item.slug || slug,
    usageCount: Number.isFinite(usageCount) ? usageCount : null,
    recentClicks: Number.isFinite(recentClicks) ? recentClicks : 0,
    isAi: Boolean(item.is_ai || item.source === 'ai'),
  }
}

// ─── sub-components ───────────────────────────────────────────────────────────

function SearchInput({ value, onChange, onKeyDown, onPaste, inputRef, disabled, hint }) {
  return (
    <div className="relative">
      <input
        ref={inputRef}
        type="text"
        value={value}
        onChange={(e) => onChange(e.target.value)}
        onKeyDown={onKeyDown}
        onPaste={onPaste}
        disabled={disabled}
        className="w-full rounded-xl border border-white/10 bg-white/5 py-2.5 pl-3 pr-9 text-sm text-white placeholder:text-white/40 focus:border-accent/50 focus:outline-none focus:ring-2 focus:ring-accent/30 disabled:cursor-not-allowed disabled:opacity-60"
        placeholder={hint || 'Search or add tags…'}
        aria-label="Search or add tags"
        autoComplete="off"
        spellCheck={false}
      />
      {/* Search icon – right side */}
      <svg
        className="pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-white/35"
        viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"
      >
        <path fillRule="evenodd" d="M12.9 14.32a8 8 0 111.414-1.414l4.387 4.387-1.414 1.414-4.387-4.387zM8 14A6 6 0 108 2a6 6 0 000 12z" clipRule="evenodd" />
      </svg>
    </div>
  )
}

function SelectedChips({ slugs, names, onRemove, disabled }) {
  if (slugs.length === 0) return null
  return (
    <div className="flex flex-wrap gap-1.5">
      {slugs.map((slug) => (
        <span
          key={slug}
          className="inline-flex items-center gap-1 rounded-lg bg-accent/20 px-2.5 py-1 text-xs font-medium text-accent"
        >
          {names[slug] || slug}
          <button
            type="button"
            onClick={() => onRemove(slug)}
            disabled={disabled}
            className="ml-0.5 flex h-4 w-4 items-center justify-center rounded-full hover:bg-white/15 disabled:cursor-not-allowed"
            aria-label={`Remove tag ${names[slug] || slug}`}
          >
            <svg className="h-2.5 w-2.5" viewBox="0 0 10 10" fill="currentColor" aria-hidden="true">
              <path d="M6.414 5l2.293-2.293a1 1 0 00-1.414-1.414L5 3.586 2.707 1.293A1 1 0 001.293 2.707L3.586 5 1.293 7.293a1 1 0 101.414 1.414L5 6.414l2.293 2.293a1 1 0 001.414-1.414L6.414 5z" />
            </svg>
          </button>
        </span>
      ))}
    </div>
  )
}

function AddNewRow({ label, onAdd, disabled }) {
  return (
    <button
      type="button"
      onClick={onAdd}
      disabled={disabled}
      className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-sky-300 transition-all hover:bg-sky-400/10 disabled:cursor-not-allowed disabled:opacity-50"
    >
      <svg className="h-3.5 w-3.5 shrink-0" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
        <path strokeLinecap="round" d="M8 3v10M3 8h10" />
      </svg>
      <span>Add <span className="font-semibold">"{label}"</span></span>
    </button>
  )
}

function ListRow({ item, isSelected, onToggle, disabled }) {
  const detailLabel = item.recentClicks > 0
    ? `${item.recentClicks.toLocaleString()} recent clicks`
    : (typeof item.usageCount === 'number' ? `${item.usageCount.toLocaleString()} uses` : null)

  return (
    <button
      type="button"
      onClick={() => onToggle(item)}
      disabled={disabled}
      className={`flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm transition-all disabled:cursor-not-allowed ${
        isSelected
          ? 'bg-accent/12 text-accent'
          : 'text-slate-300 hover:bg-white/6 hover:text-white'
      }`}
    >
      <span className="flex min-w-0 items-center gap-2.5">
        {isSelected ? (
          <svg className="h-3.5 w-3.5 shrink-0 text-accent" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
            <path fillRule="evenodd" d="M8 16A8 8 0 108 0a8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L7 8.586 5.707 7.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
          </svg>
        ) : (
          <svg className="h-3.5 w-3.5 shrink-0 text-white/30" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" aria-hidden="true">
            <circle cx="8" cy="8" r="6.25" />
          </svg>
        )}

        <span className="truncate">{item.name}</span>

        {item.isAi && (
          <span className="shrink-0 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>
        )}
      </span>

      {detailLabel && (
        <span className="ml-3 shrink-0 text-[11px] text-white/40">
          {detailLabel}
        </span>
      )}
    </button>
  )
}

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-picker-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-picker-paste-title" className="mt-2 text-lg font-semibold text-white">
            Add {preview.itemsToAdd.length} pasted tag{preview.itemsToAdd.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 applying them.
          </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.itemsToAdd.length} tag{preview.itemsToAdd.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.itemsToAdd.map((item) => (
                  <span
                    key={item.slug}
                    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"
                  >
                    {item.name}
                  </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,
  )
}

// ─── main component ───────────────────────────────────────────────────────────

export default function TagPicker({
  value = [],
  onChange,
  suggestedTags = [],
  disabled = false,
  maxTags = getMaxUserTags(),
  searchEndpoint = '/api/tags/search',
  popularEndpoint = '/api/tags/popular',
  placeholder,
  error,
}) {
  const selectedSlugs = useMemo(
    () => Array.from(new Set((Array.isArray(value) ? value : []).map(normalizeSlug).filter(Boolean))),
    [value]
  )

  const [query, setQuery] = useState('')
  const [results, setResults] = useState([])
  const [loading, setLoading] = useState(false)
  const [fetchError, setFetchError] = useState(false)
  const [inputError, setInputError] = useState('')
  const [pastePreview, setPastePreview] = useState(null)

  // slug → display name (for chips)
  const [nameMap, setNameMap] = useState({})
  const updateNameMap = useCallback((items) => {
    setNameMap((prev) => {
      const next = { ...prev }
      items.forEach((item) => {
        if (item?.slug && item.name) next[item.slug] = item.name
      })
      return next
    })
  }, [])

  const abortRef = useRef(null)
  const timerRef = useRef(null)
  const inputRef = useRef(null)
  const cacheRef = useRef(new Map())

  const aiItems = useMemo(() => {
    return (Array.isArray(suggestedTags) ? suggestedTags : [])
      .map(toListItem)
      .filter(Boolean)
      .map((item) => ({ ...item, isAi: true }))
  }, [suggestedTags])

  const fetchTags = useCallback(async (q) => {
    const cacheKey = q.trim() || '__popular__'
    if (cacheRef.current.has(cacheKey)) {
      setResults(cacheRef.current.get(cacheKey))
      setFetchError(false)
      return
    }

    if (abortRef.current) abortRef.current.abort()
    abortRef.current = new AbortController()

    setLoading(true)
    setFetchError(false)

    try {
      const url = q.trim()
        ? `${searchEndpoint}?q=${encodeURIComponent(q.trim())}`
        : popularEndpoint
      const res = await window.axios.get(url, { signal: abortRef.current.signal })
      const raw = res?.data?.data || res?.data || []
      const items = (Array.isArray(raw) ? raw : []).map(toListItem).filter(Boolean).slice(0, MAX_RESULTS)
      cacheRef.current.set(cacheKey, items)
      setResults(items)
      updateNameMap(items)
    } catch (err) {
      if (err?.code === 'ERR_CANCELED' || abortRef.current?.signal?.aborted) return
      setFetchError(true)
      setResults([])
    } finally {
      if (!abortRef.current?.signal?.aborted) setLoading(false)
    }
  }, [searchEndpoint, popularEndpoint, updateNameMap])

  useEffect(() => {
    if (timerRef.current) clearTimeout(timerRef.current)
    timerRef.current = setTimeout(() => fetchTags(query), query ? DEBOUNCE_MS : 0)
    return () => clearTimeout(timerRef.current)
  }, [query, fetchTags])

  useEffect(() => () => { abortRef.current?.abort() }, [])

  useEffect(() => { updateNameMap(aiItems) }, [aiItems, updateNameMap])

  // ── tag actions ──────────────────────────────────────────────────────────

  const addTag = useCallback((rawSlug, displayName) => {
    const slug = normalizeSlug(rawSlug)
    if (!slug) return
    if (slug.length < MIN_LENGTH) { setInputError(`Too short (min ${MIN_LENGTH} chars)`); return }
    if (selectedSlugs.includes(slug)) { setInputError('Already added'); return }
    if (selectedSlugs.length >= maxTags) { setInputError('Maximum tags reached'); return }
    setInputError('')
    updateNameMap([{ slug, name: displayName || rawSlug }])
    onChange?.([...selectedSlugs, slug])
    setQuery('')
  }, [selectedSlugs, maxTags, onChange, updateNameMap])

  const toggleTag = useCallback((item) => {
    if (selectedSlugs.includes(item.slug)) {
      onChange?.(selectedSlugs.filter((s) => s !== item.slug))
    } else {
      addTag(item.slug, item.name)
    }
  }, [selectedSlugs, addTag, onChange])

  const removeTag = useCallback((slug) => {
    setInputError('')
    onChange?.(selectedSlugs.filter((s) => s !== slug))
  }, [selectedSlugs, onChange])

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

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

    updateNameMap(pastePreview.itemsToAdd)
    onChange?.(pastePreview.nextSlugs)
    setQuery('')
    setInputError('')
    setPastePreview(null)
  }, [onChange, pastePreview, updateNameMap])

  // Commit on Enter / comma / Tab
  const handleKeyDown = useCallback((e) => {
    const commit = e.key === 'Enter' || e.key === ',' || e.key === 'Tab'
    if (!commit) return

    // Backspace on empty → remove last chip
    if (e.key === 'Backspace' && query === '' && selectedSlugs.length > 0) {
      removeTag(selectedSlugs[selectedSlugs.length - 1])
      return
    }

    const candidate = query.trim().replace(/,$/, '')
    if (!candidate) return

    e.preventDefault()
    addTag(candidate, candidate)
  }, [query, selectedSlugs, addTag, removeTag])

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

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

    event.preventDefault()

    const preview = analyzePastedTags(raw, selectedSlugs, maxTags)
    if (preview.itemsToAdd.length === 0) {
      if (selectedSlugs.length >= maxTags || preview.skippedOverflow.length > 0) {
        setInputError('Maximum tags reached')
      } else {
        setInputError('No new tags found in pasted text')
      }
      return
    }

    setInputError('')
    setPastePreview(preview)
  }, [maxTags, selectedSlugs])

  // Show "Add 'query'" row when the query doesn't exactly match any result
  const querySlug = normalizeSlug(query)
  const showAddNew = Boolean(
    query.trim() &&
    querySlug.length >= MIN_LENGTH &&
    !results.some((r) => r.slug === querySlug) &&
    !selectedSlugs.includes(querySlug)
  )

  const displayList = useMemo(() => {
    const seen = new Set()
    const merged = []

    if (!query.trim()) {
      aiItems.forEach((item) => {
        if (!seen.has(item.slug)) { seen.add(item.slug); merged.push(item) }
      })
    }

    results.forEach((item) => {
      if (!seen.has(item.slug)) { seen.add(item.slug); merged.push(item) }
    })

    return merged
  }, [aiItems, results, query])

  const atMax = selectedSlugs.length >= maxTags

  return (
    <div className="space-y-3" data-testid="tag-picker-root">
      {/* Search / add input */}
      <SearchInput
        value={query}
        onChange={(v) => { setQuery(v); setInputError('') }}
        onKeyDown={handleKeyDown}
        onPaste={handlePaste}
        inputRef={inputRef}
        disabled={disabled}
        hint={placeholder}
      />

      {/* Inline input validation */}
      {inputError && (
        <p className="text-xs text-red-300" role="alert">{inputError}</p>
      )}

      {/* Selected chips */}
      <SelectedChips
        slugs={selectedSlugs}
        names={nameMap}
        onRemove={removeTag}
        disabled={disabled}
      />

      {/* Scrollable results list */}
      <div className="max-h-52 overflow-y-auto rounded-xl border border-white/8 bg-white/[0.02] p-1 sb-scrollbar">
        {loading && (
          <div className="flex items-center justify-center py-5">
            <div className="h-5 w-5 animate-spin rounded-full border-2 border-accent/30 border-t-accent" />
          </div>
        )}

        {!loading && fetchError && (
          <p className="py-4 text-center text-xs text-amber-300/80">Tag search unavailable</p>
        )}

        {/* "Add new" row — always shown when query doesn't match */}
        {!loading && showAddNew && (
          <AddNewRow
            label={query.trim()}
            onAdd={() => addTag(query.trim(), query.trim())}
            disabled={disabled || atMax}
          />
        )}

        {!loading && !fetchError && displayList.length === 0 && !showAddNew && (
          <p className="py-4 text-center text-sm text-white/40">
            {query ? 'No tags found — press Enter to add' : 'Type to search or add tags'}
          </p>
        )}

        {!loading && !fetchError && displayList.map((item) => (
          <ListRow
            key={item.key}
            item={item}
            isSelected={selectedSlugs.includes(item.slug)}
            onToggle={toggleTag}
            disabled={disabled || (atMax && !selectedSlugs.includes(item.slug))}
          />
        ))}
      </div>

      {/* Footer */}
      <div className="flex items-center justify-between text-xs text-white/45">
        <span>{selectedSlugs.length}/{maxTags} tags selected</span>
        {atMax
          ? <span className="text-amber-300/80">Maximum tags reached</span>
          : <span className="text-white/30">Enter, comma or Tab to add. Paste a comma-separated list to review multiple tags.</span>
        }
      </div>

      {error && <p className="text-xs text-red-300">{error}</p>}

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