import { useEffect, useMemo, useRef, useState } from 'react'
import {
  Star,
  X,
  ArrowDown,
  ArrowUp,
  Search,
  PanelLeftOpen,
  PanelLeftClose,
  PanelRightOpen,
  PanelRightClose,
} from 'lucide-react'
import clsx from 'clsx'
import {
  useFilterStore,
  hasActiveFilters,
  type MediaType,
  type SortField,
} from '../../store/filterStore'
import { useTagsQuery } from '../../hooks/useTagsQuery'
import { usePhotosQuery } from '../../hooks/usePhotosQuery'
import { FilterPill } from './FilterPill'
import { COLOR_LABEL_OPTIONS } from '../../constants/colorLabels'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'
import { MultiSelect } from '@/components/ui/multi-select'
import { Calendar } from '@/components/ui/calendar'

const SEARCH_DEBOUNCE_MS = 300

const MEDIA_TYPES: { value: MediaType; label: string }[] = [
  { value: 'photo', label: 'Photo' },
  { value: 'video', label: 'Video' },
  { value: 'raw', label: 'RAW' },
  { value: 'heic', label: 'HEIC' },
]

const SORT_OPTIONS: { value: SortField; label: string }[] = [
  { value: 'taken_at', label: 'Date taken' },
  { value: 'added_at', label: 'Date added' },
  { value: 'filename', label: 'Filename' },
  { value: 'file_size', label: 'File size' },
  { value: 'rating', label: 'Rating' },
]

/**
 * Compact, always-visible filter toolbar built out of FilterPill primitives.
 * Each pill represents a filter category, opens a popover with the
 * underlying control, and shows a short value summary inline when active.
 * Replaces the old expandable FilterBar + ActiveFilterChips combo.
 */
interface FilterBarProps {
  leftSidebarOpen: boolean
  rightSidebarOpen: boolean
  onToggleLeftSidebar: () => void
  onToggleRightSidebar: () => void
}

export function FilterBar({
  leftSidebarOpen,
  rightSidebarOpen,
  onToggleLeftSidebar,
  onToggleRightSidebar,
}: FilterBarProps) {
  const filterState = useFilterStore()
  const dateFrom = useFilterStore((s) => s.dateFrom)
  const dateTo = useFilterStore((s) => s.dateTo)
  const mediaTypes = useFilterStore((s) => s.mediaTypes)
  const ratingMin = useFilterStore((s) => s.ratingMin)
  const colorLabel = useFilterStore((s) => s.colorLabel)
  const flag = useFilterStore((s) => s.flag)
  const sortBy = useFilterStore((s) => s.sortBy)
  const sortOrder = useFilterStore((s) => s.sortOrder)
  const tagIds = useFilterStore((s) => s.tagIds)
  const needsReview = useFilterStore((s) => s.needsReview)
  const setNeedsReview = useFilterStore((s) => s.setNeedsReview)
  const currentSection = useFilterStore((s) => s.currentSection)

  // Only the Flag pill is hidden inside the Discarded section. Flag has
  // exactly two values and the section locks one of them, so the pill
  // would only ever toggle the section off — useless. Rating + Tags
  // pills stay visible in their sections because the user can refine
  // them further (ratingMin >= 3, restrict to specific tag ids).
  const hideFlagPill = currentSection === 'discarded'

  const setDateFrom = useFilterStore((s) => s.setDateFrom)
  const setDateTo = useFilterStore((s) => s.setDateTo)
  const toggleMediaType = useFilterStore((s) => s.toggleMediaType)
  const setRatingMin = useFilterStore((s) => s.setRatingMin)
  const setColorLabel = useFilterStore((s) => s.setColorLabel)
  const setFlag = useFilterStore((s) => s.setFlag)
  const setTagIds = useFilterStore((s) => s.setTagIds)
  const setSortBy = useFilterStore((s) => s.setSortBy)
  const toggleSortOrder = useFilterStore((s) => s.toggleSortOrder)
  const clearAll = useFilterStore((s) => s.clearAll)

  const { data: allTags = [] } = useTagsQuery()

  // Search box. Local state mirrors the store so typing stays responsive
  // while we debounce store writes (each store write triggers a re-fetch).
  const storeQ = useFilterStore((s) => s.q)
  const setStoreQ = useFilterStore((s) => s.setQ)
  const [searchQuery, setSearchQuery] = useState(storeQ)
  useEffect(() => {
    setSearchQuery(storeQ)
  }, [storeQ])
  const debounceRef = useRef<number | null>(null)
  useEffect(() => {
    if (searchQuery === storeQ) return
    if (debounceRef.current) window.clearTimeout(debounceRef.current)
    debounceRef.current = window.setTimeout(() => {
      setStoreQ(searchQuery)
    }, SEARCH_DEBOUNCE_MS)
    return () => {
      if (debounceRef.current) window.clearTimeout(debounceRef.current)
    }
  }, [searchQuery, storeQ, setStoreQ])

  // Pre-compute pill values + active flags so the JSX stays terse.
  const dateActive = dateFrom !== null || dateTo !== null
  const dateValue = dateActive
    ? `${dateFrom ?? '…'} → ${dateTo ?? '…'}`
    : null

  const typeActive = mediaTypes.length > 0
  const typeValue = typeActive
    ? mediaTypes.map((t) => t.toUpperCase()).join(', ')
    : null

  const ratingActive = ratingMin > 0
  const ratingValue = ratingActive ? `≥ ${ratingMin}★` : null

  const colorActive = colorLabel !== null
  const colorValue = colorActive ? colorLabel : null

  const flagActive = flag !== 'any' || needsReview
  const flagValue = needsReview
    ? 'needs review'
    : flag !== 'any'
      ? flag === 'date_warning'
        ? 'date issues'
        : flag
      : null

  const tagActive = tagIds.length > 0
  const activeTagNames = allTags
    .filter((t) => tagIds.includes(t.id))
    .map((t) => t.name)
  const tagValue = tagActive
    ? activeTagNames.length <= 2
      ? activeTagNames.join(', ')
      : `${activeTagNames.slice(0, 2).join(', ')} +${activeTagNames.length - 2}`
    : null

  const sortLabel = SORT_OPTIONS.find((o) => o.value === sortBy)?.label ?? sortBy
  const sortValue = `${sortLabel} ${sortOrder === 'desc' ? '↓' : '↑'}`

  const anyActive = hasActiveFilters(filterState)

  return (
    <div className="flex h-9 items-center gap-3 border-b border-border bg-surface px-3 py-0">
      {/* Left sidebar toggle — pinned to the far-left edge of the bar so it
       *  sits flush against the panel it controls (or the viewport edge
       *  when collapsed). */}
      <button
        onClick={onToggleLeftSidebar}
        className="flex-shrink-0 rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text"
        title={leftSidebarOpen ? 'Collapse panel (Tab)' : 'Expand panel (Tab)'}
        aria-label={leftSidebarOpen ? 'Collapse left panel' : 'Expand left panel'}
      >
        {leftSidebarOpen ? (
          <PanelLeftClose className="h-3.5 w-3.5" />
        ) : (
          <PanelLeftOpen className="h-3.5 w-3.5" />
        )}
      </button>

      {/* Pills — left side, scroll horizontally if they overflow. */}
      <div className="flex min-w-0 flex-1 items-center gap-1.5 overflow-x-auto">
        {/* Date */}
        <FilterPill
          label="Date"
          value={dateValue}
          isActive={dateActive}
          onClear={() => {
            setDateFrom(null)
            setDateTo(null)
          }}
        >
          <DateRangePicker
            from={dateFrom}
            to={dateTo}
            onFromChange={setDateFrom}
            onToChange={setDateTo}
          />
        </FilterPill>

        {/* Type */}
        <FilterPill
          label="Type"
          value={typeValue}
          isActive={typeActive}
          onClear={() => mediaTypes.forEach((t) => toggleMediaType(t))}
        >
          <MultiSelect
            searchable={false}
            pinSelected={false}
            options={MEDIA_TYPES.map(({ value, label }) => ({
              value,
              label,
            }))}
            values={mediaTypes}
            onChange={(next) => {
              // Diff against current selection — store uses per-item toggles.
              for (const t of MEDIA_TYPES.map((m) => m.value)) {
                const wasOn = mediaTypes.includes(t)
                const nowOn = next.includes(t as MediaType)
                if (wasOn !== nowOn) toggleMediaType(t)
              }
            }}
          />
        </FilterPill>

        {/* Rating */}
        <FilterPill
          label="Rating"
          value={ratingValue}
          isActive={ratingActive}
          onClear={() => setRatingMin(0)}
        >
          <div>
            <p className="mb-1 text-[11px] text-text-muted">Minimum</p>
            <div className="flex gap-1">
              {[1, 2, 3, 4, 5].map((n) => (
                <button
                  key={n}
                  onClick={() => setRatingMin(ratingMin === n ? 0 : n)}
                  className="p-0.5"
                  title={`At least ${n} star${n > 1 ? 's' : ''}`}
                >
                  <Star
                    className={clsx(
                      'h-5 w-5 transition-colors',
                      n <= ratingMin
                        ? 'fill-star text-star'
                        : 'text-text-muted hover:text-star'
                    )}
                  />
                </button>
              ))}
            </div>
          </div>
        </FilterPill>

        {/* Color */}
        <FilterPill
          label="Color"
          value={colorValue}
          isActive={colorActive}
          onClear={() => setColorLabel(null)}
        >
          <div className="flex items-center gap-1.5">
            {COLOR_LABEL_OPTIONS.map(({ value, className }) => {
              const active = colorLabel === value
              return (
                <button
                  key={value}
                  onClick={() => setColorLabel(active ? null : value)}
                  className={clsx(
                    'h-5 w-5 rounded-full ring-offset-2 ring-offset-surface transition-all',
                    className,
                    active ? 'ring-2 ring-primary' : 'opacity-60 hover:opacity-100'
                  )}
                  title={value}
                />
              )
            })}
            {colorLabel && (
              <button
                onClick={() => setColorLabel(null)}
                className="ml-1 rounded p-0.5 text-text-muted hover:bg-surface-2 hover:text-text"
                title="Clear color"
              >
                <X className="h-3 w-3" />
              </button>
            )}
          </div>
        </FilterPill>

        {/* Flag — hidden in the Discarded section, where the flag is
       *  pinned to "discarded" by the section preset. The Needs review
       *  option lives here too: it sets a different store field
       *  (`needsReview`) but is mutually exclusive with the other flag
       *  values from the user's perspective. */}
        {!hideFlagPill && (
          <FilterPill
            label="Flag"
            value={flagValue}
            isActive={flagActive}
            onClear={() => {
              setFlag('any')
              setNeedsReview(false)
            }}
            >
            <MultiSelect
              searchable={false}
              pinSelected={false}
              options={[
                { value: 'discarded', label: 'Discarded' },
                { value: 'date_warning', label: 'Date issues' },
                { value: 'needs_review', label: 'Needs review' },
              ]}
              values={
                needsReview
                  ? ['needs_review']
                  : flag !== 'any'
                    ? [flag]
                    : []
              }
              onChange={(next) => {
                  // Flag state is mutually exclusive in the store; treat
                  // the just-added value as the new single selection, or
                  // clear everything when the user unchecks the current.
                  const added = next.find(
                    (v) =>
                      v !==
                      (needsReview ? 'needs_review' : flag !== 'any' ? flag : '')
                  )
                  if (!added) {
                    setFlag('any')
                    setNeedsReview(false)
                    return
                  }
                  if (added === 'needs_review') {
                    setFlag('any')
                    setNeedsReview(true)
                  } else {
                    setFlag(added as 'discarded' | 'date_warning')
                    setNeedsReview(false)
                  }
                }}
              />
          </FilterPill>
        )}

        {/* Tags */}
        {allTags.length > 0 && (
          <FilterPill
            label="Tags"
            value={tagValue}
            isActive={tagActive}
            onClear={() => setTagIds([])}
            >
            <MultiSelect
              className="w-60"
              searchPlaceholder="Search tags…"
              emptyMessage="No tags match"
              options={allTags
                .slice()
                .sort((a, b) => {
                  if (b.photo_count !== a.photo_count)
                    return b.photo_count - a.photo_count
                  return a.name.localeCompare(b.name)
                })
                .map((t) => ({
                  value: t.id,
                  label: t.name,
                  meta: t.photo_count,
                }))}
              values={tagIds}
              onChange={setTagIds}
            />
          </FilterPill>
        )}

        {/* Sort — always present, never "active/inactive" since there's
          always a value. */}
        <FilterPill label="Sort" value={sortValue} isActive>
          <div className="space-y-2">
            <Select
              value={sortBy}
              onValueChange={(v) => setSortBy(v as SortField)}
            >
              <SelectTrigger>
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                {SORT_OPTIONS.map((opt) => (
                  <SelectItem key={opt.value} value={opt.value}>
                    {opt.label}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
            <Button
              variant="secondary"
              size="sm"
              onClick={toggleSortOrder}
              className="w-full"
            >
              {sortOrder === 'desc' ? (
                <>
                  <ArrowDown className="h-3.5 w-3.5" />
                  Descending
                </>
              ) : (
                <>
                  <ArrowUp className="h-3.5 w-3.5" />
                  Ascending
                </>
              )}
            </Button>
          </div>
        </FilterPill>

        {/* Clear-all — borderless text affordance pinned next to the pill
       *  cluster on the right. Lives inside the pills container so it
       *  shares the same flex group and gap and reads as "another
       *  pill". Only renders when any filter is active. */}
        {anyActive && (
          <button
            onClick={clearAll}
            className="ml-1 flex h-7 flex-shrink-0 items-center whitespace-nowrap px-1 text-xs text-text-muted underline-offset-2 hover:text-text hover:underline"
            title="Clear all filters in this section"
          >
            Clear all
          </button>
        )}
      </div>

      {/* Search — pinned to the right edge of the bar. Same id as before
       *  so the global "/" focus shortcut still finds it. */}
      <div className="relative w-56 flex-shrink-0">
        <Search className="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-text-muted" />
        <Input
          id="topbar-search"
          type="text"
          value={searchQuery}
          onChange={(e) => setSearchQuery(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Escape') {
              setSearchQuery('')
              setStoreQ('')
              e.currentTarget.blur()
            }
          }}
          placeholder="Search photos…"
          className="h-7 w-full rounded-full bg-surface-2 pl-8 pr-7 text-xs"
        />
        {searchQuery && (
          <button
            onClick={() => {
              setSearchQuery('')
              setStoreQ('')
            }}
            className="absolute right-1.5 top-1/2 -translate-y-1/2 rounded-full p-0.5 text-text-muted hover:bg-surface-offset hover:text-text"
            title="Clear search (Esc)"
            aria-label="Clear search"
          >
            <X className="h-3 w-3" />
          </button>
        )}
      </div>

      {/* Right sidebar toggle — pinned to the far-right edge. */}
      <button
        onClick={onToggleRightSidebar}
        className="flex-shrink-0 rounded p-1 text-text-muted hover:bg-surface-2 hover:text-text"
        title={rightSidebarOpen ? 'Collapse panel (I)' : 'Expand panel (I)'}
        aria-label={rightSidebarOpen ? 'Collapse right panel' : 'Expand right panel'}
      >
        {rightSidebarOpen ? (
          <PanelRightClose className="h-3.5 w-3.5" />
        ) : (
          <PanelRightOpen className="h-3.5 w-3.5" />
        )}
      </button>
    </div>
  )
}

/** Date range picker used inside the Date FilterPill. A single
 *  shadcn/react-day-picker Calendar in `range` mode — first click picks
 *  the start, second click picks the end. Writes both bounds to the
 *  store as yyyy-mm-dd ISO strings.
 *
 *  Days without matching photos are disabled + visually dimmed
 *  ("booked days" pattern from shadcn docs), so the user can see at a
 *  glance which dates are worth clicking. The booked set is derived
 *  from the currently-cached photos — reflects every other active
 *  filter, which is the intended UX: "which days have 5★ photos of
 *  screenshots" etc. The caption uses the dropdown layout so the user
 *  can jump across months/years without clicking the arrows. */
function DateRangePicker({
  from,
  to,
  onFromChange,
  onToChange,
}: {
  from: string | null
  to: string | null
  onFromChange: (v: string | null) => void
  onToChange: (v: string | null) => void
}) {
  // Unique yyyy-mm-dd keys of every cached photo's taken_at. Derived
  // from the shared photos cache so opening the calendar doesn't fire
  // another network request.
  const { data: photos = [] } = usePhotosQuery()
  const bookedSet = useMemo(() => {
    const s = new Set<string>()
    for (const p of photos) {
      if (!p.taken_at) continue
      const d = new Date(p.taken_at)
      if (Number.isNaN(d.getTime())) continue
      s.add(formatIsoDate(d))
    }
    return s
  }, [photos])

  const currentYear = new Date().getFullYear()
  const earliestYear = useMemo(() => {
    let min = currentYear
    for (const k of bookedSet) {
      const y = parseInt(k.slice(0, 4), 10)
      if (!Number.isNaN(y) && y < min) min = y
    }
    return min
  }, [bookedSet, currentYear])

  const selected = {
    from: from ? parseIsoDate(from) : undefined,
    to: to ? parseIsoDate(to) : undefined,
  }
  const handleSelect = (range: { from?: Date; to?: Date } | undefined) => {
    onFromChange(range?.from ? formatIsoDate(range.from) : null)
    onToChange(range?.to ? formatIsoDate(range.to) : null)
  }
  const label =
    from && to
      ? from === to
        ? from
        : `${from} → ${to}`
      : from
        ? `From ${from}`
        : to
          ? `Until ${to}`
          : 'Click to pick a start date, then an end date'

  return (
    <div className="space-y-2">
      <div className="text-[11px] text-text-muted">{label}</div>
      <Calendar
        mode="range"
        selected={selected}
        onSelect={handleSelect}
        numberOfMonths={1}
        defaultMonth={selected.from ?? selected.to ?? new Date()}
        captionLayout="dropdown"
        fromYear={earliestYear}
        toYear={currentYear + 1}
        modifiers={{
          booked: (d) => bookedSet.has(formatIsoDate(d)),
        }}
        modifiersClassNames={{
          // Days that DO have matching photos get a small primary dot
          // below the number; unmatched days stay visible and
          // clickable so the user can still pick any bound they want.
          booked:
            'relative font-semibold text-text after:absolute after:bottom-0.5 after:left-1/2 after:h-1 after:w-1 after:-translate-x-1/2 after:rounded-full after:bg-primary after:content-[""]',
        }}
        className="rounded-md border border-border bg-bg p-2"
      />
    </div>
  )
}

function parseIsoDate(s: string): Date {
  // yyyy-mm-dd → Date with local components so a user's "2024-01-15"
  // never rolls back to 2024-01-14 in a pacific timezone.
  const [y, m, d] = s.split('-').map(Number)
  return new Date(y, (m ?? 1) - 1, d ?? 1)
}

function formatIsoDate(d: Date): string {
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, '0')
  const day = String(d.getDate()).padStart(2, '0')
  return `${y}-${m}-${day}`
}
