import { useCallback, useEffect, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import PublicLayout from '../../components/PublicLayout.jsx'
import PageHeader from '../../components/PageHeader.jsx'
import { Loading, ErrorState, EmptyState } from '../../components/PageState.jsx'
import { useAsync } from '../../lib/useAsync.js'
import { api } from '../../api/client.js'

// ── The spawn atlas ─────────────────────────────────────────────────────────
//
// What the shard CONTAINS, as opposed to what it is doing: which creatures
// spawn, where, and which champion altars are configured. There is no live feed
// here and no `connected` indicator, deliberately — this is parsed from the
// shard's own files and stays complete while the shard is down.
//
// Facet names come from the shard's data, never from a list in this file. A
// shard running custom maps gets its own names in the filter with no code
// change (docs/link/v3.md §6.1 R2).

const PAGE = 50

const num = (v) => (Number.isFinite(v) ? v.toLocaleString() : '—')

const TABS = [
  { key: 'creatures', label: 'Creatures' },
  { key: 'champions', label: 'Champion altars' },
  { key: 'places', label: 'Places' },
]

function Chip({ active, onClick, children }) {
  return (
    <button
      type="button"
      onClick={onClick}
      className="sans"
      style={{
        fontSize: '0.78rem',
        padding: '5px 12px',
        borderRadius: 999,
        cursor: 'pointer',
        color: active ? 'var(--bg-deep)' : 'var(--muted)',
        background: active ? 'var(--accent)' : 'transparent',
        border: `1px solid ${active ? 'var(--accent)' : 'var(--line)'}`,
      }}
    >
      {children}
    </button>
  )
}

function CreatureCard({ creature }) {
  const facets = Object.entries(creature.facets || {}).sort((a, b) => b[1] - a[1])
  return (
    <Link
      to={`/site/atlas/${encodeURIComponent(creature.slug)}`}
      className="panel"
      style={{
        padding: '13px 15px',
        display: 'flex',
        alignItems: 'center',
        gap: 14,
        textDecoration: 'none',
        color: 'inherit',
      }}
    >
      <div style={{ minWidth: 0, flex: 1 }}>
        <div
          className="display"
          style={{
            fontSize: '0.98rem',
            color: 'var(--head)',
            overflow: 'hidden',
            textOverflow: 'ellipsis',
            whiteSpace: 'nowrap',
          }}
        >
          {creature.name}
        </div>
        <div className="sans dim" style={{ fontSize: '0.74rem', marginTop: 3 }}>
          {facets.length === 0
            ? '—'
            : facets.map(([facet, n]) => `${facet} (${n})`).join(' · ')}
        </div>
      </div>
      <div className="sans" style={{ flex: 'none', textAlign: 'right' }}>
        <div style={{ color: 'var(--head)', fontSize: '0.92rem' }}>{num(creature.total)}</div>
        <div className="dim" style={{ fontSize: '0.68rem', letterSpacing: '0.05em' }}>
          {num(creature.points)} spawners
        </div>
      </div>
    </Link>
  )
}

// The creature list owns its own paging rather than going through useAsync: a
// "load more" appends to what is already on screen, which a hook that resets to
// `{ loading: true, data: null }` on every dependency change cannot express.
function Creatures({ q, facet }) {
  const [state, setState] = useState({ loading: true, error: null, items: [], total: 0 })
  const [more, setMore] = useState(false)

  const load = useCallback(
    async (offset) => {
      const page = await api.atlas.creatures({ q, facet, limit: PAGE, offset })
      return page
    },
    [q, facet],
  )

  useEffect(() => {
    let alive = true
    setState({ loading: true, error: null, items: [], total: 0 })
    load(0)
      .then((page) => {
        if (alive) setState({ loading: false, error: null, items: page.creatures || [], total: page.total || 0 })
      })
      .catch((error) => alive && setState({ loading: false, error, items: [], total: 0 }))
    return () => {
      alive = false
    }
  }, [load])

  const loadMore = async () => {
    setMore(true)
    try {
      const page = await load(state.items.length)
      setState((s) => ({ ...s, items: [...s.items, ...(page.creatures || [])], total: page.total ?? s.total }))
    } catch {
      // A failed "load more" leaves what is already on screen alone; the button
      // simply stays available to retry.
    } finally {
      setMore(false)
    }
  }

  if (state.loading) return <Loading />
  if (state.error) return <ErrorState message="Could not load the bestiary right now." />
  if (state.items.length === 0) {
    return <EmptyState>Nothing in the atlas matches that.</EmptyState>
  }

  return (
    <>
      <p className="sans dim" style={{ fontSize: '0.78rem', margin: '0 0 12px' }}>
        Showing {num(state.items.length)} of {num(state.total)}
      </p>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        {state.items.map((c) => (
          <CreatureCard key={c.slug} creature={c} />
        ))}
      </div>
      {state.items.length < state.total && (
        <div style={{ textAlign: 'center', marginTop: 16 }}>
          <button type="button" className="btn" onClick={loadMore} disabled={more}>
            {more ? 'Loading…' : 'Load more'}
          </button>
        </div>
      )}
    </>
  )
}

// The CONFIGURED altar roster — where the altars are and what each summons. The
// live board ("it is on level 3 right now") is a different page, /site/champs,
// fed by the sidecar. Both exist; they are not the same thing.
function Champions({ facet }) {
  const { loading, error, data } = useAsync(() => api.atlas.champions(facet), [facet])
  if (loading) return <Loading />
  if (error) return <ErrorState message="Could not load the champion altars right now." />
  if (!data || data.length === 0) return <EmptyState>No champion altars are configured.</EmptyState>
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {data.map((champ) => (
        <div key={champ.slug} className="panel" style={{ padding: '13px 15px', display: 'flex', gap: 14, alignItems: 'center' }}>
          <div style={{ minWidth: 0, flex: 1 }}>
            <div className="display" style={{ fontSize: '0.98rem', color: 'var(--head)' }}>
              {champ.label || champ.name}
            </div>
            <div className="sans dim" style={{ fontSize: '0.74rem', marginTop: 3 }}>
              {champ.facet}
              {champ.group ? ` · ${champ.group}` : ''} · {champ.x}, {champ.y}
            </div>
          </div>
          <span className="sans" style={{ flex: 'none', fontSize: '0.76rem', color: 'var(--muted)' }}>
            {champ.randomType ? 'Random champion' : champ.type || '—'}
          </span>
        </div>
      ))}
    </div>
  )
}

// Regions and landmarks together: both answer "where is that?", and splitting
// them into two tabs would make the visitor guess which list a name lives in.
function Places({ q, facet }) {
  const { loading, error, data } = useAsync(
    () => Promise.all([api.atlas.regions({ q, facet }), api.atlas.landmarks({ q, facet })]),
    [q, facet],
  )
  const rows = useMemo(() => {
    if (!data) return []
    const [regions, landmarks] = data
    return [
      ...regions.map((r) => ({ key: `r:${r.facet}:${r.name}`, name: r.name, facet: r.facet, detail: r.parent || r.type || 'Region', kind: 'Region' })),
      ...landmarks.map((l) => ({ key: `l:${l.facet}:${l.group || ''}:${l.name}:${l.x}:${l.y}`, name: l.group ? `${l.group} — ${l.name}` : l.name, facet: l.facet, detail: `${l.x}, ${l.y}`, kind: 'Landmark' })),
    ].sort((a, b) => a.name.localeCompare(b.name))
  }, [data])

  if (loading) return <Loading />
  if (error) return <ErrorState message="Could not load places right now." />
  if (rows.length === 0) return <EmptyState>No regions or landmarks match that.</EmptyState>
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {rows.map((row) => (
        <div key={row.key} className="panel" style={{ padding: '10px 14px', display: 'flex', gap: 12, alignItems: 'baseline' }}>
          <span className="sans" style={{ flex: 1, minWidth: 0, color: 'var(--head)', fontSize: '0.88rem' }}>{row.name}</span>
          <span className="sans dim" style={{ fontSize: '0.72rem' }}>{row.facet} · {row.detail}</span>
          <span className="sans dim" style={{ fontSize: '0.66rem', letterSpacing: '0.06em', flex: 'none' }}>{row.kind}</span>
        </div>
      ))}
    </div>
  )
}

export default function Atlas() {
  const [tab, setTab] = useState('creatures')
  const [input, setInput] = useState('')
  const [q, setQ] = useState('')
  const [facet, setFacet] = useState('')
  const meta = useAsync(() => api.atlas.meta())

  // Debounced: typing "lizardman" should be one request, not nine.
  useEffect(() => {
    const timer = setTimeout(() => setQ(input.trim()), 250)
    return () => clearTimeout(timer)
  }, [input])

  const facets = meta.data?.facets || []
  const counts = meta.data?.counts || null
  const imported = meta.data?.importedAt ? new Date(meta.data.importedAt) : null

  return (
    <PublicLayout section="website">
      <div className="shell-narrow page-body">
        <PageHeader
          eyebrow="Bestiary"
          title="Spawn atlas"
          lead="Where everything lives, read straight out of the shard's own spawn files — so it stays accurate whether or not the server is up."
        />

        {/* The atlas is only as good as its placement rate, so the page states
            it rather than implying every spawner resolved to a named place. */}
        {counts && (
          <p className="sans dim" style={{ fontSize: '0.76rem', margin: '-12px 0 18px' }}>
            {num(counts.creatures)} creatures across {num(counts.points)} spawners
            {Number.isFinite(counts.unresolvedPoints) && counts.points
              ? ` · ${Math.round(((counts.points - counts.unresolvedPoints) / counts.points) * 100)}% placed to a named region or landmark`
              : ''}
            {imported ? ` · parsed ${imported.toLocaleDateString()}` : ''}
          </p>
        )}

        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12 }}>
          {TABS.map((t) => (
            <Chip key={t.key} active={tab === t.key} onClick={() => setTab(t.key)}>
              {t.label}
            </Chip>
          ))}
        </div>

        {tab !== 'champions' && (
          <input
            className="input"
            type="search"
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder={tab === 'creatures' ? 'Search creatures…' : 'Search regions and landmarks…'}
            style={{ width: '100%', marginBottom: 12 }}
          />
        )}

        {facets.length > 0 && (
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 18 }}>
            <Chip active={facet === ''} onClick={() => setFacet('')}>
              All facets
            </Chip>
            {facets.map((f) => (
              <Chip key={f} active={facet === f} onClick={() => setFacet(f)}>
                {f}
              </Chip>
            ))}
          </div>
        )}

        {meta.error && <ErrorState message="Could not load the atlas right now." />}
        {!meta.error && !meta.loading && !imported && (
          <EmptyState>The spawn atlas has not been imported yet.</EmptyState>
        )}

        {!meta.error && imported && (
          <>
            {tab === 'creatures' && <Creatures q={q} facet={facet} />}
            {tab === 'champions' && <Champions facet={facet} />}
            {tab === 'places' && <Places q={q} facet={facet} />}
          </>
        )}
      </div>
    </PublicLayout>
  )
}
