import { useEffect, useMemo, useState } from 'react'
import { Loading, ErrorState } from '../../../components/PageState.jsx'
import { api } from '../../../api/client.js'
import { useSite } from '../../../contexts/SiteContext.jsx'
import { parseJsonSetting } from '../../../lib/settingsJson.js'
import BrandAssetsPanel from './BrandAssetsPanel.jsx'

// Admin · Appearance — the theme and brand-asset halves of
// docs/website/THEMING_AND_NAV.md (phases 3-5). The nav builder is phase 7 and
// gets its own screen.
//
// Two things shape this form:
//
//   • Every control is a closed set. The presets, the font shortlist and the
//     shadow depths all come from GET /settings/theme/options, which is derived
//     from the same server config the save is validated against — so the form
//     can never offer a value the server would reject. Nothing here is free
//     text except the color inputs, which are <input type="color"> and so are
//     hex by construction.
//   • Saving means writing a settings row; resetting means DELETING it. Absence
//     of the row is what selects the shipped default, so "reset" cannot write a
//     copy of the defaults — see §2.

// Human labels for the eight editable colors and four radii. The field names
// and the CSS variables they drive both come from the server
// (colorFields / radiusFields); this only decorates them, and a field with no
// label here still renders under its raw name rather than vanishing.
const COLOR_LABELS = {
  bg: 'Background',
  bgDeep: 'Background (deep)',
  panelA: 'Panel (top)',
  panelB: 'Panel (bottom)',
  accent: 'Accent',
  accentBright: 'Accent (bright)',
  ink: 'Ink / headings',
  text: 'Body text',
}
const RADIUS_LABELS = {
  radiusPill: 'Pills & buttons',
  radiusPanel: 'Flat panels',
  radiusCard: 'Cards & panels',
  radiusInput: 'Inputs & notes',
}
const FONT_LABELS = {
  serif: 'Body serif',
  display: 'Display / headings',
  sans: 'Interface sans',
}

// Strip empty groups so a theme the admin cleared back out is stored as a bare
// preset rather than as `{colors:{}, fonts:{}, structure:{}}`. Never null a
// field out to "clear" it — remove it (§6.1).
function compactCustom(custom) {
  const out = {}
  for (const [group, fields] of Object.entries(custom)) {
    const kept = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== '' && v != null))
    if (Object.keys(kept).length) out[group] = kept
  }
  return Object.keys(out).length ? out : null
}

export default function AppearanceAdmin() {
  const { refresh: refreshSite } = useSite()
  const [options, setOptions] = useState(null)
  const [preset, setPreset] = useState('runic-gateway')
  const [custom, setCustom] = useState({ colors: {}, fonts: {}, structure: {} })
  // Whether a theme_visual row exists at all. Drives the "reset" button and the
  // "this instance is using the shipped theme" note — an admin needs to be able
  // to tell "never themed" from "themed to look like the default".
  const [stored, setStored] = useState(false)
  // The brand-asset overrides, read in the same settings fetch and then owned by
  // the panel below (its uploads save on their own, so it does not share this
  // screen's Save button).
  const [assets, setAssets] = useState(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState('')
  const [busy, setBusy] = useState(false)
  const [saved, setSaved] = useState(false)

  useEffect(() => {
    let active = true
    Promise.all([api.themeOptions(), api.admin.getSettings()])
      .then(([opts, all]) => {
        if (!active) return
        setOptions(opts)
        // The stored values are JSON strings (settings.value is TEXT), and a
        // malformed one reads as absent exactly as the server treats it — the
        // form then shows the shipped default rather than an error.
        const parsed = parseJsonSetting(all.theme_visual)
        setStored(Boolean(all.theme_visual))
        setAssets(parseJsonSetting(all.brand_assets) || {})
        if (parsed) {
          setPreset(parsed.preset || 'runic-gateway')
          setCustom({
            colors: parsed.custom?.colors || {},
            fonts: parsed.custom?.fonts || {},
            structure: parsed.custom?.structure || {},
          })
        }
      })
      .catch(() => active && setError('Could not load the appearance settings.'))
      .finally(() => active && setLoading(false))
    return () => {
      active = false
    }
  }, [])

  // What an unset field currently resolves to: the selected preset's palette,
  // or the shipped theme when the preset is Custom (which has no base). Lets a
  // color picker open on the value the admin is actually looking at.
  const baseTokens = useMemo(() => {
    if (!options) return {}
    return options.presets.find((p) => p.id === preset)?.tokens || options.shippedTokens
  }, [options, preset])

  if (loading) return <Loading />
  if (error && !options) return <ErrorState message={error} />

  const setField = (group, field) => (value) => {
    setCustom((c) => ({ ...c, [group]: { ...c[group], [field]: value } }))
    setSaved(false)
  }
  const clearField = (group, field) => () => {
    setCustom((c) => {
      const next = { ...c[group] }
      delete next[field]
      return { ...c, [group]: next }
    })
    setSaved(false)
  }

  async function save() {
    setBusy(true)
    setError('')
    try {
      await api.admin.updateSettings({ theme_visual: { preset, custom: compactCustom(custom) } })
      setStored(true)
      setSaved(true)
      // Repull the public settings so the surrounding admin UI re-themes itself
      // immediately — the admin sees the change they just made.
      await refreshSite()
    } catch (err) {
      setError(err.message || 'Could not save the theme.')
    } finally {
      setBusy(false)
    }
  }

  async function resetAll() {
    setBusy(true)
    setError('')
    try {
      await api.admin.resetSetting('theme_visual')
      setPreset('runic-gateway')
      setCustom({ colors: {}, fonts: {}, structure: {} })
      setStored(false)
      setSaved(false)
      await refreshSite()
    } catch (err) {
      setError(err.message || 'Could not reset the theme.')
    } finally {
      setBusy(false)
    }
  }

  return (
    <section style={{ maxWidth: 720, display: 'flex', flexDirection: 'column', gap: 26 }}>
      <p className="sans dim" style={{ margin: 0, fontSize: '0.82rem', lineHeight: 1.7 }}>
        Colors, fonts and corner radius for the public site, this admin panel and the player portal.
        {' '}
        {stored ? (
          <>This instance has a saved theme. <strong style={{ color: 'var(--muted)' }}>Reset to default</strong> deletes it and returns to the shipped look.</>
        ) : (
          <>This instance has never been themed, so it uses the shipped look and its <code>BRAND_*</code> accent.</>
        )}
      </p>

      {/* ── Preset ─────────────────────────────────────────────── */}
      <div>
        <span className="field-label">Preset</span>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 10, marginTop: 8 }}>
          {options.presets.map((p) => (
            <button
              key={p.id}
              type="button"
              onClick={() => {
                setPreset(p.id)
                setSaved(false)
              }}
              className="sans"
              style={{
                display: 'flex',
                alignItems: 'center',
                gap: 10,
                padding: '10px 14px',
                borderRadius: 'var(--radius-input)',
                border: `1px solid ${preset === p.id ? 'var(--accent)' : 'var(--line)'}`,
                background: preset === p.id ? 'var(--blue)' : 'transparent',
                color: preset === p.id ? 'var(--ink)' : 'var(--muted)',
                cursor: 'pointer',
                fontSize: '0.86rem',
              }}
              aria-pressed={preset === p.id}
            >
              {p.tokens ? (
                <span style={{ display: 'flex', borderRadius: 4, overflow: 'hidden', border: '1px solid var(--line)' }}>
                  {['--bg', '--panel-a', '--accent', '--ink'].map((t) => (
                    <span key={t} style={{ width: 11, height: 18, background: p.tokens[t] }} />
                  ))}
                </span>
              ) : (
                <span style={{ width: 44, height: 18, borderRadius: 4, border: '1px dashed var(--line)' }} />
              )}
              {p.label}
            </button>
          ))}
        </div>
        <span className="sans dim" style={{ display: 'block', marginTop: 8, fontSize: '0.76rem' }}>
          {preset === 'custom'
            ? 'Custom starts from the shipped theme — only the fields you set below change.'
            : 'A preset sets the whole palette. Anything you set below overrides it, field by field.'}
        </span>
      </div>

      {/* ── Colors ─────────────────────────────────────────────── */}
      <div>
        <span className="field-label">Colors</span>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(210px, 1fr))', gap: 12, marginTop: 8 }}>
          {options.colorFields.map(({ name, token }) => {
            const set = custom.colors[name] !== undefined
            return (
              <div key={name} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                {/* <input type="color"> has no empty state, so an unset field
                    shows what it currently resolves to rather than black. */}
                <input
                  type="color"
                  value={custom.colors[name] || baseTokens[token] || '#000000'}
                  onChange={(e) => setField('colors', name)(e.target.value)}
                  aria-label={COLOR_LABELS[name] || name}
                  style={{ width: 34, height: 30, padding: 0, border: '1px solid var(--line)', borderRadius: 6, background: 'transparent', cursor: 'pointer' }}
                />
                <span className="sans" style={{ flex: 1, fontSize: '0.82rem', color: set ? 'var(--ink)' : 'var(--dim)' }}>
                  {COLOR_LABELS[name] || name}
                </span>
                {set && (
                  <button type="button" onClick={clearField('colors', name)} className="sans" title="Follow the preset again" style={linkBtn}>
                    clear
                  </button>
                )}
              </div>
            )
          })}
        </div>
        <span className="sans dim" style={{ display: 'block', marginTop: 8, fontSize: '0.76rem' }}>
          A color you have not set follows the preset. “Live” and “maintenance” status colors are never themed — green has to keep meaning live.
        </span>
      </div>

      {/* ── Fonts ──────────────────────────────────────────────── */}
      <div>
        <span className="field-label">Fonts</span>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 8 }}>
          {Object.keys(options.fonts).map((role) => (
            <label key={role} style={{ display: 'block' }}>
              <span className="sans dim" style={{ display: 'block', fontSize: '0.76rem', marginBottom: 4 }}>
                {FONT_LABELS[role] || role}
              </span>
              <select
                className="select"
                value={custom.fonts[role] || ''}
                onChange={(e) => (e.target.value ? setField('fonts', role)(e.target.value) : clearField('fonts', role)())}
              >
                <option value="">Follow the preset</option>
                {options.fonts[role].map((o) => (
                  <option key={o.value} value={o.value}>
                    {o.label}
                  </option>
                ))}
              </select>
            </label>
          ))}
        </div>
      </div>

      {/* ── Structure ──────────────────────────────────────────── */}
      <div>
        <span className="field-label">Corners &amp; depth</span>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(210px, 1fr))', gap: 12, marginTop: 8 }}>
          {options.radiusFields.map(({ name, token }) => (
            <label key={name} style={{ display: 'block' }}>
              <span className="sans dim" style={{ display: 'block', fontSize: '0.76rem', marginBottom: 4 }}>
                {RADIUS_LABELS[name] || name}
              </span>
              <input
                className="input"
                type="number"
                min="0"
                max={options.radiusMaxPx}
                placeholder={(baseTokens[token] || '').replace('px', '')}
                value={(custom.structure[name] || '').replace('px', '')}
                onChange={(e) =>
                  e.target.value === ''
                    ? clearField('structure', name)()
                    : setField('structure', name)(`${Math.min(Math.max(parseInt(e.target.value, 10) || 0, 0), options.radiusMaxPx)}px`)
                }
              />
            </label>
          ))}
        </div>
        <label style={{ display: 'block', marginTop: 12 }}>
          <span className="sans dim" style={{ display: 'block', fontSize: '0.76rem', marginBottom: 4 }}>
            Card shadow
          </span>
          <select
            className="select"
            value={custom.structure.shadowDepth || ''}
            onChange={(e) => (e.target.value ? setField('structure', 'shadowDepth')(e.target.value) : clearField('structure', 'shadowDepth')())}
          >
            <option value="">Follow the preset</option>
            {options.shadows.map((o) => (
              <option key={o.value} value={o.value}>
                {o.label}
              </option>
            ))}
          </select>
        </label>
      </div>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
        <button onClick={save} disabled={busy} className="btn btn-primary btn-sq">
          {busy ? 'Saving…' : 'Save theme'}
        </button>
        <button onClick={resetAll} disabled={busy || !stored} className="pill" title={stored ? 'Delete the saved theme' : 'Nothing to reset'}>
          Reset to default
        </button>
        {saved && <span className="sans" style={{ color: '#7fd0a4', fontSize: '0.85rem' }}>Saved.</span>}
        {error && <span className="sans" style={{ color: '#d98b84', fontSize: '0.85rem' }}>{error}</span>}
      </div>

      <p className="sans dim" style={{ margin: 0, fontSize: '0.76rem', lineHeight: 1.7 }}>
        The accent reaches the mobile app and the Discord bot too — both theme themselves from this
        site’s public branding.
      </p>

      {/* ── Brand assets ───────────────────────────────────────── */}
      <BrandAssetsPanel initial={assets || {}} />
    </section>
  )
}

const linkBtn = {
  border: 'none',
  background: 'transparent',
  color: 'var(--accent)',
  fontSize: '0.72rem',
  cursor: 'pointer',
  padding: 0,
}
