import { useState, useEffect } from 'react'
import { CheckCircle, XCircle, RefreshCw, ChevronDown, ChevronUp, Clock, Folder, FolderOpen, ChevronRight } from 'lucide-react'
import { Sidebar } from '../components/Sidebar'
import type { User } from '../types'

const MASKED = '••••••••'

interface Integration {
  slug: string
  name: string
  config: Record<string, string>
  secrets: Record<string, string | null>
  enabled: boolean
  updated_at: string
}

interface RoomCategory {
  id: string
  name: string
  sort_order: number
  colour: string | null
}

interface RoomsData {
  categories: RoomCategory[]
  sites: { id: string; name: string; category_id: string }[]
  synced_at: string
}

const FIELD_LABELS: Record<string, string> = {
  region: 'Region', username: 'Username', api_key: 'API Key', password: 'Password',
  base_url: 'Base URL', client_secret: 'Client Secret', tenant_id: 'Tenant ID',
  client_id: 'Client ID', host: 'Host', port: 'Port', database: 'Database',
  graphql_endpoint: 'GraphQL Endpoint', bearer_token: 'Bearer Token',
  email: 'Email', sync_hours: 'Sync Interval (hours)', user: 'Username', pass: 'Password',
  location_id: 'Default Location', from: 'From Address', reply_to: 'Reply-To Address',
  backup_path: 'Backup Directory',
}

interface AppRow {
  id: number
  slug: string
  name: string
  theme_color: string
  category: string | null
  active: boolean
  max_session_hours: number | null
}

export function AdminSettings({ user }: { user: User }) {
  const [tab, setTab] = useState<'integrations' | 'rooms' | 'apps' | 'device'>('integrations')
  const [integrations, setIntegrations] = useState<Integration[]>([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetch('/settings/api/integrations', { credentials: 'include' })
      .then(r => r.json()).then(setIntegrations).finally(() => setLoading(false))
  }, [])

  return (
    <div style={{ display: 'flex', height: '100dvh' }}>
      <Sidebar user={user} />
      <main style={{ flex: 1, overflowY: 'auto', padding: '2rem' }}>
        <h1 style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--text-dark)', marginBottom: '0.25rem' }}>
          Settings
        </h1>
        <p style={{ fontSize: '0.85rem', color: 'var(--text-mid)', marginBottom: '1.75rem' }}>
          Third-party integrations and global configuration
        </p>

        <div style={{ display: 'flex', gap: '0.25rem', marginBottom: '1.75rem', borderBottom: '1px solid var(--card-border)', paddingBottom: '0' }}>
          {(['integrations', 'rooms', 'apps', 'device'] as const).map(t => (
            <button key={t} onClick={() => setTab(t)} style={{
              background: 'none', border: 'none', padding: '0.5rem 1rem',
              fontSize: '0.85rem', fontWeight: 600, cursor: 'pointer',
              color: tab === t ? 'var(--navy)' : 'var(--text-mid)',
              borderBottom: tab === t ? '2px solid var(--navy)' : '2px solid transparent',
              marginBottom: '-1px',
            }}>
              {t === 'integrations' ? 'Integrations' : t === 'rooms' ? 'Rooms & Sites' : t === 'apps' ? 'App Settings' : 'This Device'}
            </button>
          ))}
        </div>

        {tab === 'integrations' && (
          loading
            ? <p style={{ color: 'var(--text-mid)' }}>Loading…</p>
            : <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', maxWidth: '640px' }}>
                {integrations.map(intg => (
                  <IntegrationCard key={intg.slug} integration={intg} onSaved={updated =>
                    setIntegrations(prev => prev.map(i => i.slug === updated.slug ? updated : i))
                  } />
                ))}
              </div>
        )}

        {tab === 'rooms' && <RoomsTab />}
        {tab === 'apps' && <AppsTab />}
        {tab === 'device' && <DeviceTab />}
      </main>
    </div>
  )
}

function IntegrationCard({ integration, onSaved }: { integration: Integration; onSaved: (i: Integration) => void }) {
  const [open, setOpen] = useState(false)
  const [form, setForm] = useState<Record<string, string>>({})
  const [changing, setChanging] = useState<Set<string>>(new Set())
  const [saving, setSaving] = useState(false)
  const [testing, setTesting] = useState(false)
  const [testResult, setTestResult] = useState<{ ok: boolean; error?: string } | null>(null)
  const [enabled, setEnabled] = useState(integration.enabled)
  const [wfLocations, setWfLocations] = useState<{ id: string; name: string; short_name: string | null }[]>([])
  const [fetchingLocs, setFetchingLocs] = useState(false)
  const [locError, setLocError] = useState<string | null>(null)
  const [ncBrowseOpen, setNcBrowseOpen] = useState(false)
  const [ncBrowsePath, setNcBrowsePath] = useState('/')
  const [ncBrowseDirs, setNcBrowseDirs] = useState<{ name: string; path: string }[]>([])
  const [ncBrowseLoading, setNcBrowseLoading] = useState(false)
  const [ncBrowseError, setNcBrowseError] = useState<string | null>(null)

  function openForm() {
    const initial: Record<string, string> = {}
    for (const [k, v] of Object.entries(integration.config)) initial[k] = v ?? ''
    setForm(initial)
    setChanging(new Set())
    setTestResult(null)
    setNcBrowseOpen(false)
    setOpen(true)
  }

  async function ncBrowse(path: string) {
    setNcBrowseLoading(true)
    setNcBrowseError(null)
    setNcBrowsePath(path)
    try {
      const res = await fetch(`/settings/api/integrations/nextcloud/browse?path=${encodeURIComponent(path)}`, {
        credentials: 'include',
      })
      const data = await res.json()
      if (!res.ok) { setNcBrowseError(data.error || `Error ${res.status}`); return }
      setNcBrowseDirs(data.dirs)
    } catch {
      setNcBrowseError('Network error')
    } finally {
      setNcBrowseLoading(false)
    }
  }

  function setField(key: string, value: string) {
    setForm(prev => ({ ...prev, [key]: value }))
  }

  function startChangingSecret(key: string) {
    setChanging(prev => new Set([...prev, key]))
    setForm(prev => ({ ...prev, [key]: '' }))
  }

  async function save() {
    setSaving(true)
    const body: Record<string, unknown> = { ...form, enabled }
    // Include secret fields only if user is actively changing them
    for (const [k, v] of Object.entries(integration.secrets)) {
      if (changing.has(k)) body[k] = form[k] ?? ''
      // else omit — backend keeps existing encrypted value
    }
    const res = await fetch(`/settings/api/integrations/${integration.slug}`, {
      method: 'PUT', credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    })
    setSaving(false)
    if (res.ok) { onSaved(await res.json()); setOpen(false) }
  }

  async function test() {
    setTesting(true)
    setTestResult(null)
    const res = await fetch(`/settings/api/integrations/${integration.slug}/test`, {
      method: 'POST', credentials: 'include',
    })
    setTestResult(await res.json())
    setTesting(false)
  }

  const isConfigured = Object.values(integration.secrets).some(v => v === MASKED)

  return (
    <div style={{
      background: 'var(--card-bg)', border: '1px solid var(--card-border)',
      borderRadius: 'var(--radius)', boxShadow: 'var(--shadow-sm)', overflow: 'hidden',
    }}>
      <div style={{
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        padding: '1rem 1.25rem',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
          <div>
            <div style={{ fontWeight: 600, fontSize: '0.9rem', color: 'var(--text-dark)' }}>{integration.name}</div>
            <div style={{ fontSize: '0.75rem', color: isConfigured ? '#16a34a' : 'var(--text-mid)', marginTop: '0.1rem' }}>
              {isConfigured ? 'Configured' : 'Not configured'}
            </div>
          </div>
        </div>
        <button onClick={open ? () => setOpen(false) : openForm} style={{
          background: 'var(--body-bg)', border: '1px solid var(--card-border)',
          borderRadius: '6px', padding: '0.35rem 0.85rem', fontSize: '0.8rem',
          fontWeight: 600, color: 'var(--text-dark)', cursor: 'pointer',
        }}>
          {open ? 'Cancel' : 'Configure'}
        </button>
      </div>

      {open && (
        <div style={{ borderTop: '1px solid var(--card-border)', padding: '1.25rem' }}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem', marginBottom: '1rem' }}>

            {/* Config fields (plaintext) */}
            {Object.entries(integration.config).map(([k]) => {
              if (integration.slug === 'nextcloud' && k === 'backup_path') {
                const pathSegments = ncBrowsePath === '/' ? [] : ncBrowsePath.split('/').filter(Boolean)
                return (
                  <div key={k} style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
                    <span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
                      Backup Directory
                    </span>
                    <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
                      <input value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
                        placeholder="e.g. HNF-Backups" style={{ ...inputStyle, flex: 1 }} />
                      <button
                        onClick={() => {
                          if (ncBrowseOpen) { setNcBrowseOpen(false); return }
                          setNcBrowseOpen(true)
                          setNcBrowsePath('/')
                          ncBrowse('/')
                        }}
                        style={{
                          background: 'var(--body-bg)', border: '1px solid var(--card-border)',
                          borderRadius: '6px', padding: '0.45rem 0.75rem', fontSize: '0.78rem',
                          cursor: 'pointer', color: 'var(--text-dark)',
                          display: 'flex', alignItems: 'center', gap: '0.35rem', whiteSpace: 'nowrap',
                        }}
                      >
                        <Folder size={13} strokeWidth={1.75} />
                        Browse
                      </button>
                    </div>

                    {ncBrowseOpen && (
                      <div style={{
                        border: '1px solid var(--card-border)', borderRadius: '6px',
                        background: 'var(--body-bg)', overflow: 'hidden',
                      }}>
                        {/* Breadcrumb */}
                        <div style={{
                          display: 'flex', alignItems: 'center', gap: '0.2rem', flexWrap: 'wrap',
                          padding: '0.5rem 0.75rem', borderBottom: '1px solid var(--card-border)',
                          fontSize: '0.78rem',
                        }}>
                          <button onClick={() => ncBrowse('/')}
                            style={{ background: 'none', border: 'none', cursor: 'pointer',
                              color: ncBrowsePath === '/' ? 'var(--text-dark)' : 'var(--gold)',
                              fontSize: '0.78rem', fontWeight: 600, padding: 0 }}>
                            /
                          </button>
                          {pathSegments.map((seg, i) => {
                            const segPath = '/' + pathSegments.slice(0, i + 1).join('/')
                            const isLast = i === pathSegments.length - 1
                            return (
                              <span key={segPath} style={{ display: 'flex', alignItems: 'center', gap: '0.2rem' }}>
                                <ChevronRight size={11} strokeWidth={2} style={{ color: 'var(--text-mid)' }} />
                                <button onClick={() => !isLast && ncBrowse(segPath)}
                                  style={{ background: 'none', border: 'none', cursor: isLast ? 'default' : 'pointer',
                                    color: isLast ? 'var(--text-dark)' : 'var(--gold)',
                                    fontSize: '0.78rem', fontWeight: isLast ? 600 : 400, padding: 0 }}>
                                  {seg}
                                </button>
                              </span>
                            )
                          })}
                        </div>

                        {/* Directory list */}
                        <div style={{ maxHeight: '180px', overflowY: 'auto' }}>
                          {ncBrowseLoading && (
                            <div style={{ padding: '0.75rem', fontSize: '0.78rem', color: 'var(--text-mid)' }}>
                              Loading…
                            </div>
                          )}
                          {ncBrowseError && (
                            <div style={{ padding: '0.75rem', fontSize: '0.78rem', color: 'var(--danger)' }}>
                              {ncBrowseError}
                            </div>
                          )}
                          {!ncBrowseLoading && !ncBrowseError && ncBrowseDirs.length === 0 && (
                            <div style={{ padding: '0.75rem', fontSize: '0.78rem', color: 'var(--text-mid)' }}>
                              No subdirectories here
                            </div>
                          )}
                          {!ncBrowseLoading && ncBrowseDirs.map(dir => (
                            <button
                              key={dir.path}
                              onClick={() => ncBrowse(dir.path)}
                              style={{
                                width: '100%', background: 'none', border: 'none',
                                borderBottom: '1px solid var(--card-border)',
                                padding: '0.45rem 0.75rem', fontSize: '0.82rem',
                                color: 'var(--text-dark)', cursor: 'pointer', textAlign: 'left',
                                display: 'flex', alignItems: 'center', gap: '0.5rem',
                              }}
                            >
                              <FolderOpen size={13} strokeWidth={1.75} style={{ color: 'var(--gold)', flexShrink: 0 }} />
                              {dir.name}
                            </button>
                          ))}
                        </div>

                        {/* Footer: select current or cancel */}
                        <div style={{
                          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                          padding: '0.5rem 0.75rem', borderTop: '1px solid var(--card-border)',
                          background: 'var(--card-bg)',
                        }}>
                          <span style={{ fontSize: '0.72rem', color: 'var(--text-mid)', fontFamily: 'monospace' }}>
                            {ncBrowsePath}
                          </span>
                          <div style={{ display: 'flex', gap: '0.5rem' }}>
                            <button onClick={() => setNcBrowseOpen(false)}
                              style={{ background: 'none', border: '1px solid var(--card-border)',
                                borderRadius: '5px', padding: '0.25rem 0.65rem', fontSize: '0.75rem',
                                cursor: 'pointer', color: 'var(--text-mid)' }}>
                              Cancel
                            </button>
                            <button
                              onClick={() => {
                                setField(k, ncBrowsePath.replace(/^\//, ''))
                                setNcBrowseOpen(false)
                              }}
                              style={{ background: 'var(--navy)', color: '#fff', border: 'none',
                                borderRadius: '5px', padding: '0.25rem 0.65rem', fontSize: '0.75rem',
                                fontWeight: 600, cursor: 'pointer' }}>
                              Select
                            </button>
                          </div>
                        </div>
                      </div>
                    )}
                  </div>
                )
              }
              if (integration.slug === 'workforce' && k === 'location_id') {
                return (
                  <div key={k} style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
                    <span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
                      Default Location
                    </span>
                    <div style={{ display: 'flex', gap: '0.5rem', alignItems: 'center' }}>
                      <select value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
                        style={{ ...inputStyle, flex: 1 }}>
                        <option value="">— Select location —</option>
                        {wfLocations.map(l => (
                          <option key={l.id} value={l.id}>
                            {l.name}{l.short_name ? ` (${l.short_name})` : ''}
                          </option>
                        ))}
                        {wfLocations.length === 0 && form[k] && (
                          <option value={form[k]}>Location ID: {form[k]}</option>
                        )}
                      </select>
                      <button
                        onClick={async () => {
                          setFetchingLocs(true)
                          setLocError(null)
                          try {
                            const res = await fetch('/settings/api/integrations/workforce/locations', { credentials: 'include' })
                            if (res.ok) {
                              setWfLocations(await res.json())
                            } else {
                              const data = await res.json().catch(() => ({}))
                              setLocError(data.error || `Error ${res.status}`)
                            }
                          } catch {
                            setLocError('Network error')
                          } finally { setFetchingLocs(false) }
                        }}
                        disabled={fetchingLocs}
                        style={{
                          background: 'var(--body-bg)', border: '1px solid var(--card-border)',
                          borderRadius: '6px', padding: '0.45rem 0.75rem', fontSize: '0.78rem',
                          cursor: fetchingLocs ? 'wait' : 'pointer', color: 'var(--text-dark)',
                          whiteSpace: 'nowrap', display: 'flex', alignItems: 'center', gap: '0.35rem',
                        }}
                      >
                        <RefreshCw size={12} strokeWidth={1.75} style={{ animation: fetchingLocs ? 'spin 1s linear infinite' : 'none' }} />
                        {fetchingLocs ? 'Fetching…' : 'Fetch locations'}
                      </button>
                    </div>
                    {locError && (
                      <div style={{ fontSize: '0.78rem', color: '#dc2626', marginTop: '0.35rem' }}>{locError}</div>
                    )}
                  </div>
                )
              }
              return (
                <label key={k} style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
                  <span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
                    {FIELD_LABELS[k] ?? k}
                  </span>
                  <input value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
                    style={inputStyle} />
                </label>
              )
            })}

            {/* Secret fields (encrypted) */}
            {Object.entries(integration.secrets).map(([k, v]) => (
              <label key={k} style={{ display: 'flex', flexDirection: 'column', gap: '0.3rem' }}>
                <span style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)' }}>
                  {FIELD_LABELS[k] ?? k}
                </span>
                {v === MASKED && !changing.has(k)
                  ? (
                    <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                      <span style={{ fontSize: '0.85rem', color: '#16a34a', letterSpacing: '0.05em' }}>Configured</span>
                      <button onClick={() => startChangingSecret(k)} style={{
                        background: 'none', border: '1px solid var(--card-border)', borderRadius: '4px',
                        padding: '0.2rem 0.6rem', fontSize: '0.75rem', cursor: 'pointer', color: 'var(--text-mid)',
                      }}>Change</button>
                    </div>
                  )
                  : (
                    <input type="password" value={form[k] ?? ''} onChange={e => setField(k, e.target.value)}
                      placeholder={v === null ? 'Not set' : 'Enter new value'}
                      style={inputStyle} />
                  )
                }
              </label>
            ))}

            {/* Enable toggle */}
            <label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginTop: '0.25rem' }}>
              <input type="checkbox" checked={enabled} onChange={e => setEnabled(e.target.checked)} />
              <span style={{ fontSize: '0.85rem', color: 'var(--text-dark)' }}>Enabled</span>
            </label>
          </div>

          <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
            <button onClick={save} disabled={saving} style={{
              background: 'var(--navy)', color: '#fff', border: 'none',
              borderRadius: '6px', padding: '0.45rem 1.1rem', fontSize: '0.82rem', fontWeight: 600,
              cursor: saving ? 'wait' : 'pointer',
            }}>
              {saving ? 'Saving…' : 'Save'}
            </button>

            {isConfigured && (
              <button onClick={test} disabled={testing} style={{
                background: 'var(--body-bg)', border: '1px solid var(--card-border)',
                borderRadius: '6px', padding: '0.45rem 1rem', fontSize: '0.82rem', fontWeight: 600,
                cursor: testing ? 'wait' : 'pointer', color: 'var(--text-dark)',
                display: 'flex', alignItems: 'center', gap: '0.4rem',
              }}>
                <RefreshCw size={13} strokeWidth={1.75} style={{ animation: testing ? 'spin 1s linear infinite' : 'none' }} />
                Test connection
              </button>
            )}

            {testResult && (
              <span style={{ display: 'flex', alignItems: 'center', gap: '0.35rem', fontSize: '0.82rem',
                color: testResult.ok ? '#16a34a' : '#dc2626' }}>
                {testResult.ok
                  ? <><CheckCircle size={14} strokeWidth={1.75} /> Connected</>
                  : <><XCircle size={14} strokeWidth={1.75} /> {testResult.error}</>
                }
              </span>
            )}
          </div>
        </div>
      )}
    </div>
  )
}

function RoomsTab() {
  const [data, setData] = useState<RoomsData | null>(null)
  const [loading, setLoading] = useState(true)
  const [syncing, setSyncing] = useState(false)
  const [saving, setSaving] = useState(false)

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

  async function load() {
    setLoading(true)
    const res = await fetch('/settings/api/config/newbook.rooms', { credentials: 'include' })
    if (res.ok) setData((await res.json()).value)
    setLoading(false)
  }

  async function sync() {
    setSyncing(true)
    const res = await fetch('/settings/api/integrations/newbook/sync-rooms', {
      method: 'POST', credentials: 'include',
    })
    if (res.ok) setData(await res.json())
    setSyncing(false)
  }

  async function saveOrder() {
    if (!data) return
    setSaving(true)
    await fetch('/settings/api/integrations/newbook/rooms', {
      method: 'PUT', credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ categories: data.categories }),
    })
    setSaving(false)
  }

  function move(index: number, dir: -1 | 1) {
    if (!data) return
    const cats = [...data.categories]
    const swap = index + dir
    if (swap < 0 || swap >= cats.length) return
    ;[cats[index], cats[swap]] = [cats[swap], cats[index]]
    setData({ ...data, categories: cats.map((c, i) => ({ ...c, sort_order: i })) })
  }

  function setColour(index: number, colour: string) {
    if (!data) return
    const cats = [...data.categories]
    cats[index] = { ...cats[index], colour }
    setData({ ...data, categories: cats })
  }

  return (
    <div style={{ maxWidth: '560px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', marginBottom: '1.5rem' }}>
        <button onClick={sync} disabled={syncing} style={{
          background: 'var(--navy)', color: '#fff', border: 'none',
          borderRadius: '6px', padding: '0.45rem 1.1rem', fontSize: '0.82rem', fontWeight: 600,
          cursor: syncing ? 'wait' : 'pointer', display: 'flex', alignItems: 'center', gap: '0.4rem',
        }}>
          <RefreshCw size={13} strokeWidth={1.75} style={{ animation: syncing ? 'spin 1s linear infinite' : 'none' }} />
          {syncing ? 'Syncing…' : 'Sync from Newbook'}
        </button>
        {data?.synced_at && (
          <span style={{ fontSize: '0.78rem', color: 'var(--text-mid)' }}>
            Last synced {new Date(data.synced_at).toLocaleString()}
          </span>
        )}
      </div>

      {loading && <p style={{ color: 'var(--text-mid)' }}>Loading…</p>}

      {!loading && !data && (
        <p style={{ color: 'var(--text-mid)', fontSize: '0.85rem' }}>
          No room data yet. Configure Newbook credentials then sync.
        </p>
      )}

      {data && (
        <>
          <p style={{ fontSize: '0.78rem', fontWeight: 600, color: 'var(--text-mid)',
            textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '0.75rem' }}>
            Room categories ({data.categories.length})
          </p>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', marginBottom: '1.25rem' }}>
            {data.categories.map((cat, i) => (
              <div key={cat.id} style={{
                background: 'var(--card-bg)', border: '1px solid var(--card-border)',
                borderRadius: 'var(--radius)', padding: '0.75rem 1rem',
                display: 'flex', alignItems: 'center', gap: '0.75rem',
                boxShadow: 'var(--shadow-sm)',
              }}>
                <input type="color" value={cat.colour ?? '#1e3a5f'}
                  onChange={e => setColour(i, e.target.value)}
                  style={{ width: '28px', height: '28px', border: 'none', padding: 0,
                    borderRadius: '4px', cursor: 'pointer', background: 'none' }} />
                <span style={{ flex: 1, fontSize: '0.88rem', fontWeight: 500, color: 'var(--text-dark)' }}>
                  {cat.name}
                </span>
                <span style={{ fontSize: '0.75rem', color: 'var(--text-mid)' }}>
                  {data.sites.filter(s => s.category_id === cat.id).length} rooms
                </span>
                <div style={{ display: 'flex', flexDirection: 'column', gap: '1px' }}>
                  <button onClick={() => move(i, -1)} disabled={i === 0} style={arrowBtn}>
                    <ChevronUp size={12} strokeWidth={2} />
                  </button>
                  <button onClick={() => move(i, 1)} disabled={i === data.categories.length - 1} style={arrowBtn}>
                    <ChevronDown size={12} strokeWidth={2} />
                  </button>
                </div>
              </div>
            ))}
          </div>
          <button onClick={saveOrder} disabled={saving} style={{
            background: 'var(--navy)', color: '#fff', border: 'none',
            borderRadius: '6px', padding: '0.45rem 1.1rem', fontSize: '0.82rem', fontWeight: 600,
            cursor: saving ? 'wait' : 'pointer',
          }}>
            {saving ? 'Saving…' : 'Save order'}
          </button>
        </>
      )}
    </div>
  )
}

function AppsTab() {
  const [apps, setApps] = useState<AppRow[]>([])
  const [loading, setLoading] = useState(true)
  const [values, setValues] = useState<Record<string, string>>({})
  const [saving, setSaving] = useState<Record<string, boolean>>({})
  const [saved, setSaved] = useState<Record<string, boolean>>({})

  useEffect(() => {
    fetch('/api/auth/admin/apps', { credentials: 'include' })
      .then(r => r.json())
      .then((data: AppRow[]) => {
        const active = data.filter(a => a.active)
        setApps(active)
        const init: Record<string, string> = {}
        active.forEach(a => { init[a.slug] = a.max_session_hours != null ? String(a.max_session_hours) : '' })
        setValues(init)
      })
      .finally(() => setLoading(false))
  }, [])

  async function save(slug: string) {
    setSaving(prev => ({ ...prev, [slug]: true }))
    setSaved(prev => ({ ...prev, [slug]: false }))
    const raw = values[slug].trim()
    const max_session_hours = raw === '' ? null : parseInt(raw)
    await fetch(`/api/auth/admin/apps/${slug}`, {
      method: 'PATCH', credentials: 'include',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ max_session_hours }),
    })
    setSaving(prev => ({ ...prev, [slug]: false }))
    setSaved(prev => ({ ...prev, [slug]: true }))
    setTimeout(() => setSaved(prev => ({ ...prev, [slug]: false })), 2000)
  }

  const byCategory = apps.reduce<Record<string, AppRow[]>>((acc, a) => {
    const cat = a.category ?? 'Other'
    ;(acc[cat] ??= []).push(a)
    return acc
  }, {})

  if (loading) return <p style={{ color: 'var(--text-mid)' }}>Loading…</p>

  return (
    <div style={{ maxWidth: '640px' }}>
      <p style={{ fontSize: '0.83rem', color: 'var(--text-mid)', marginBottom: '1.5rem' }}>
        Set a maximum session age per app. Users will be prompted to re-login when the limit is reached,
        even if their global session is still valid. Leave blank to use the global default (30 days).
      </p>

      {Object.entries(byCategory).sort(([a], [b]) => a.localeCompare(b)).map(([cat, catApps]) => (
        <div key={cat} style={{ marginBottom: '1.5rem' }}>
          <p style={{ fontSize: '0.72rem', fontWeight: 700, color: 'var(--text-mid)',
            textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '0.5rem' }}>
            {cat}
          </p>
          <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
            {catApps.map(app => (
              <div key={app.slug} style={{
                background: 'var(--card-bg)', border: '1px solid var(--card-border)',
                borderRadius: 'var(--radius)', padding: '0.85rem 1.1rem',
                display: 'flex', alignItems: 'center', gap: '1rem',
                boxShadow: 'var(--shadow-sm)',
              }}>
                <div style={{
                  width: '10px', height: '10px', borderRadius: '50%', flexShrink: 0,
                  background: app.theme_color,
                }} />
                <span style={{ flex: 1, fontSize: '0.88rem', fontWeight: 500, color: 'var(--text-dark)' }}>
                  {app.name}
                </span>
                <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
                  <Clock size={13} strokeWidth={1.75} style={{ color: 'var(--text-mid)', flexShrink: 0 }} />
                  <input
                    type="number" min="1"
                    value={values[app.slug] ?? ''}
                    onChange={e => setValues(prev => ({ ...prev, [app.slug]: e.target.value }))}
                    onKeyDown={e => e.key === 'Enter' && save(app.slug)}
                    placeholder="Default (720h)"
                    style={{ ...inputStyle, width: '130px', textAlign: 'right' }}
                  />
                  <span style={{ fontSize: '0.78rem', color: 'var(--text-mid)', flexShrink: 0 }}>hours</span>
                </div>
                <button
                  onClick={() => save(app.slug)}
                  disabled={saving[app.slug]}
                  style={{
                    background: saved[app.slug] ? '#16a34a' : 'var(--navy)',
                    color: '#fff', border: 'none', borderRadius: '6px',
                    padding: '0.35rem 0.85rem', fontSize: '0.78rem', fontWeight: 600,
                    cursor: saving[app.slug] ? 'wait' : 'pointer', flexShrink: 0,
                    transition: 'background 0.2s',
                  }}
                >
                  {saving[app.slug] ? '…' : saved[app.slug] ? '✓' : 'Save'}
                </button>
              </div>
            ))}
          </div>
        </div>
      ))}
    </div>
  )
}

const TIMEOUT_OPTIONS = [
  { label: 'Off',    value: 0  },
  { label: '5 min',  value: 5  },
  { label: '10 min', value: 10 },
  { label: '15 min', value: 15 },
  { label: '30 min', value: 30 },
]

function getInactivityMins(): number {
  const c = document.cookie.split(';').map(s => s.trim()).find(s => s.startsWith('hnf_inactivity_mins='))
  return c ? parseInt(c.split('=')[1]) || 0 : 0
}

function setInactivityMins(mins: number) {
  document.cookie = mins > 0
    ? `hnf_inactivity_mins=${mins}; max-age=31536000; path=/; SameSite=Strict`
    : 'hnf_inactivity_mins=; max-age=0; path=/; SameSite=Strict'
}

function DeviceTab() {
  const [inactivityMins, setInactivityMinsState] = useState(getInactivityMins)
  const [saved, setSaved] = useState(false)
  const isPwa = window.matchMedia('(display-mode: standalone)').matches

  return (
    <div style={{ maxWidth: '480px' }}>
      <p style={{ fontSize: '0.83rem', color: 'var(--text-mid)', marginBottom: '1.5rem' }}>
        {isPwa
          ? 'This device is running as an installed PWA — inactivity timeout is automatically disabled. Sessions last until the 30-day sign-in limit.'
          : 'Set an inactivity timeout for this device. Users will be signed out automatically after the chosen period of no activity. Use on shared or front-desk computers. Ignored when running as an installed PWA.'}
      </p>

      <p style={{ fontSize: '0.72rem', fontWeight: 700, color: 'var(--text-mid)',
        textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '0.75rem' }}>
        Inactivity timeout
      </p>

      <div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap', marginBottom: '1.25rem' }}>
        {TIMEOUT_OPTIONS.map(opt => (
          <button
            key={opt.value}
            disabled={isPwa}
            onClick={() => {
              setInactivityMins(opt.value)
              setInactivityMinsState(opt.value)
              setSaved(true)
              setTimeout(() => setSaved(false), 2000)
            }}
            style={{
              padding: '0.4rem 1rem', borderRadius: '6px', fontSize: '0.85rem', fontWeight: 600,
              cursor: isPwa ? 'not-allowed' : 'pointer', opacity: isPwa ? 0.4 : 1,
              border: `1px solid ${inactivityMins === opt.value ? 'var(--navy)' : 'var(--card-border)'}`,
              background: inactivityMins === opt.value ? 'var(--navy)' : 'var(--body-bg)',
              color: inactivityMins === opt.value ? '#fff' : 'var(--text-dark)',
              transition: 'all 0.15s',
            }}
          >
            {opt.label}
          </button>
        ))}
      </div>

      {saved && (
        <p style={{ fontSize: '0.82rem', color: '#16a34a' }}>
          ✓ Saved — takes effect immediately
        </p>
      )}
    </div>
  )
}

const inputStyle: React.CSSProperties = {
  background: 'var(--body-bg)', border: '1px solid var(--card-border)',
  borderRadius: '6px', padding: '0.45rem 0.75rem', fontSize: '0.85rem',
  color: 'var(--text-dark)', width: '100%', boxSizing: 'border-box',
}

const arrowBtn: React.CSSProperties = {
  background: 'var(--body-bg)', border: '1px solid var(--card-border)',
  borderRadius: '3px', padding: '1px 3px', cursor: 'pointer',
  color: 'var(--text-mid)', lineHeight: 1, display: 'flex',
}
