import { useEffect, useMemo, useRef, useState } from 'react'
import { Link } from 'react-router-dom'
import { de } from '../strings/de'
import { listGerbils, listOriginBreeders } from '../api/gerbils'
import { listColorVarieties } from '../api/lookups'
import { andFilter, condition, orGroup } from '../api/gridify'
import { GENDERS, GERBIL_STATUSES, type Gender, type GerbilStatus } from '../api/types'
import { useApi } from '../hooks/useApi'
import { useInfiniteList, useInfiniteSentinel } from '../hooks/useInfiniteList'
import { formatDate, genderLabel, statusLabel } from '../format/labels'
import {
  GerbilFilterBar,
  ActiveFilterChips,
  BottomSheet,
  type FilterCategory,
} from '../components/GerbilFilterBar'
import { useGerbilName } from '../components/breederSuffix'
import './gerbils.css'

const PAGE_SIZE = 20

// SEARCH-2b: separator-insensitive search via Pam's normalized field. The term
// is normalized the SAME way server-side stores it (lowercase + strip [\s._-]),
// so 'clan kleine chaoten' matches 'clan-kleine-chaoten'.
const NAME_SEARCH_FIELD = 'nameSearch'
const normalizeSearch = (s: string): string => s.toLowerCase().replace(/[\s._-]/g, '')

type SortKey = 'nameAsc' | 'nameDesc' | 'birthDesc' | 'birthAsc'
// A trailing `id` makes every ordering a TOTAL order: rows that tie on name (or
// dateOfBirth) keep a stable position across page fetches, so OFFSET pagination
// never returns the same row on two pages (which caused duplicate React keys).
const SORT_ORDER_BY: Record<SortKey, string> = {
  nameAsc: 'name,id',
  nameDesc: 'name desc,id',
  birthDesc: 'dateOfBirth desc,id',
  birthAsc: 'dateOfBirth,id',
}
const SORT_KEYS: SortKey[] = ['nameAsc', 'nameDesc', 'birthDesc', 'birthAsc']

// DESIGN: per-status chip/dot/badge colours (from the prototype's status palette).
const STATUS_COLORS: Record<GerbilStatus, string> = {
  Breeding: '#5e8c5a',
  Pet: '#5a7da8',
  ForSale: '#c2703d',
  Deceased: '#8a847b',
  GivenAway: '#b08e5c',
}
const GENDER_SYMBOL: Record<Gender, string> = { unknown: '?', male: '♂', female: '♀' }

// Multi-select filter state. Keys match Gridify field names so the query builds directly.
interface GerbilFilters {
  [key: string]: string[]
  status: GerbilStatus[]
  gender: Gender[]
  colorVarietyId: string[]
  originBreeder: string[]
}
// BESTAND-FILTER: default to showing the own breeding stock (Status = Zucht).
const DEFAULT_FILTERS: GerbilFilters = {
  status: ['Breeding'],
  gender: [],
  colorVarietyId: [],
  originBreeder: [],
}
const EMPTY_FILTERS: GerbilFilters = { status: [], gender: [], colorVarietyId: [], originBreeder: [] }

export default function GerbilsPage() {
  const t = de.pages.gerbils
  const gerbilName = useGerbilName()
  const [search, setSearch] = useState('')
  const [filters, setFilters] = useState<GerbilFilters>(DEFAULT_FILTERS)
  // BESTAND-FILTER: default to the own clan (Bestand); opt in to external ancestors.
  const [showExternal, setShowExternal] = useState(false)
  // BESTAND-FILTER: optionally hide animals without a date of birth (server-side).
  const [hideUndated, setHideUndated] = useState(false)
  const [sort, setSort] = useState<SortKey>('nameAsc')
  const [sortOpen, setSortOpen] = useState(false)
  // DESIGN: the header (search + filters) is sticky; the title row blends out once
  // the user scrolls. A 1px sentinel above the sticky header drives `condensed`
  // via IntersectionObserver — robust regardless of which element actually scrolls.
  const [condensed, setCondensed] = useState(false)
  const topSentinelRef = useRef<HTMLDivElement | null>(null)
  useEffect(() => {
    const el = topSentinelRef.current
    if (!el) return
    const io = new IntersectionObserver(([entry]) => setCondensed(!entry.isIntersecting))
    io.observe(el)
    return () => io.disconnect()
  }, [])

  // Back-to-top: show a floating button once the user has scrolled a screenful,
  // and smooth-scroll the main area back to the top on tap.
  const [showBackToTop, setShowBackToTop] = useState(false)
  const scrollerOf = () =>
    (topSentinelRef.current?.closest('.app-main') as HTMLElement | null) ?? null
  useEffect(() => {
    const scroller = scrollerOf()
    const read = () => (scroller ? scroller.scrollTop : window.scrollY)
    const target: HTMLElement | Window = scroller ?? window
    const onScroll = () => setShowBackToTop(read() > 600)
    target.addEventListener('scroll', onScroll, { passive: true })
    onScroll()
    return () => target.removeEventListener('scroll', onScroll)
  }, [])
  const scrollToTop = () => (scrollerOf() ?? window).scrollTo({ top: 0, behavior: 'smooth' })

  const colorVarieties = useApi(() => listColorVarieties(), [])
  const colorNameById = useMemo(() => {
    const map = new Map<string, string>()
    for (const cv of colorVarieties.data ?? []) map.set(cv.id, cv.name)
    return map
  }, [colorVarieties.data])
  const breeders = useApi(() => listOriginBreeders(), [])

  // DESIGN: filter categories for the Quick-Chips bar. `key` doubles as the Gridify field.
  const categories: FilterCategory[] = useMemo(
    () => [
      {
        key: 'status',
        label: t.filters.status,
        options: GERBIL_STATUSES.map((s) => ({
          value: s,
          label: statusLabel(s),
          color: STATUS_COLORS[s],
        })),
      },
      {
        key: 'gender',
        label: t.filters.gender,
        options: GENDERS.map((g) => ({ value: g, label: genderLabel(g), symbol: GENDER_SYMBOL[g] })),
      },
      {
        key: 'colorVarietyId',
        label: t.filters.colorVariety,
        searchable: true,
        options: (colorVarieties.data ?? []).map((cv) => ({ value: cv.id, label: cv.name })),
      },
      {
        key: 'originBreeder',
        label: t.fields.origin,
        searchable: true,
        options: (breeders.data ?? []).map((b) => ({ value: b, label: b })),
      },
    ],
    [t, colorVarieties.data, breeders.data],
  )

  const filter = andFilter(
    search.trim() &&
      condition({ field: NAME_SEARCH_FIELD, op: 'contains', value: normalizeSearch(search) }),
    // Multi-select: values within a category are OR'd, categories are AND'd.
    orGroup('status', filters.status),
    orGroup('gender', filters.gender),
    orGroup('colorVarietyId', filters.colorVarietyId),
    orGroup('originBreeder', filters.originBreeder),
    // Default view = own clan (Bestand). Unless "externe Ahnen einblenden" is on,
    // restrict to resident animals (server-side, so paging/counts stay correct).
    !showExternal && condition({ field: 'isResident', op: '==', value: true }),
    // Optionally drop animals without a date of birth. Gridify reads an empty
    // value as null, so `dateOfBirth!=` means "date of birth is set".
    hideUndated && condition({ field: 'dateOfBirth', op: '!=', value: '' }),
  )
  const orderBy = SORT_ORDER_BY[sort]

  // Infinite scroll: page 1 loads now; further pages append as the sentinel scrolls
  // into view. Changing the query signature (filter/sort) reloads from page 1.
  const resetKey = `${filter}${orderBy}`
  const list = useInfiniteList(
    (page) => listGerbils({ filter: filter || undefined, orderBy, page, pageSize: PAGE_SIZE }),
    resetKey,
    'gerbils',
  )

  const sentinelRef = useInfiniteSentinel(list)

  // Filter mutations.
  const toggleFilter = (key: string, value: string) => {
    setFilters((f) => {
      const arr = f[key as keyof GerbilFilters] as string[]
      const next = arr.includes(value) ? arr.filter((v) => v !== value) : [...arr, value]
      return { ...f, [key]: next }
    })
  }
  const removeFilter = (key: string, value: string) => {
    setFilters((f) => ({
      ...f,
      [key]: (f[key as keyof GerbilFilters] as string[]).filter((v) => v !== value),
    }))
  }
  const clearAllFilters = () => setFilters(EMPTY_FILTERS)
  const resetAll = () => {
    setSearch('')
    setFilters(DEFAULT_FILTERS)
    setShowExternal(false)
    setHideUndated(false)
    setSort('nameAsc')
  }

  const chipColor = (key: string, value: string): string | undefined =>
    key === 'status' ? STATUS_COLORS[value as GerbilStatus] : undefined

  const { items, total } = list

  return (
    <section className="page gerbils-page">
      {/* 1px sentinel above the sticky header — drives the title blend-out. */}
      <div ref={topSentinelRef} className="gerbils-scrolltop" aria-hidden="true" />

      <div className="gerbils-topbar">
        <header className={`page-head gerbils-head${condensed ? ' gerbils-head--condensed' : ''}`}>
          <div>
            <h2>{t.title}</h2>
            {!list.loading && (
              <p className="muted">
                {total} {t.countLabel}
              </p>
            )}
          </div>
          <Link to="/rennmaeuse/neu" className="btn btn--primary">
            + {t.newButton}
          </Link>
        </header>

        <div className="rmf-searchbar">
          <span className="rmf-search-ico" aria-hidden="true">
            ⌕
          </span>
          <input
            type="search"
            className="rmf-searchfield"
            placeholder={t.searchPlaceholder}
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            aria-label={t.fields.name}
          />
          {search && (
            <button
              type="button"
              className="rmf-search-clear"
              onClick={() => setSearch('')}
              aria-label={de.filterBar.searchClear}
            >
              ×
            </button>
          )}
        </div>

        <GerbilFilterBar categories={categories} selected={filters} onToggle={toggleFilter} />

        <div className="rm-bar">
          <button
            type="button"
            className={`rmf-bar-chip${showExternal ? ' is-on' : ''}`}
            onClick={() => setShowExternal((v) => !v)}
            aria-pressed={showExternal}
            title={t.filters.showExternalHint}
          >
            <span className="rmf-chip-toggle-ico" aria-hidden="true">
              {showExternal ? '✓' : '+'}
            </span>
            <span className="rmf-bar-chip-label">{t.filters.showExternalShort}</span>
          </button>
          <button
            type="button"
            className={`rmf-bar-chip${hideUndated ? ' is-on' : ''}`}
            onClick={() => setHideUndated((v) => !v)}
            aria-pressed={hideUndated}
            title={t.filters.hideUndatedHint}
          >
            <span className="rmf-chip-toggle-ico" aria-hidden="true">
              {hideUndated ? '✓' : '+'}
            </span>
            <span className="rmf-bar-chip-label">{t.filters.hideUndatedShort}</span>
          </button>
          <button
            type="button"
            className="rmf-bar-chip"
            onClick={() => setSortOpen(true)}
            aria-haspopup="dialog"
          >
            <span className="rmf-bar-chip-ico" aria-hidden="true">
              ⇅
            </span>
            <span className="rmf-bar-chip-label">{t.sort[sort]}</span>
            <span className="rmf-bar-chip-chev" aria-hidden="true">
              ▾
            </span>
          </button>
        </div>
      </div>

      {sortOpen && (
        <BottomSheet title={t.filters.sortBy} onClose={() => setSortOpen(false)}>
          <div className="rmf-optlist">
            {SORT_KEYS.map((key) => (
              <button
                key={key}
                type="button"
                className={'rmf-opt' + (sort === key ? ' is-on' : '')}
                onClick={() => {
                  setSort(key)
                  setSortOpen(false)
                }}
                aria-pressed={sort === key}
              >
                <span className="rmf-opt-box" aria-hidden="true">
                  {sort === key ? '✓' : ''}
                </span>
                <span className="rmf-opt-label">{t.sort[key]}</span>
              </button>
            ))}
          </div>
        </BottomSheet>
      )}

      <ActiveFilterChips
        categories={categories}
        selected={filters}
        onRemove={removeFilter}
        onClearAll={clearAllFilters}
        chipColor={chipColor}
      />

      {list.loading && <p className="muted">{de.common.loading}</p>}
      {list.error && (
        <div className="alert alert--error">
          <span>{list.error}</span>
          <button type="button" className="btn" onClick={list.reload}>
            {de.common.retry}
          </button>
        </div>
      )}

      {!list.loading && !list.error && items.length === 0 && (
        <div className="rm-noresults">
          <p className="muted">{t.empty}</p>
          <button type="button" className="btn btn--ghost" onClick={resetAll}>
            {t.filters.reset}
          </button>
        </div>
      )}

      {items.length > 0 && (
        <ul className="card-list">
          {items.map((g) => (
            <li key={g.id} className="gerbil-row">
              <Link to={`/rennmaeuse/${g.id}`} className="gerbil-card">
                <div className="gerbil-card__top">
                  <span className="gerbil-card__name">
                    {gerbilName(g) || t.nameless}
                    {g.isResident === false && (
                      <span className="badge badge--external" title={t.externalTitle}>
                        {t.externalBadge}
                      </span>
                    )}
                  </span>
                  <span className="gerbil-card__right">
                    <span
                      className={`gerbil-sex gerbil-sex--${g.gender}`}
                      title={genderLabel(g.gender)}
                      aria-label={genderLabel(g.gender)}
                    >
                      {GENDER_SYMBOL[g.gender]}
                    </span>
                    <span className={`badge badge--${g.status.toLowerCase()}`}>
                      {statusLabel(g.status)}
                    </span>
                  </span>
                </div>
                <div className="gerbil-card__meta">
                  <span className="gerbil-card__meta-strong">
                    {g.colorVarietyId ? (colorNameById.get(g.colorVarietyId) ?? '—') : '—'}
                  </span>
                  <span className="gerbil-card__dot" aria-hidden="true">
                    ·
                  </span>
                  <span>{g.originBreeder || '—'}</span>
                  <span className="gerbil-card__dot" aria-hidden="true">
                    ·
                  </span>
                  <span>
                    {t.born} {formatDate(g.dateOfBirth)}
                  </span>
                </div>
              </Link>
            </li>
          ))}
        </ul>
      )}

      {/* Infinite-scroll sentinel + "loading more" indicator. */}
      {list.hasMore && <div ref={sentinelRef} className="rmf-sentinel" aria-hidden="true" />}
      {list.loadingMore && <p className="muted rmf-loadmore">{de.common.loading}</p>}

      {showBackToTop && (
        <button
          type="button"
          className="rmf-backtotop"
          onClick={scrollToTop}
          aria-label={t.backToTop}
          title={t.backToTop}
        >
          ↑
        </button>
      )}
    </section>
  )
}
