import { useCallback, useEffect, useRef, useState } from 'react'
import { MysteryGraphEditor } from './mysteryGraph'

type Pose = { poseKey: string; assetId: string; url: string }
type Npc = { id: string; key: string; name: string; role: string; defaultPose: string | null; poses: Pose[]; inUse: boolean }
type Mystery = { id: string; slug: string; title: string; nodes: number }

async function json<T>(url: string, init?: RequestInit): Promise<T> {
  const response = await fetch(url, init)
  if (!response.ok) throw new Error((await response.json().catch(() => ({}))).error || `Request failed (${response.status})`)
  return response.json()
}

export function AdminPanel() {
  const [isAdmin, setIsAdmin] = useState<boolean | null>(null)
  const [tab, setTab] = useState<'npcs' | 'mysteries' | 'assets'>('npcs')
  const [npcs, setNpcs] = useState<Npc[]>([])
  const [mysteries, setMysteries] = useState<Mystery[]>([])
  const [selectedId, setSelectedId] = useState<string | null>(null)
  const [editingMystery, setEditingMystery] = useState<{ id: string; title: string } | null>(null)
  const [status, setStatus] = useState('')

  useEffect(() => {
    fetch('/api/session').then(r => r.json()).then(session => setIsAdmin(Boolean(session?.isAdmin))).catch(() => setIsAdmin(false))
  }, [])

  const reloadNpcs = useCallback(async (selectKey?: string) => {
    const list = await json<Npc[]>('/api/admin/npcs')
    setNpcs(list)
    setSelectedId(current => selectKey ? (list.find(npc => npc.key === selectKey)?.id ?? current) : (current && list.some(npc => npc.id === current) ? current : list[0]?.id ?? null))
  }, [])
  const reloadMysteries = useCallback(() => json<Mystery[]>('/api/admin/mysteries').then(setMysteries).catch(() => {}), [])

  useEffect(() => {
    if (!isAdmin) return
    reloadNpcs().catch(error => setStatus(String(error.message || error)))
    void reloadMysteries()
  }, [isAdmin, reloadNpcs, reloadMysteries])

  if (isAdmin === null) return <div className="boot"><div className="seal">GU</div><p>GLITCH UNIVERSITY · ADMIN</p><small>AUTHENTICATING…</small></div>
  if (!isAdmin) return <div className="boot"><div className="seal">GU</div><p>ADMINISTRATOR ACCESS REQUIRED</p><small><a className="admin-link" href="/">← RETURN TO TERMINAL</a></small></div>

  const selected = npcs.find(npc => npc.id === selectedId) || null
  return <div className="admin">
    <header className="admin-head">
      <div className="brand"><span className="brand-mark">GU</span><span>OSINT BOARD <em>/ AUTHORING</em></span></div>
      <nav className="admin-tabs">
        <button className={tab === 'npcs' ? 'active' : ''} onClick={() => setTab('npcs')}>NPCS</button>
        <button className={tab === 'mysteries' ? 'active' : ''} onClick={() => setTab('mysteries')}>MYSTERIES</button>
        <button className={tab === 'assets' ? 'active' : ''} onClick={() => setTab('assets')}>ASSETS</button>
      </nav>
      <a className="admin-link" href="/">← TERMINAL</a>
    </header>

    {tab === 'npcs' && <div className="admin-body">
      <aside className="npc-list">
        <div className="npc-list-head"><span>NPC TEMPLATES</span><button onClick={() => createNpc(setStatus, reloadNpcs)}>+ NEW</button></div>
        {npcs.length === 0 && <p className="admin-empty">No NPC templates yet.</p>}
        {npcs.map(npc => <button key={npc.id} className={`npc-row${npc.id === selectedId ? ' selected' : ''}`} onClick={() => setSelectedId(npc.id)}>
          <span className="npc-avatar">{npc.poses[0] ? <img src={npc.poses[0].url} alt="" /> : npc.name.slice(0, 1).toUpperCase()}</span>
          <span className="npc-row-text"><strong>{npc.name || npc.key}</strong><small>{npc.role || npc.key}</small></span>
        </button>)}
      </aside>
      {selected
        ? <NpcEditor key={selected.id} npc={selected} onChanged={reloadNpcs} setStatus={setStatus} />
        : <div className="npc-editor empty">Select or create an NPC.</div>}
    </div>}

    {tab === 'mysteries' && (editingMystery
      ? <MysteryGraphEditor mysteryId={editingMystery.id} title={editingMystery.title} onClose={() => { setEditingMystery(null); void reloadMysteries() }} setStatus={setStatus} />
      : <div className="admin-body">
        <div className="mystery-list">
          <div className="mystery-list-head"><span>MYSTERIES</span><button onClick={() => newMystery(setStatus, reloadMysteries, setEditingMystery)}>+ NEW</button></div>
          {mysteries.length === 0 && <p className="admin-empty">No mysteries yet — create one to begin.</p>}
          {mysteries.map(mystery => <div key={mystery.id} className="mystery-row" onClick={() => setEditingMystery({ id: mystery.id, title: mystery.title })}>
            <div className="mystery-row-main"><strong>{mystery.title}</strong><span>{mystery.slug} · {mystery.nodes} node{mystery.nodes === 1 ? '' : 's'} · edit graph →</span></div>
            <button className="mystery-del" title="Delete mystery" onClick={event => { event.stopPropagation(); void deleteMystery(mystery, setStatus, reloadMysteries) }}>×</button>
          </div>)}
        </div>
      </div>)}

    {tab === 'assets' && <AssetStore setStatus={setStatus} />}

    <footer className="admin-foot">{status || 'READY'}</footer>
  </div>
}

function slugify(value: string) { return value.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '') }
function formatSize(bytes: number) { return bytes < 1024 ? `${bytes} B` : bytes < 1048576 ? `${(bytes / 1024).toFixed(0)} KB` : `${(bytes / 1048576).toFixed(1)} MB` }

async function newMystery(setStatus: (m: string) => void, reload: () => Promise<void>, open: (m: { id: string; title: string }) => void) {
  const title = window.prompt('Mystery title (e.g. The Glass Harbour Diversion)')?.trim()
  if (!title) return
  const slug = window.prompt('URL slug', slugify(title))?.trim()
  if (!slug) return
  try {
    await json('/api/mysteries?edit=1', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ slug, title }) })
    await reload()
    const created = (await json<Mystery[]>('/api/admin/mysteries')).find(m => m.slug === slugify(slug))
    if (created) open({ id: created.id, title: created.title })
    setStatus(`Created ${title}`)
  } catch (error) { setStatus(String((error as Error).message || error)) }
}
async function deleteMystery(mystery: Mystery, setStatus: (m: string) => void, reload: () => Promise<void>) {
  if (!window.confirm(`Delete mystery “${mystery.title}”? This removes its whole story graph.`)) return
  try { await json(`/api/admin/mysteries/${mystery.id}`, { method: 'DELETE' }); await reload(); setStatus(`Deleted ${mystery.title}`) }
  catch (error) { setStatus(String((error as Error).message || error)) }
}

type Asset = { id: string; originalName: string; mimeType: string; byteSize: number; url: string }
function AssetStore({ setStatus }: { setStatus: (m: string) => void }) {
  const [assets, setAssets] = useState<Asset[]>([])
  const [busy, setBusy] = useState(false)
  const fileRef = useRef<HTMLInputElement>(null)
  const reload = useCallback(() => json<Asset[]>('/api/admin/assets').then(setAssets).catch(error => setStatus(String((error as Error).message || error))), [setStatus])
  useEffect(() => { void reload() }, [reload])

  const upload = async (files: FileList) => {
    setBusy(true)
    try {
      for (const file of Array.from(files)) { const form = new FormData(); form.append('file', file); await json('/api/admin/assets', { method: 'POST', body: form }) }
      if (fileRef.current) fileRef.current.value = ''
      await reload(); setStatus(`Uploaded ${files.length} file${files.length === 1 ? '' : 's'}`)
    } catch (error) { setStatus(String((error as Error).message || error)) } finally { setBusy(false) }
  }
  const remove = async (asset: Asset) => {
    if (!window.confirm(`Delete ${asset.originalName}?`)) return
    try { await json(`/api/admin/assets/${asset.id}`, { method: 'DELETE' }); await reload(); setStatus('Deleted') }
    catch (error) { setStatus(String((error as Error).message || error)) }
  }
  const copy = (text: string) => { void navigator.clipboard?.writeText(text); setStatus(`Copied ${text}`) }

  return <div className="admin-body">
    <div className="asset-store">
      <div className="mystery-list-head"><span>ASSET LIBRARY · images · audio · pdf</span>
        <label className="asset-upload-btn">{busy ? 'UPLOADING…' : '+ UPLOAD'}
          <input ref={fileRef} type="file" accept="image/*,audio/*,application/pdf" multiple onChange={event => { if (event.target.files?.length) void upload(event.target.files) }} />
        </label>
      </div>
      {assets.length === 0 && <p className="admin-empty">No assets yet. Upload images, audio, or PDFs.</p>}
      <div className="asset-grid">
        {assets.map(asset => <figure key={asset.id} className="asset-card">
          <div className="asset-thumb">{asset.mimeType.startsWith('image/')
            ? <img src={asset.url} alt="" />
            : <span className="asset-icon">{asset.mimeType.startsWith('audio/') ? '♪' : asset.mimeType === 'application/pdf' ? 'PDF' : 'FILE'}</span>}</div>
          <figcaption title={asset.originalName}>{asset.originalName}</figcaption>
          <small>{formatSize(asset.byteSize)}</small>
          <div className="asset-actions">
            <button onClick={() => copy(asset.id)} title="Copy asset id">id</button>
            <button onClick={() => copy(asset.url)} title="Copy URL">url</button>
            <button className="asset-del" onClick={() => remove(asset)} title="Delete (blocked if in use)">×</button>
          </div>
        </figure>)}
      </div>
    </div>
  </div>
}

async function createNpc(setStatus: (message: string) => void, reload: (key?: string) => Promise<void>) {
  const name = window.prompt('NPC display name (e.g. Prof. Almira Vetch)')?.trim()
  if (!name) return
  const key = window.prompt('Short key (e.g. professor)', name.toLowerCase().split(/\s+/).pop() || '')?.trim()
  if (!key) return
  try {
    await json('/api/admin/npcs', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ key, name }) })
    await reload(key.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, ''))
    setStatus(`Created ${name}`)
  } catch (error) { setStatus(String((error as Error).message || error)) }
}

function NpcEditor({ npc, onChanged, setStatus }: { npc: Npc; onChanged: (key?: string) => Promise<void>; setStatus: (message: string) => void }) {
  const [name, setName] = useState(npc.name)
  const [role, setRole] = useState(npc.role)
  const [defaultPose, setDefaultPose] = useState(npc.defaultPose || '')
  const [poseKey, setPoseKey] = useState('')
  const [busy, setBusy] = useState(false)
  const fileRef = useRef<HTMLInputElement>(null)
  const dirty = name !== npc.name || role !== npc.role || (defaultPose || null) !== npc.defaultPose

  const save = async () => {
    setBusy(true)
    try {
      await json(`/api/admin/npcs/${npc.id}`, { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name, role, defaultPose: defaultPose || null }) })
      await onChanged(npc.key); setStatus(`Saved ${name}`)
    } catch (error) { setStatus(String((error as Error).message || error)) } finally { setBusy(false) }
  }
  const uploadPose = async (file: File) => {
    const key = (poseKey || file.name.replace(/\.[^.]+$/, '')).trim()
    setBusy(true)
    try {
      const form = new FormData(); form.append('file', file); form.append('poseKey', key)
      await json(`/api/admin/npcs/${npc.id}/poses`, { method: 'POST', body: form })
      setPoseKey(''); if (fileRef.current) fileRef.current.value = ''
      await onChanged(npc.key); setStatus(`Uploaded pose “${key}”`)
    } catch (error) { setStatus(String((error as Error).message || error)) } finally { setBusy(false) }
  }
  const removePose = async (key: string) => {
    if (!window.confirm(`Remove pose “${key}”?`)) return
    try { await json(`/api/admin/npcs/${npc.id}/poses/${encodeURIComponent(key)}`, { method: 'DELETE' }); await onChanged(npc.key) }
    catch (error) { setStatus(String((error as Error).message || error)) }
  }
  const remove = async () => {
    if (!window.confirm(`Delete NPC ${npc.name}? This cannot be undone.`)) return
    try { await json(`/api/admin/npcs/${npc.id}`, { method: 'DELETE' }); await onChanged() }
    catch (error) { setStatus(String((error as Error).message || error)) }
  }

  return <section className="npc-editor">
    <div className="npc-editor-head">
      <h2>{npc.name || npc.key}</h2>
      <code>{npc.key}</code>
      <button className="danger" disabled={npc.inUse} title={npc.inUse ? 'Used by a cutscene' : 'Delete NPC'} onClick={remove}>DELETE</button>
    </div>
    <div className="admin-field"><label>Display name</label><input value={name} onChange={event => setName(event.target.value)} /></div>
    <div className="admin-field"><label>Role / affiliation</label><input value={role} onChange={event => setRole(event.target.value)} placeholder="Glitch University · Investigative Method" /></div>
    <div className="admin-field"><label>Default pose</label>
      <select value={defaultPose} onChange={event => setDefaultPose(event.target.value)}>
        <option value="">— none —</option>
        {npc.poses.map(pose => <option key={pose.poseKey} value={pose.poseKey}>{pose.poseKey}</option>)}
      </select>
    </div>
    <button className="admin-save" disabled={!dirty || busy} onClick={save}>{busy ? 'SAVING…' : dirty ? 'SAVE CHANGES' : 'SAVED'}</button>

    <h3>Poses</h3>
    <div className="pose-grid">
      {npc.poses.map(pose => <figure key={pose.poseKey} className={`pose-card${pose.poseKey === defaultPose ? ' is-default' : ''}`}>
        <img src={pose.url} alt={pose.poseKey} />
        <figcaption>{pose.poseKey}</figcaption>
        <button className="pose-remove" title="Remove pose" onClick={() => removePose(pose.poseKey)}>×</button>
      </figure>)}
      {npc.poses.length === 0 && <p className="admin-empty">No poses yet. Upload a portrait below.</p>}
    </div>
    <div className="pose-upload">
      <input className="pose-key" value={poseKey} onChange={event => setPoseKey(event.target.value)} placeholder="pose key (e.g. neutral)" />
      <input ref={fileRef} type="file" accept="image/*" onChange={event => { const file = event.target.files?.[0]; if (file) void uploadPose(file) }} />
    </div>
    <small className="admin-hint">Poses referenced in dialogue fall back to the default pose, then to no artwork. Upload big portraits — they fill the screen in cutscenes.</small>
  </section>
}
