import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useParams } from 'react-router-dom'
import { api, postDownload } from '../api'
import Gallery from '../components/Gallery'
import Lightbox from '../components/Lightbox'
import SelectionBar from '../components/SelectionBar'
import Stars from '../components/Stars'
import TagEditor from '../components/TagEditor'
import Thumbs from '../components/Thumbs'
import useLightbox from '../useLightbox'
import useSelection from '../useSelection'

const FILTERS = [
  { key: 'all', label: 'All' },
  { key: 'accept', label: '👍' },
  { key: 'reject', label: '👎' },
  { key: 'undecided', label: 'Undecided' },
]

const matchesFilter = (photo, key) =>
  key === 'all' || (key === 'undecided' ? !photo.my_verdict : photo.my_verdict === key)

export default function SharePage() {
  const { token } = useParams()
  const [view, setView] = useState(null)
  const [error, setError] = useState(null)
  const [password, setPassword] = useState('')
  const [unlockError, setUnlockError] = useState(null)
  const [filter, setFilter] = useState('all')

  const photos = view?.photos ?? []
  const visible = useMemo(() => photos.filter((p) => matchesFilter(p, filter)), [photos, filter])
  const filterCounts = useMemo(() => {
    const counts = { all: photos.length, accept: 0, reject: 0, undecided: 0 }
    for (const p of photos) counts[p.my_verdict ?? 'undecided'] += 1
    return counts
  }, [photos])

  // Selection spans the whole album (so switching filters keeps it), while
  // the selection bar describes only the current view: its counts, bytes and
  // downloads cover the visible photos, and "select all" adds them.
  const { selected, toggle, selectAll, clear } = useSelection(photos)
  const visibleSelected = visible.filter((p) => selected.has(p.id))
  const sumBytes = (list) => list.reduce((sum, p) => sum + p.size_bytes, 0)
  const lightbox = useLightbox(visible)

  const load = useCallback(
    () => api(`/api/share/${token}`).then(setView).catch((e) => setError(e.message)),
    [token],
  )
  useEffect(() => {
    load()
  }, [load])

  const unlock = async (e) => {
    e.preventDefault()
    try {
      await api(`/api/share/${token}/unlock`, { method: 'POST', body: { password } })
      setUnlockError(null)
      load()
    } catch (err) {
      setUnlockError(err.status === 401 ? 'Wrong password' : err.message)
    }
  }

  const patchPhoto = (photoId, patch) => {
    setView((v) => ({
      ...v,
      photos: v.photos.map((p) => (p.id === photoId ? { ...p, ...patch } : p)),
    }))
  }

  // Optimistic write: patch local state, PUT, reload from the server on
  // failure to undo the patch.
  const saveFeedback = async (photo, patch, endpoint, body) => {
    patchPhoto(photo.id, patch)
    try {
      await api(`/api/share/${token}/photos/${photo.id}/${endpoint}`, { method: 'PUT', body })
    } catch {
      load()
    }
  }
  const setRating = (photo, rating) =>
    saveFeedback(photo, { my_rating: rating || null }, 'rating', { rating })
  const setVerdict = (photo, verdict) =>
    saveFeedback(photo, { my_verdict: verdict }, 'verdict', { verdict })
  const setTags = (photo, tags) => saveFeedback(photo, { my_tags: tags }, 'tags', { tags })

  // Keyboard votes navigate away from the photo they change, so a transient
  // toast names what just happened to it — without it the jump reads as
  // "did that register?".
  const [flash, setFlash] = useState(null)
  const flashSeq = useRef(0)
  const flashTimer = useRef()
  const showFlash = (text) => {
    flashSeq.current += 1
    setFlash({ text, key: flashSeq.current })
    clearTimeout(flashTimer.current)
    flashTimer.current = setTimeout(() => setFlash(null), 1400)
  }
  useEffect(() => () => clearTimeout(flashTimer.current), [])

  // Culling flow: vote, then advance to the photo that was next in the
  // current view. Under a filter that hides the voted photo, the advance
  // target stays visible, so the run continues seamlessly.
  const voteAndAdvance = (photo, verdict) => {
    const next = visible[lightbox.index + 1]
    setVerdict(photo, verdict)
    showFlash(
      verdict === 'accept'
        ? `👍 ${photo.filename}`
        : verdict === 'reject'
          ? `👎 ${photo.filename}`
          : `↺ ${photo.filename} cleared`,
    )
    if (next) lightbox.show(next.id)
  }

  const keyActions = [
    { keys: ['p'], help: ['P', 'accept and go to next'], run: (p) => voteAndAdvance(p, 'accept') },
    { keys: ['x'], help: ['X', 'reject and go to next'], run: (p) => voteAndAdvance(p, 'reject') },
    {
      keys: ['u'],
      help: ['U', 'clear accept / reject'],
      // When clearing hides the photo from the current filter, advance like
      // a vote so the lightbox doesn't just close.
      run: (p) =>
        matchesFilter({ my_verdict: null }, filter)
          ? setVerdict(p, null)
          : voteAndAdvance(p, null),
    },
    {
      keys: ['1', '2', '3', '4', '5'],
      help: ['1–5', 'star rating'],
      run: (p, key) => setRating(p, Number(key)),
    },
    { keys: ['0'], help: ['0', 'clear star rating'], run: (p) => setRating(p, 0) },
    ...(view?.allow_download
      ? [{ keys: ['s'], help: ['S', 'select for download'], run: (p) => toggle(p.id) }]
      : []),
  ]

  if (error) return <div className="center-page">{error}</div>
  if (!view) return <div className="center-page">Loading…</div>

  if (view.locked) {
    return (
      <div className="center-page">
        <form className="login-card" onSubmit={unlock}>
          <h1>{view.album_name}</h1>
          <p className="muted">This gallery is password protected.</p>
          <input
            type="password"
            autoFocus
            placeholder="Password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
          <button className="btn btn-primary" type="submit">
            Open gallery
          </button>
          {unlockError && <p className="error">{unlockError}</p>}
        </form>
      </div>
    )
  }

  return (
    <>
      <header className="share-head">
        <h1>{view.album_name}</h1>
        {view.album_description && <p className="muted">{view.album_description}</p>}
        <p className="muted">
          {photos.length} photo{photos.length === 1 ? '' : 's'} · click a photo to view, rate and
          tag · press <kbd>?</kbd> in the viewer for shortcuts
        </p>
        {photos.length > 0 && (
          <div className="filter-bar">
            {FILTERS.map((f) => (
              <button
                key={f.key}
                className={`filter-chip${filter === f.key ? ' active' : ''}`}
                onClick={() => setFilter(f.key)}
              >
                {f.label} {filterCounts[f.key]}
              </button>
            ))}
          </div>
        )}
      </header>
      <main className="page">
        <Gallery
          photos={visible}
          onOpen={lightbox.openAt}
          selected={view.allow_download ? selected : undefined}
          // Shift-ranges must walk the filtered view, not the full album —
          // otherwise hidden photos get swept into the selection.
          onToggleSelect={
            view.allow_download ? (id, shift) => toggle(id, shift, visible) : undefined
          }
          overlay={(p) =>
            p.my_verdict || p.my_rating || p.my_tags.length > 0 ? (
              <div className="g-overlay">
                {p.my_verdict && <span>{p.my_verdict === 'accept' ? '👍' : '👎'}</span>}
                {p.my_rating && <span>★ {p.my_rating}</span>}
                {p.my_tags.length > 0 && <span># {p.my_tags.length}</span>}
              </div>
            ) : null
          }
        />
        {photos.length === 0 && (
          <p className="center-page muted">Nothing here yet — check back soon.</p>
        )}
        {photos.length > 0 && visible.length === 0 && (
          <p className="center-page muted">No photos match this filter.</p>
        )}
      </main>
      {view.allow_download && (
        <SelectionBar
          count={visibleSelected.length}
          total={visible.length}
          selectedBytes={sumBytes(visibleSelected)}
          totalBytes={sumBytes(visible)}
          onSelectAll={() => selectAll(visible)}
          onClear={clear}
          onDownload={() =>
            postDownload(`/api/share/${token}/zip`, visibleSelected.map((p) => p.id).join(','))
          }
          onDownloadAll={() =>
            // Under a filter, "download all" means all photos shown.
            postDownload(
              `/api/share/${token}/zip`,
              filter === 'all' ? '' : visible.map((p) => p.id).join(','),
            )
          }
        />
      )}
      {lightbox.index >= 0 && (
        <Lightbox
          photos={visible}
          index={lightbox.index}
          onClose={lightbox.close}
          onNav={lightbox.openAt}
          actions={keyActions}
          footer={(p) => (
            <div className="client-footer">
              <Thumbs value={p.my_verdict} onChange={(v) => setVerdict(p, v)} />
              <Stars value={p.my_rating || 0} onChange={(r) => setRating(p, r)} />
              <TagEditor tags={p.my_tags} onChange={(tags) => setTags(p, tags)} />
              {view.allow_download && (
                <label className="select-toggle">
                  <input
                    type="checkbox"
                    checked={selected.has(p.id)}
                    onChange={() => toggle(p.id)}
                  />
                  select
                </label>
              )}
              {view.allow_download && (
                <a className="btn" href={`/api/photos/${p.id}/original`}>
                  Download original
                </a>
              )}
            </div>
          )}
        />
      )}
      {flash && (
        <div key={flash.key} className="action-flash">
          {flash.text}
        </div>
      )}
    </>
  )
}
