import { useCallback, useEffect, useState } from 'react'
import { Loading, ErrorState } from '../../../components/PageState.jsx'
import { api } from '../../../api/client.js'

// ── Admin · Shard visibility ────────────────────────────────────────────────
//
// Who may see which shard surface, and which sensitive fields within it.
// Admin-only, because this decides what ANONYMOUS visitors get.
//
// Two things the UI must communicate honestly, because they are not negotiable
// server-side (see docs/link/v3.md §3.4):
//   • acct / webId are admin-only always and are not listed as editable fields.
//   • an event kind the server doesn't know about never reaches anyone below
//     admin, whatever is set here.
//
// Defaults reproduce the behavior the site had before this panel existed, so a
// fresh install shows "everything as it was" rather than an empty form.

const RUNG_LABEL = {
  anonymous: 'Everyone',
  logged_in: 'Signed in',
  player: 'Linked players',
  staff: 'Staff',
  admin: 'Admins only',
}

const RUNG_HINT = {
  anonymous: 'Visible to anyone, signed in or not.',
  logged_in: 'Any signed-in account, linked or not.',
  player: 'Accounts with a linked game account. Staff always qualify.',
  staff: 'Admins and moderators.',
  admin: 'Admins only.',
}

const FEATURE_LABEL = {
  status: 'Shard status',
  activity: 'Activity feed',
  champs: 'Champion spawns',
  guilds: 'Guilds',
  governors: 'Town governors',
  houses: 'Houses / IDOC',
  presence: 'Players online',
  ruleset: 'Shard rules',
  atlas: 'Spawn atlas',
  leaderboards: 'Leaderboards',
  market: 'Marketplace',
}

const FEATURE_HINT = {
  status: 'Connection state, online count, gold-supply series.',
  activity: 'Deaths, kills, skill gains, quests, logins.',
  champs: 'The live champion / mini-champ / sea-boss board.',
  guilds: 'Guild rosters, alliances and leaders.',
  governors: 'City Loyalty governors, elections and term history.',
  houses: 'Houses in danger (IDOC). Owner and price are separate fields below.',
  presence: 'Population aggregate and the staff-online widget.',
  ruleset: 'Skill/stat caps, house limits, vet rewards and the rest of the ruleset.',
  atlas: 'The spawn atlas and bestiary. Static shard content, not live state.',
  leaderboards: 'Point and loyalty standings across every points system.',
  market: 'The shard-wide player-vendor index.',
}

const FIELD_LABEL = {
  owner: 'House owner',
  price: 'House price',
  location: 'In-game location (map + coordinates)',
  connect: 'Server connect address',
  // Keyed on the WIRE field, which for a leaderboard entry is `name` — the
  // projection matches literal JSON keys, so the rule cannot be spelled after the
  // field's meaning. The label is what carries the meaning to the admin.
  name: 'Character names on leaderboards',
  ownerName: 'Vendor owner name',
  // One rule, one key — `location` is a nested object on both the wire frame and
  // the stored read model precisely so that hiding it takes the facet, the
  // coordinates, the region and the house together.
  ownerSerial: 'Vendor owner character id',
}

function RungSelect({ value, onChange, ladder, disabled }) {
  return (
    <select
      className="input"
      value={value}
      disabled={disabled}
      onChange={(e) => onChange(e.target.value)}
      style={{ maxWidth: 200 }}
    >
      {ladder.map((rung) => (
        <option key={rung} value={rung}>
          {RUNG_LABEL[rung] || rung}
        </option>
      ))}
    </select>
  )
}

function FeatureRow({ name, settings, defaults, ladder, onPatch }) {
  const fields = Object.entries(settings.fields || {})
  const changed =
    defaults &&
    (settings.enabled !== defaults.enabled ||
      settings.audience !== defaults.audience ||
      settings.stream !== defaults.stream ||
      JSON.stringify(settings.fields) !== JSON.stringify(defaults.fields))

  return (
    <div
      style={{
        border: '1px solid var(--line)',
        borderRadius: 10,
        padding: 16,
        display: 'flex',
        flexDirection: 'column',
        gap: 12,
        opacity: settings.enabled ? 1 : 0.62,
      }}
    >
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
        <div style={{ minWidth: 0 }}>
          <h3 className="display" style={{ margin: 0, fontSize: '1rem', color: 'var(--head)' }}>
            {FEATURE_LABEL[name] || name}
            {changed && (
              <span
                className="sans"
                style={{ marginLeft: 8, fontSize: '0.62rem', letterSpacing: '0.06em', textTransform: 'uppercase', color: 'var(--accent)' }}
              >
                changed
              </span>
            )}
          </h3>
          <p className="sans" style={{ margin: '4px 0 0', fontSize: '0.82rem', color: 'var(--muted)', lineHeight: 1.5 }}>
            {FEATURE_HINT[name]}
          </p>
        </div>
        <label
          className="sans"
          style={{ flex: 'none', display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: '0.86rem', color: 'var(--ink)' }}
        >
          <input
            type="checkbox"
            checked={settings.enabled}
            onChange={(e) => onPatch(name, { enabled: e.target.checked })}
          />
          Enabled
        </label>
      </div>

      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 20, alignItems: 'flex-end' }}>
        <label style={{ display: 'block' }}>
          <span className="field-label">Who can see it</span>
          <RungSelect
            value={settings.audience}
            ladder={ladder}
            disabled={!settings.enabled}
            onChange={(audience) => onPatch(name, { audience })}
          />
          <span className="sans dim" style={{ display: 'block', marginTop: 4, fontSize: '0.75rem' }}>
            {RUNG_HINT[settings.audience]}
          </span>
        </label>
        <label
          className="sans"
          style={{ display: 'inline-flex', alignItems: 'center', gap: 8, cursor: 'pointer', fontSize: '0.86rem', color: 'var(--ink)', paddingBottom: 22 }}
        >
          <input
            type="checkbox"
            checked={settings.stream}
            disabled={!settings.enabled}
            onChange={(e) => onPatch(name, { stream: e.target.checked })}
          />
          Live updates
        </label>
      </div>

      {fields.length > 0 && (
        <div style={{ borderTop: '1px solid var(--line-soft)', paddingTop: 12 }}>
          <span className="field-label" style={{ display: 'block', marginBottom: 8 }}>
            Sensitive fields
          </span>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16 }}>
            {fields.map(([field, rung]) => (
              <label key={field} style={{ display: 'block' }}>
                <span className="sans dim" style={{ display: 'block', fontSize: '0.78rem', marginBottom: 4 }}>
                  {FIELD_LABEL[field] || field}
                </span>
                <RungSelect
                  value={rung}
                  ladder={ladder}
                  disabled={!settings.enabled}
                  onChange={(level) =>
                    onPatch(name, { fieldRules: { ...settings.fields, [field]: level } })
                  }
                />
              </label>
            ))}
          </div>
        </div>
      )}
    </div>
  )
}

export default function ShardVisibility() {
  const [config, setConfig] = useState(null)
  const [defaults, setDefaults] = useState(null)
  const [ladder, setLadder] = useState([])
  const [lockedFields, setLockedFields] = useState([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState('')
  const [saving, setSaving] = useState(false)
  const [msg, setMsg] = useState('')

  const load = useCallback(async () => {
    setLoading(true)
    setError('')
    try {
      const data = await api.admin.getShardVisibility()
      setConfig(data.features)
      setDefaults(data.defaults)
      setLadder(data.ladder || [])
      setLockedFields(data.lockedFields || [])
    } catch (err) {
      setError(err.message || 'Could not load visibility settings.')
    } finally {
      setLoading(false)
    }
  }, [])

  useEffect(() => {
    load()
  }, [load])

  function patch(name, changes) {
    setMsg('')
    setConfig((prev) => {
      const next = { ...prev[name], ...changes }
      // `fieldRules` in the API is `fields` in the effective config.
      if (changes.fieldRules) {
        next.fields = changes.fieldRules
        delete next.fieldRules
      }
      return { ...prev, [name]: next }
    })
  }

  async function save() {
    setSaving(true)
    setMsg('')
    setError('')
    try {
      const body = {}
      for (const [name, s] of Object.entries(config)) {
        body[name] = {
          enabled: s.enabled,
          audience: s.audience,
          stream: s.stream,
          fieldRules: s.fields || {},
        }
      }
      const data = await api.admin.saveShardVisibility(body)
      setConfig(data.features)
      setMsg('Saved. Changes take effect within a few seconds, including on open live streams.')
    } catch (err) {
      setError(err.message || 'Could not save.')
    } finally {
      setSaving(false)
    }
  }

  function resetToDefaults() {
    setMsg('')
    setConfig(structuredClone(defaults))
  }

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

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
      <header>
        <h2 className="display" style={{ margin: 0, fontSize: '1.3rem', color: 'var(--head)' }}>
          Shard visibility
        </h2>
        <p className="sans" style={{ margin: '6px 0 0', color: 'var(--muted)', fontSize: '0.88rem', lineHeight: 1.6, maxWidth: 760 }}>
          Choose who can see each shard surface on the public site, and how much detail they get.
          Turning a feature off hides it entirely — its pages return “not found” rather than
          revealing that it exists. “Live updates” controls whether the feature streams changes in
          real time; the pages still work without it, they just refresh on load.
        </p>
        {lockedFields.length > 0 && (
          <p className="sans dim" style={{ margin: '8px 0 0', fontSize: '0.82rem', lineHeight: 1.6, maxWidth: 760 }}>
            Not configurable: <strong style={{ color: 'var(--ink)' }}>{lockedFields.join(', ')}</strong> —
            game account names and website user ids are never shown below admin, on any surface. They
            aren’t visible in game either, so publishing them would disclose something the shard
            itself doesn’t.
          </p>
        )}
      </header>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
        {Object.entries(config).map(([name, settings]) => (
          <FeatureRow
            key={name}
            name={name}
            settings={settings}
            defaults={defaults?.[name]}
            ladder={ladder}
            onPatch={patch}
          />
        ))}
      </div>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
        <button onClick={save} disabled={saving} className="btn btn-primary btn-sq">
          {saving ? 'Saving…' : 'Save changes'}
        </button>
        <button onClick={resetToDefaults} disabled={saving} className="btn btn-sq">
          Restore defaults
        </button>
        {msg && <span className="sans" style={{ color: '#7fd0a4', fontSize: '0.85rem' }}>{msg}</span>}
        {error && <span className="sans" style={{ color: '#d98b84', fontSize: '0.85rem' }}>{error}</span>}
      </div>
    </div>
  )
}
