import { useState } from 'react'
import { X, Star, ShoppingBasket, Trash2, Plus } from 'lucide-react'
import clsx from 'clsx'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { format } from 'date-fns'
import { usePhotoStore } from '../../store/photoStore'
import {
  photos as photosApi,
  heaps as heapsApi,
  tags as tagsApi,
} from '../../services/api'
import type { Photo } from '../../types/photo'
import {
  guessDateFromPath,
  type DateGuess,
} from '../../lib/guessDateFromPath'
import { useActiveHeapMembers } from '../../hooks/useActiveHeapMembersQuery'
import { HEAPS_QUERY_KEY } from '../../hooks/useHeapsQuery'
import { LIBRARY_STATS_QUERY_KEY } from '../../hooks/useLibraryStatsQuery'
import { useTagsQuery, TAGS_QUERY_KEY } from '../../hooks/useTagsQuery'
import { stripPhotosFromCache } from '../../hooks/usePhotosQuery'
import { toast } from '../ToastContainer'
import { PhotoInfoPanel } from '../sidebar/PhotoInfoPanel'
import { COLOR_LABEL_OPTIONS } from '../../constants/colorLabels'

/**
 * Right-hand details panel.
 *  - 1 photo selected → delegates to PhotoInfoPanel for the full editor.
 *  - 2+ photos selected → renders a slim bulk-action panel that fans out
 *    rating / color / discard / pick across the entire selection.
 */
export function RightSidebar() {
  const { selectedPhotos, activePhotoId, clearSelection } = usePhotoStore()
  const queryClient = useQueryClient()

  const invalidatePhotoQueries = () => {
    queryClient.invalidateQueries({ queryKey: ['photo'] })
    queryClient.invalidateQueries({ queryKey: ['photos'] })
    queryClient.invalidateQueries({ queryKey: LIBRARY_STATS_QUERY_KEY })
  }

  const bulkRatingMutation = useMutation({
    mutationFn: ({ ids, rating }: { ids: string[]; rating: number }) =>
      photosApi.bulkSetRating(ids, rating),
    onSuccess: invalidatePhotoQueries,
  })
  const bulkColorMutation = useMutation({
    mutationFn: ({ ids, color }: { ids: string[]; color: string | null }) =>
      photosApi.bulkSetColor(ids, color),
    onSuccess: invalidatePhotoQueries,
  })
  const bulkDiscardMutation = useMutation({
    mutationFn: (ids: string[]) => photosApi.bulkDiscard(ids),
    // Yank the photos from the timeline before the network round-trip
    // so the grid reflows immediately. Same pattern as the X hotkey
    // path in useKeyboardShortcuts.
    onMutate: (ids) => {
      usePhotoStore.getState().removePhotosFromTimeline(ids)
      stripPhotosFromCache(queryClient, ids)
    },
    onSuccess: invalidatePhotoQueries,
  })

  // Shared report-and-invalidate tail for both bulk taken_at mutations.
  // They return a partial-apply shape (updated/skipped/errors) because
  // EXIF writes can fail per-photo (unsupported format, missing file)
  // without wrecking the rest of the batch.
  const reportBulkTakenAt = (
    data: {
      status: string
      updated: number
      skipped: number
      errors: { id: string; message: string }[]
    },
  ) => {
    const errCount = data.errors?.length ?? 0
    const detail =
      errCount > 0
        ? `${data.updated} updated · ${errCount} error${errCount === 1 ? '' : 's'}`
        : `${data.updated} updated`
    if (errCount > 0) {
      toast.error('Date update partial', detail)
    } else {
      toast.success('Dates updated', detail)
    }
    invalidatePhotoQueries()
  }

  const bulkTakenAtMutation = useMutation({
    mutationFn: ({ ids, iso }: { ids: string[]; iso: string }) =>
      photosApi.bulkSetTakenAt(ids, iso),
    onSuccess: reportBulkTakenAt,
    onError: (e: any) =>
      toast.error('Date update failed', e?.message || 'Unknown error'),
  })

  const bulkTakenAtMapMutation = useMutation({
    mutationFn: (map: Record<string, string>) =>
      photosApi.bulkSetTakenAtMap(map),
    onSuccess: reportBulkTakenAt,
    onError: (e: any) =>
      toast.error('Date update failed', e?.message || 'Unknown error'),
  })

  // Bulk tag mutations. Tag mutations also need to invalidate the tags
  // query so the FilterBar / sidebar tag counts stay fresh.
  const invalidateTagsAndPhotos = () => {
    invalidatePhotoQueries()
    queryClient.invalidateQueries({ queryKey: TAGS_QUERY_KEY })
  }
  const bulkAddTagsMutation = useMutation({
    mutationFn: ({ ids, tagIds }: { ids: string[]; tagIds: string[] }) =>
      photosApi.bulkAddTags(ids, tagIds),
    onSuccess: (data) => {
      const added = data?.added ?? 0
      toast.success(
        'Tags added',
        `${added} new link${added === 1 ? '' : 's'}`
      )
      invalidateTagsAndPhotos()
    },
    onError: (e: any) =>
      toast.error('Add tags failed', e?.message || 'Unknown error'),
  })
  const bulkRemoveTagsMutation = useMutation({
    mutationFn: ({ ids, tagIds }: { ids: string[]; tagIds: string[] }) =>
      photosApi.bulkRemoveTags(ids, tagIds),
    onSuccess: (data) => {
      const removed = data?.removed ?? 0
      toast.success(
        'Tags removed',
        `${removed} link${removed === 1 ? '' : 's'} removed`
      )
      invalidateTagsAndPhotos()
    },
    onError: (e: any) =>
      toast.error('Remove tags failed', e?.message || 'Unknown error'),
  })

  // Idempotent create-and-attach: lets the user type a brand-new tag
  // name and apply it to the whole selection in one click.
  const createAndAttachMutation = useMutation({
    mutationFn: async ({ name, ids }: { name: string; ids: string[] }) => {
      const created = await tagsApi.create(name)
      return photosApi.bulkAddTags(ids, [created.id])
    },
    onSuccess: () => {
      toast.success('Tag created and applied')
      invalidateTagsAndPhotos()
    },
    onError: (e: any) =>
      toast.error('Create tag failed', e?.message || 'Unknown error'),
  })

  const { data: allTags = [] } = useTagsQuery()
  const [tagInput, setTagInput] = useState('')

  // Active heap membership for the bulk Select toggle.
  const { activeHeap, memberIds: activeHeapMembers } = useActiveHeapMembers()

  const heapMutation = useMutation({
    mutationFn: ({ ids, remove }: { ids: string[]; remove: boolean }) => {
      if (!activeHeap || ids.length === 0) return Promise.resolve(null)
      return remove
        ? heapsApi.removePhotos(activeHeap.id, ids)
        : heapsApi.addPhotos(activeHeap.id, ids)
    },
    onMutate: ({ ids, remove }) => {
      if (!activeHeap || ids.length === 0) return { previous: undefined }
      const key = ['heap-photo-ids', activeHeap.id] as const
      const previous = queryClient.getQueryData<string[]>(key)
      const set = new Set(previous ?? [])
      if (remove) ids.forEach((id) => set.delete(id))
      else ids.forEach((id) => set.add(id))
      queryClient.setQueryData<string[]>(key, Array.from(set))
      return { previous }
    },
    onError: (e: any, _vars, ctx) => {
      if (activeHeap && ctx?.previous) {
        queryClient.setQueryData(['heap-photo-ids', activeHeap.id], ctx.previous)
      }
      toast.error('Heap update failed', e?.message || 'Unknown error')
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: HEAPS_QUERY_KEY })
      if (activeHeap) {
        queryClient.invalidateQueries({
          queryKey: ['heap-photo-ids', activeHeap.id],
        })
      }
    },
  })

  // Unified header rendered in every branch so the collapse button is
  // always reachable regardless of selection state. Title and the
  // clear-selection X adapt to what's selected.
  const headerTitle =
    selectedPhotos.length === 0
      ? 'Metadata'
      : selectedPhotos.length === 1
        ? 'Metadata'
        : `${selectedPhotos.length} Photos Selected`

  const Header = () => (
    <div className="flex h-9 flex-shrink-0 items-center justify-between border-b border-border px-3">
      <h2 className="text-[11px] font-semibold uppercase tracking-[0.14em] text-text-muted">
        {headerTitle}
      </h2>
      <div className="flex items-center gap-0.5">
        {selectedPhotos.length > 0 && (
          <button
            onClick={clearSelection}
            className="rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-text"
            title="Clear selection (Esc)"
            aria-label="Clear selection"
          >
            <X className="h-3.5 w-3.5" />
          </button>
        )}
      </div>
    </div>
  )

  if (selectedPhotos.length === 0) {
    return (
      <div className="flex h-full flex-col bg-surface">
        <Header />
        <div className="flex flex-1 items-center justify-center p-4 text-center">
          <div className="text-text-muted">
            <pre className="mx-auto mb-3 text-[6px] leading-[6px] opacity-30">{`⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⡇⠀⣠⣤⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⡇⢸⣿⡟⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⡿⠀⣿⡿⠁⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⠃⣼⡟⠁⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣤⣶⣿⣿⣿⣧⣀⠋⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀
⠀⠀⠀⢀⡀⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⠛⠻⣿⣿⡄⠀⠀⠀⠀⠀
⠀⠀⢠⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣿⣿⣷⡀⠀⠀⠀⠀⠀
⣠⣤⣿⣿⣿⣿⣿⣿⣿⣿⡿⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠘⢿⣿⣿⣿⣿⣿⣿⣿⢿⡆⠀⠀
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠙⠛⠿⠿⣿⣿⠿⣾⡇⠀⠀
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠁⠀⠀⠀⠀⠀⠀⠹⣿⡿⣦⠈⠁⠀⠀
⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠛⠛⠛⠛⠛⠛⠛⠛⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀`}</pre>
            <p className="text-sm">Select photos to view details</p>
          </div>
        </div>
      </div>
    )
  }

  // ── Single-photo: full editor via PhotoInfoPanel ────────────────────
  if (selectedPhotos.length === 1) {
    const id = activePhotoId ?? selectedPhotos[0]
    return (
      <div className="flex h-full flex-col bg-surface">
        <Header />
        <PhotoInfoPanel photoId={id} />
      </div>
    )
  }

  // ── Multi-photo: bulk action panel ──────────────────────────────────
  const allMembers = selectedPhotos.every((id) => activeHeapMembers.has(id))

  /** Walk the react-query cache for every selected id and return the
   *  full Photo records. Checks the standalone `['photo', id]` entry
   *  first (populated whenever a single-photo view or preview opens),
   *  then falls back to scanning every cached timeline list for the
   *  id. Any id with no cached record is skipped — the selection UI
   *  can't act on a photo the user hasn't loaded yet anyway. */
  const collectSelectedPhotos = (): Photo[] => {
    const out: Photo[] = []
    const seen = new Set<string>()
    for (const id of selectedPhotos) {
      if (seen.has(id)) continue
      const direct = queryClient.getQueryData<Photo>(['photo', id])
      if (direct) {
        out.push(direct)
        seen.add(id)
        continue
      }
      const lists = queryClient.getQueriesData<Photo[]>({ queryKey: ['photos'] })
      for (const [, list] of lists) {
        if (!list) continue
        const hit = list.find((p) => p.id === id)
        if (hit) {
          out.push(hit)
          seen.add(id)
          break
        }
      }
    }
    return out
  }

  return (
    <div className="flex h-full flex-col bg-surface">
      <Header />

      <div className="space-y-2.5 border-b border-border p-3">
        <p className="text-[11px] text-text-muted">
          Rating, color, and flag apply to all {selectedPhotos.length} selected.
        </p>

        {/* Bulk rating */}
        <div>
          <label className="mb-1 block text-xs text-text-muted">Rating</label>
          <div className="flex gap-1">
            {[1, 2, 3, 4, 5].map((value) => (
              <button
                key={value}
                onClick={() =>
                  bulkRatingMutation.mutate({ ids: selectedPhotos, rating: value })
                }
                className="p-0.5"
                title={`Set rating to ${value}`}
              >
                <Star className="h-5 w-5 text-text-muted hover:text-star" />
              </button>
            ))}
            <button
              onClick={() =>
                bulkRatingMutation.mutate({ ids: selectedPhotos, rating: 0 })
              }
              className="ml-1 rounded px-1 text-xs text-text-muted hover:text-text"
              title="Clear rating"
            >
              clear
            </button>
          </div>
        </div>

        {/* Bulk color */}
        <div>
          <label className="mb-1 block text-xs text-text-muted">Color label</label>
          <div className="flex items-center gap-1.5">
            {COLOR_LABEL_OPTIONS.map(({ value, className }) => (
              <button
                key={value}
                onClick={() =>
                  bulkColorMutation.mutate({ ids: selectedPhotos, color: value })
                }
                className={clsx(
                  'h-5 w-5 rounded-full opacity-80 ring-offset-2 ring-offset-surface transition-all hover:opacity-100',
                  className
                )}
                title={value}
              />
            ))}
            <button
              onClick={() =>
                bulkColorMutation.mutate({ ids: selectedPhotos, color: null })
              }
              className="ml-1 rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-text"
              title="Clear color label"
            >
              <X className="h-3 w-3" />
            </button>
          </div>
        </div>

        {/* Bulk flag */}
        <div>
          <label className="mb-1 block text-xs text-text-muted">Flag</label>
          <div className="flex gap-2">
            <button
              onClick={() => {
                if (!activeHeap) return
                heapMutation.mutate({ ids: selectedPhotos, remove: allMembers })
              }}
              disabled={!activeHeap || heapMutation.isPending}
              className={clsx(
                'flex items-center gap-1 rounded px-2 py-1 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50',
                allMembers
                  ? 'bg-pick/20 text-pick'
                  : 'bg-surface-2 text-text-muted hover:bg-surface-offset'
              )}
              title={
                activeHeap
                  ? allMembers
                    ? `Remove all from "${activeHeap.name}"`
                    : `Add all to "${activeHeap.name}"`
                  : 'Set an active heap first'
              }
            >
              <ShoppingBasket className="h-3 w-3" />
              {allMembers ? 'Selected' : 'Select'}
            </button>
            <button
              onClick={() => bulkDiscardMutation.mutate(selectedPhotos)}
              className="flex items-center gap-1 rounded bg-surface-2 px-2 py-1 text-sm text-text-muted transition-colors hover:bg-surface-offset"
            >
              <Trash2 className="h-3 w-3" />
              Discard
            </button>
          </div>
        </div>

        {/* Bulk tags. Click an existing tag chip to apply it to the
         *  whole selection; long-press / X icon to remove. The text
         *  input adds an existing tag if it matches a name, or creates
         *  a new tag and applies it. */}
        <div>
          <label className="mb-1 block text-xs text-text-muted">Tags</label>
          <BulkTagsEditor
            allTags={allTags}
            tagInput={tagInput}
            onTagInputChange={setTagInput}
            disabled={
              bulkAddTagsMutation.isPending ||
              bulkRemoveTagsMutation.isPending ||
              createAndAttachMutation.isPending
            }
            onApply={(tagId) =>
              bulkAddTagsMutation.mutate({ ids: selectedPhotos, tagIds: [tagId] })
            }
            onRemove={(tagId) =>
              bulkRemoveTagsMutation.mutate({
                ids: selectedPhotos,
                tagIds: [tagId],
              })
            }
            onCreate={(name) => {
              createAndAttachMutation.mutate({ name, ids: selectedPhotos })
              setTagInput('')
            }}
          />
        </div>

        {/* Bulk Date Taken — lets an operator repair the capture date on
         *  a whole selection at once, either by applying one date to
         *  everything or by inferring a per-photo date from each file's
         *  folder path and filename. Useful for cameras that lost their
         *  clock (1970 epoch) and for legacy libraries where the folder
         *  structure is the only trustworthy date signal. */}
        <div>
          <label className="mb-1 block text-xs text-text-muted">Date Taken</label>
          <BulkTakenAtEditor
            disabled={
              bulkTakenAtMutation.isPending || bulkTakenAtMapMutation.isPending
            }
            selectedCount={selectedPhotos.length}
            collectPhotos={collectSelectedPhotos}
            onApplyUniform={(iso) =>
              bulkTakenAtMutation.mutate({ ids: selectedPhotos, iso })
            }
            onApplyMap={(map) => bulkTakenAtMapMutation.mutate(map)}
          />
        </div>
      </div>
    </div>
  )
}

interface BulkTakenAtEditorProps {
  disabled: boolean
  selectedCount: number
  collectPhotos: () => Photo[]
  onApplyUniform: (iso: string) => void
  onApplyMap: (map: Record<string, string>) => void
}

/** Bulk Date Taken sub-panel used by RightSidebar in multi-select mode.
 *  Two modes share one UI:
 *    1. Apply-one: user types a datetime, clicks Apply, every selected
 *       photo is rewritten to that date.
 *    2. Guess-from-path: we run `guessDateFromPath` against each selected
 *       photo's filepath, show a preview of the hits + misses, and let
 *       the user commit the per-photo map in one round-trip. */
function BulkTakenAtEditor({
  disabled,
  selectedCount,
  collectPhotos,
  onApplyUniform,
  onApplyMap,
}: BulkTakenAtEditorProps) {
  const [uniformDraft, setUniformDraft] = useState('')
  const [preview, setPreview] = useState<
    | {
        hits: { photo: Photo; guess: DateGuess }[]
        misses: Photo[]
      }
    | null
  >(null)

  const handleGuess = () => {
    const photos = collectPhotos()
    const hits: { photo: Photo; guess: DateGuess }[] = []
    const misses: Photo[] = []
    for (const p of photos) {
      const g = guessDateFromPath(p.filepath)
      if (g) hits.push({ photo: p, guess: g })
      else misses.push(p)
    }
    setPreview({ hits, misses })
  }

  const handleApplyPreview = () => {
    if (!preview) return
    const map: Record<string, string> = {}
    for (const { photo, guess } of preview.hits) {
      map[photo.id] = guess.date.toISOString()
    }
    if (Object.keys(map).length === 0) return
    onApplyMap(map)
    setPreview(null)
  }

  const handleApplyUniform = () => {
    if (!uniformDraft) return
    const parsed = new Date(uniformDraft)
    if (Number.isNaN(parsed.getTime())) return
    onApplyUniform(parsed.toISOString())
  }

  return (
    <div className="space-y-2">
      {/* Apply-one row */}
      <div className="flex items-center gap-1.5">
        <input
          type="datetime-local"
          value={uniformDraft}
          onChange={(e) => setUniformDraft(e.target.value)}
          disabled={disabled}
          className="flex-1 rounded border border-border bg-bg px-2 py-1 text-xs text-text focus:border-primary focus:outline-none disabled:opacity-50"
        />
        <button
          onClick={handleApplyUniform}
          disabled={disabled || !uniformDraft}
          className="rounded bg-primary/20 px-2 py-1 text-xs text-primary hover:bg-primary/30 disabled:cursor-not-allowed disabled:opacity-40"
          title={`Apply this date to all ${selectedCount} selected`}
        >
          Apply
        </button>
      </div>

      {/* Guess-from-path preview */}
      {preview === null ? (
        <button
          onClick={handleGuess}
          disabled={disabled}
          className="flex w-full items-center justify-center gap-1 rounded border border-dashed border-primary/50 px-2 py-1 text-xs text-primary hover:bg-primary/10 disabled:opacity-50"
          title="Scan each photo's folder + filename for a date pattern"
        >
          Guess from folder paths
        </button>
      ) : (
        <div className="rounded border border-border bg-bg p-2 text-[11px]">
          <div className="mb-1.5 text-text-muted">
            {preview.hits.length} will update ·{' '}
            {preview.misses.length} skipped
          </div>
          {preview.hits.length > 0 && (
            <ul className="mb-1.5 max-h-24 space-y-0.5 overflow-y-auto font-mono text-[10px] text-text">
              {preview.hits.slice(0, 5).map(({ photo, guess }) => (
                <li key={photo.id} className="truncate" title={photo.filepath}>
                  <span className="text-text-muted">{photo.filename}</span>
                  {' → '}
                  <span className="text-primary">
                    {format(guess.date, 'yyyy-MM-dd')}
                  </span>
                </li>
              ))}
              {preview.hits.length > 5 && (
                <li className="text-text-muted">
                  …and {preview.hits.length - 5} more
                </li>
              )}
            </ul>
          )}
          <div className="flex gap-1.5">
            <button
              onClick={handleApplyPreview}
              disabled={disabled || preview.hits.length === 0}
              className="flex-1 rounded bg-primary/20 px-2 py-1 text-xs text-primary hover:bg-primary/30 disabled:cursor-not-allowed disabled:opacity-40"
            >
              Apply {preview.hits.length}
            </button>
            <button
              onClick={() => setPreview(null)}
              disabled={disabled}
              className="rounded bg-surface-2 px-2 py-1 text-xs text-text-muted hover:bg-surface-offset disabled:opacity-50"
            >
              Cancel
            </button>
          </div>
        </div>
      )}
    </div>
  )
}

interface BulkTagsEditorProps {
  allTags: { id: string; name: string; color: string | null }[]
  tagInput: string
  onTagInputChange: (value: string) => void
  disabled: boolean
  onApply: (tagId: string) => void
  onRemove: (tagId: string) => void
  onCreate: (name: string) => void
}

/**
 * Compact bulk tag editor for the multi-select right sidebar. Unlike the
 * single-photo TagsEditor we don't show "current tags" — there's no clean
 * single-photo notion of that across an arbitrary selection. Instead the
 * user picks an existing tag (apply to all) or types a new one (create
 * and apply to all).
 */
function BulkTagsEditor({
  allTags,
  tagInput,
  onTagInputChange,
  disabled,
  onApply,
  onRemove,
  onCreate,
}: BulkTagsEditorProps) {
  const trimmed = tagInput.trim()
  const lower = trimmed.toLowerCase()

  const filtered = trimmed
    ? allTags.filter((t) => t.name.toLowerCase().includes(lower))
    : allTags

  const exactMatch = trimmed
    ? allTags.find((t) => t.name.toLowerCase() === lower)
    : null

  const handleSubmit = () => {
    if (!trimmed || disabled) return
    if (exactMatch) {
      onApply(exactMatch.id)
      onTagInputChange('')
    } else {
      onCreate(trimmed)
    }
  }

  return (
    <div className="space-y-2">
      <input
        type="text"
        value={tagInput}
        onChange={(e) => onTagInputChange(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === 'Enter') {
            e.preventDefault()
            handleSubmit()
          } else if (e.key === 'Escape') {
            onTagInputChange('')
          }
        }}
        placeholder="Filter or create…"
        disabled={disabled}
        className="w-full rounded border border-border bg-bg px-2 py-1 text-xs text-text placeholder-text-faint focus:border-primary focus:outline-none disabled:opacity-50"
      />

      {trimmed && !exactMatch && (
        <button
          onClick={handleSubmit}
          disabled={disabled}
          className="flex w-full items-center justify-center gap-1 rounded border border-dashed border-primary/50 px-2 py-1 text-xs text-primary hover:bg-primary/10 disabled:opacity-50"
        >
          <Plus className="h-3 w-3" />
          Create "{trimmed}" and apply
        </button>
      )}

      {filtered.length > 0 ? (
        <div className="flex max-h-40 flex-wrap gap-1 overflow-y-auto">
          {filtered.map((tag) => (
            <span
              key={tag.id}
              className="flex items-center gap-1 rounded bg-surface-2 px-2 py-0.5 text-xs text-text"
              style={
                tag.color
                  ? { backgroundColor: `${tag.color}33`, color: tag.color }
                  : undefined
              }
            >
              <button
                onClick={() => onApply(tag.id)}
                disabled={disabled}
                className="hover:underline disabled:opacity-50"
                title={`Apply "${tag.name}" to selection`}
              >
                {tag.name}
              </button>
              <button
                onClick={() => onRemove(tag.id)}
                disabled={disabled}
                className="rounded p-0.5 opacity-60 hover:bg-surface-offset hover:opacity-100 disabled:opacity-30"
                title={`Remove "${tag.name}" from selection`}
                aria-label={`Remove ${tag.name} from selection`}
              >
                <X className="h-3 w-3" />
              </button>
            </span>
          ))}
        </div>
      ) : (
        <div className="text-xs text-text-faint">No tags match</div>
      )}
    </div>
  )
}
