'use client'

/**
 * The catalogue: search, category pills, level, availability, sort and pages.
 *
 * It filters a list the server already rendered — the cards arrive as `children`, one per course, and
 * this only decides which of them to show. So the catalogue is in the HTML for a crawler and for a
 * visitor whose JavaScript has not arrived, and the filtering costs no second request and no API of
 * its own. The price of that is that `items` carries a small searchable record beside each card; it is
 * text already on the page, nothing more.
 *
 * `level` is not an enum anywhere: the platform stores it as a phrase per language («من الصفر»,
 * «متقدّم»). So the options are the distinct phrases actually present in the catalogue, built on the
 * server — a level added on the platform tomorrow appears here with nothing changed.
 */

import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { counted, type CountForms } from '@/lib/plural'

export type CatalogItem = {
  slug: string
  /** Everything worth matching a search against, already lowercased on the server. */
  haystack: string
  categorySlug: string | null
  level: string
  status: 'live' | 'partial' | 'soon'
  /** What the visitor pays today, for the «lowest price» sort. Null sorts last. */
  price: number | null
}

export type CatalogLabels = {
  search: string
  category: string
  level: string
  status: string
  all: string
  allLevels: string
  available: string
  partial: string
  coming: string
  results: string
  sort: string
  sortDefault: string
  sortPrice: string
  empty: string
  page: string
}

type Props = {
  items: CatalogItem[]
  /** One card per item, in the same order. */
  children: ReactNode[]
  categories: { slug: string; name: string }[]
  levels: string[]
  labels: CatalogLabels
  /** The counted noun's forms, for «6 مناهج في النتايج». */
  forms: CountForms
  locale: 'ar' | 'en'
  pageSize: number
  /** ?category=… from the address, so a category card opens the catalogue already filtered. */
  initialCategory?: string | null
}

export function Catalog({ items, children, categories, levels, labels, forms, locale, pageSize, initialCategory }: Props) {
  const known = (c: string | null | undefined) => (c && categories.some((k) => k.slug === c) ? c : 'all')
  const [category, setCategory] = useState(() => known(initialCategory))
  const [search, setSearch] = useState('')
  const [level, setLevel] = useState('all')
  const [status, setStatus] = useState('all')
  const [sort, setSort] = useState<'default' | 'price'>('default')
  const [page, setPage] = useState(1)

  // Any change to what is being asked for starts again at page one; staying on page 3 of a list that
  // now has one page shows an empty catalogue that looks like a fault.
  useEffect(() => setPage(1), [category, search, level, status, sort])

  // Keep the address in step, so a filtered catalogue can be copied, shared and reopened. replaceState
  // rather than push: the back button should leave the catalogue, not walk back through every keystroke.
  useEffect(() => {
    const url = new URL(window.location.href)
    if (category === 'all') url.searchParams.delete('category')
    else url.searchParams.set('category', category)
    window.history.replaceState(null, '', url.toString())
  }, [category])

  const shown = useMemo(() => {
    const q = search.trim().toLowerCase()
    const out: number[] = []
    items.forEach((it, i) => {
      if (category !== 'all' && it.categorySlug !== category) return
      if (level !== 'all' && it.level !== level) return
      if (status !== 'all' && it.status !== status) return
      if (q && !it.haystack.includes(q)) return
      out.push(i)
    })
    if (sort === 'price') {
      out.sort((a, b) => (items[a].price ?? Number.POSITIVE_INFINITY) - (items[b].price ?? Number.POSITIVE_INFINITY))
    }
    return out
  }, [items, category, level, status, search, sort])

  const pages = Math.max(1, Math.ceil(shown.length / pageSize))
  const here = Math.min(page, pages)
  const slice = shown.slice((here - 1) * pageSize, here * pageSize)

  return (
    <>
      <div className="filters">
        <div className="field">
          <label htmlFor="q">{labels.search}</label>
          <input id="q" type="search" value={search} autoComplete="off"
                 placeholder={labels.search} onChange={(e) => setSearch(e.target.value)} />
        </div>
        {levels.length > 1 ? (
          <div className="field">
            <label htmlFor="level">{labels.level}</label>
            <select id="level" value={level} onChange={(e) => setLevel(e.target.value)}>
              <option value="all">{labels.allLevels}</option>
              {levels.map((l) => (
                <option key={l} value={l}>{l}</option>
              ))}
            </select>
          </div>
        ) : null}
        <div className="field">
          <label htmlFor="status">{labels.status}</label>
          <select id="status" value={status} onChange={(e) => setStatus(e.target.value)}>
            <option value="all">{labels.all}</option>
            <option value="live">{labels.available}</option>
            <option value="partial">{labels.partial}</option>
            <option value="soon">{labels.coming}</option>
          </select>
        </div>
      </div>

      <div className="pill-row" role="group" aria-label={labels.category}>
        <button type="button" className={`pill ${category === 'all' ? 'active' : ''}`}
                aria-pressed={category === 'all'} onClick={() => setCategory('all')}>
          {labels.all}
        </button>
        {categories.map((k) => (
          <button key={k.slug} type="button" className={`pill ${category === k.slug ? 'active' : ''}`}
                  aria-pressed={category === k.slug} onClick={() => setCategory(k.slug)}>
            {k.name}
          </button>
        ))}
      </div>

      <div className="catalog-meta">
        {/* The count changes without the focus moving, so it is announced rather than silently replaced. */}
        <span aria-live="polite">
          {counted(shown.length, forms, locale)} {labels.results}
        </span>
        <span>
          <label htmlFor="sort">{labels.sort}: </label>
          <select id="sort" value={sort} onChange={(e) => setSort(e.target.value as 'default' | 'price')}>
            <option value="default">{labels.sortDefault}</option>
            <option value="price">{labels.sortPrice}</option>
          </select>
        </span>
      </div>

      <div className="grid">
        {slice.length ? slice.map((i) => <div key={items[i].slug}>{children[i]}</div>)
          : <div className="empty" style={{ gridColumn: '1 / -1' }}>{labels.empty}</div>}
      </div>

      {pages > 1 ? (
        <nav className="pagination" aria-label={labels.page}>
          {Array.from({ length: pages }, (_, i) => i + 1).map((n) => (
            <button key={n} type="button" className={n === here ? 'active' : undefined}
                    aria-label={`${labels.page} ${n}`} aria-current={n === here ? 'page' : undefined}
                    onClick={() => setPage(n)}>
              {n}
            </button>
          ))}
        </nav>
      ) : null}
    </>
  )
}
