import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useState, useRef } from 'react'
import { toast } from 'sonner'
import { UserPlus, Trash2, Pencil, ChevronDown, ChevronUp, ChevronRight, Settings, RefreshCw, CheckCircle2, XCircle, DollarSign, Layers, AlertTriangle, Upload, FileBox, Plus, X, LayoutDashboard, Cpu, Zap, AlertCircle, HardDrive, Mail, Monitor, Eye, Box } from 'lucide-react'
import { Link } from 'react-router-dom'
import api from '../api/client'
import ConfirmModal from '../components/ConfirmModal'
import HelpTooltip from '../components/HelpTooltip'
import TemplateEditor from '../components/admin/TemplateEditor'
import PricingTierTable from '../components/admin/PricingTierTable'
import OutputTypeTable from '../components/admin/OutputTypeTable'
import RenderTemplateTable from '../components/admin/RenderTemplateTable'
import GlobalRenderPositionsPanel from '../components/admin/GlobalRenderPositionsPanel'
import { useAuthStore, isAdmin as checkIsAdmin } from '../store/auth'
import { listPricingTiers } from '../api/pricing'
import { listOutputTypes } from '../api/outputTypes'
import {
  listAssetLibraries, createAssetLibrary, deleteAssetLibrary, refreshAssetLibraryCatalog,
  type AssetLibrary,
} from '../api/assetLibraries'
import { getTenantDefaultDashboard } from '../api/dashboard'
import type { WidgetConfig } from '../api/dashboard'
import DashboardCustomizeModal from '../components/dashboard/DashboardCustomizeModal'
import { getGpuProbeResult, triggerGpuProbe } from '../api/worker'
import type { GPUProbeResult } from '../api/worker'

export default function AdminPage() {
  const qc = useQueryClient()
  const user = useAuthStore((s) => s.user)
  const isAdmin = checkIsAdmin(user)
  const [showNewUser, setShowNewUser] = useState(false)
  const [newUser, setNewUser] = useState({ email: '', password: '', full_name: '', role: 'client' })
  const [editingUserId, setEditingUserId] = useState<string | null>(null)
  const [editUserDraft, setEditUserDraft] = useState<{ full_name: string; role: string; is_active: boolean }>({ full_name: '', role: 'client', is_active: true })
  const [editingTemplateId, setEditingTemplateId] = useState<string | null>(null)

  const { data: users } = useQuery({
    queryKey: ['admin-users'],
    queryFn: async () => {
      const res = await api.get('/admin/users')
      return res.data as any[]
    },
  })

  const { data: templates } = useQuery({
    queryKey: ['admin-templates'],
    queryFn: async () => {
      const res = await api.get('/templates?include_inactive=true')
      return res.data as any[]
    },
  })

  const createUserMut = useMutation({
    mutationFn: (data: typeof newUser) => api.post('/admin/users', data),
    onSuccess: () => {
      toast.success('User created')
      qc.invalidateQueries({ queryKey: ['admin-users'] })
      setShowNewUser(false)
      setNewUser({ email: '', password: '', full_name: '', role: 'client' })
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const deleteUserMut = useMutation({
    mutationFn: (id: string) => api.delete(`/admin/users/${id}`),
    onSuccess: () => {
      toast.success('User deleted')
      qc.invalidateQueries({ queryKey: ['admin-users'] })
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const updateUserMut = useMutation({
    mutationFn: ({ id, data }: { id: string; data: { full_name: string; role: string; is_active: boolean } }) =>
      api.patch(`/admin/users/${id}`, data),
    onSuccess: () => {
      toast.success('User updated')
      qc.invalidateQueries({ queryKey: ['admin-users'] })
      setEditingUserId(null)
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  type Settings = {
    thumbnail_renderer: string
    blender_engine: string
    blender_cycles_samples: number
    blender_eevee_samples: number
    threejs_render_size: number
    blender_smooth_angle: number
    cycles_device: string
    render_backend: string
    smtp_enabled: boolean
    smtp_host: string
    smtp_port: number
    smtp_user: string
    smtp_password: string
    smtp_from_address: string
    viewer_max_distance: number
    viewer_min_distance: number
    scene_linear_deflection: number
    scene_angular_deflection: number
    tessellation_engine: string
  }

  const { data: settings } = useQuery({
    queryKey: ['admin-settings'],
    queryFn: async () => {
      const res = await api.get('/admin/settings')
      return res.data as Settings
    },
  })

  const { data: outputTypes } = useQuery({
    queryKey: ['output-types-all'],
    queryFn: () => listOutputTypes(false),
    enabled: isAdmin,
  })

  // Local draft for Blender options so the user can change multiple fields before saving
  const [blenderDraft, setBlenderDraft] = useState<Partial<Settings>>({})
  const blender = { ...settings, ...blenderDraft } as Settings

  const [viewerDraft, setViewerDraft] = useState<Partial<Settings>>({})
  const viewer3d = { ...settings, ...viewerDraft } as Settings

  const [tessellationDraft, setTessellationDraft] = useState<Partial<Settings>>({})
  const tess = { ...settings, ...tessellationDraft } as Settings
  const [showAdvancedTess, setShowAdvancedTess] = useState(false)

  const updateSettingsMut = useMutation({
    mutationFn: (data: Partial<Settings>) => api.put('/admin/settings', data),
    onSuccess: () => {
      toast.success('Settings saved')
      qc.invalidateQueries({ queryKey: ['admin-settings'] })
      setBlenderDraft({})
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const processUnprocessedMut = useMutation({
    mutationFn: () => api.post('/admin/settings/process-unprocessed'),
    onSuccess: (res) => {
      toast.success(res.data.message || 'Processing queued')
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const regenerateMut = useMutation({
    mutationFn: () => api.post('/admin/settings/regenerate-thumbnails'),
    onSuccess: (res) => {
      toast.success(res.data.message || 'Thumbnails re-queued')
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const importMediaAssetsMut = useMutation({
    mutationFn: () => api.post('/admin/import-media-assets'),
    onSuccess: (res) => {
      toast.success(`Imported: ${res.data.created} created, ${res.data.skipped} skipped`)
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Import failed'),
  })

  const cleanupOrphanedMut = useMutation({
    mutationFn: () => api.post('/media/cleanup-orphaned'),
    onSuccess: (res) => {
      toast.success(`Cleanup done: ${res.data.deleted} orphaned records deleted (${res.data.checked} checked)`)
      qc.invalidateQueries({ queryKey: ['media-browser'] })
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Cleanup failed'),
  })

  const reextractMetadataMut = useMutation({
    mutationFn: () => api.post('/admin/settings/reextract-metadata'),
    onSuccess: (res) => {
      toast.success(res.data.message || 'Metadata re-extraction queued')
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const reextractRichMetadataMut = useMutation({
    mutationFn: () => api.post('/admin/settings/reextract-rich-metadata'),
    onSuccess: (res) => {
      toast.success(res.data.message || 'Rich metadata re-extraction queued')
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const cleanupOrphanedCadMut = useMutation({
    mutationFn: () => api.post('/admin/settings/cleanup-orphaned-cad-files'),
    onSuccess: (res) => {
      toast.success(`Deleted ${res.data.deleted_records} orphaned CAD records, freed ${res.data.freed_mb} MB`)
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Cleanup failed'),
  })

  const recoverStuckMut = useMutation({
    mutationFn: () => api.post('/admin/settings/recover-stuck-processing'),
    onSuccess: (res) => {
      toast.success(res.data.message || 'Stuck files recovered')
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const seedWorkflowsMut = useMutation({
    mutationFn: () => api.post('/admin/settings/seed-workflows'),
    onSuccess: (res) => {
      toast.success(res.data.message || 'Workflows seeded')
      qc.invalidateQueries({ queryKey: ['workflows'] })
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const generateMissingUsdMastersMut = useMutation({
    mutationFn: () => api.post('/admin/settings/generate-missing-usd-masters'),
    onSuccess: (res) => toast.success(res.data.message || 'USD master export queued'),
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const generateMissingCanonicalScenesMut = useMutation({
    mutationFn: () => api.post('/admin/settings/generate-missing-canonical-scenes'),
    onSuccess: (res) => toast.success(res.data.message || 'Canonical scene export queued'),
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const regenerateAllCanonicalScenesMut = useMutation({
    mutationFn: () => api.post('/admin/settings/regenerate-all-canonical-scenes'),
    onSuccess: (res) => toast.success(res.data.message || 'All canonical scenes re-queued'),
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const purgeRenderMediaMut = useMutation({
    mutationFn: () => api.delete('/admin/settings/purge-render-media'),
    onSuccess: (res) => toast.success(res.data.message || 'Render media purged'),
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed'),
  })

  const [smtpDraft, setSmtpDraft] = useState<Partial<Settings>>({})
  const smtp = { ...settings, ...smtpDraft } as Settings

  const [confirmState, setConfirmState] = useState<{ open: boolean; title: string; message: string; onConfirm: () => void }>({ open: false, title: '', message: '', onConfirm: () => {} })
  const [showTenantDashboardModal, setShowTenantDashboardModal] = useState(false)
  const { data: tenantDefaultWidgets } = useQuery<WidgetConfig[]>({
    queryKey: ['tenant-default-dashboard'],
    queryFn: getTenantDefaultDashboard,
    enabled: isAdmin,
    staleTime: 300_000,
  })

  // GPU Probe
  const [gpuProbeExpanded, setGpuProbeExpanded] = useState(false)
  const [gpuProbing, setGpuProbing] = useState(false)
  const gpuPollRef = useRef<ReturnType<typeof setInterval> | null>(null)
  const { data: gpuProbeResult, refetch: refetchGpuProbe } = useQuery<GPUProbeResult>({
    queryKey: ['gpu-probe-result'],
    queryFn: getGpuProbeResult,
    enabled: isAdmin,
    refetchInterval: gpuProbing ? 2000 : false,
    staleTime: 0,
  })

  const handleRunGpuCheck = async () => {
    if (!isAdmin) return
    setGpuProbing(true)
    try {
      await triggerGpuProbe()
      // Poll for up to 45 seconds
      let elapsed = 0
      const interval = setInterval(async () => {
        elapsed += 2
        await refetchGpuProbe()
        if (elapsed >= 45) {
          clearInterval(interval)
          setGpuProbing(false)
        }
      }, 2000)
      gpuPollRef.current = interval
    } catch {
      toast.error('Failed to trigger GPU check')
      setGpuProbing(false)
    }
  }

  const gpuStatusBadge = () => {
    if (!gpuProbeResult) return null
    const s = gpuProbeResult.status
    if (s === 'ok') {
      return (
        <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-status-success-bg text-status-success-text">
          <CheckCircle2 size={11} />
          GPU OK{gpuProbeResult.device_type ? ` (${gpuProbeResult.device_type})` : ''}
        </span>
      )
    }
    if (s === 'failed') {
      return (
        <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200">
          <Cpu size={11} />
          CPU Fallback
        </span>
      )
    }
    return (
      <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-status-error-bg text-status-error-text">
        <XCircle size={11} />
        {s === 'error' ? 'Error' : 'Unknown'}
      </span>
    )
  }

  type AdminTab = 'overview' | 'users' | 'render-settings' | 'output-types' | 'templates' | 'pricing' | 'libraries' | 'system'
  const [activeTab, setActiveTab] = useState<AdminTab>('overview')

  // Blender Status (via Celery on render-worker)
  const { data: blenderStatus, refetch: refetchBlenderStatus, isFetching: blenderStatusFetching } = useQuery({
    queryKey: ['blender-status'],
    queryFn: async () => {
      const res = await api.get('/admin/settings/renderer-status')
      return res.data.blender as { available: boolean; blender_bin: string; version: string; error?: string }
    },
    enabled: isAdmin && activeTab === 'system',
    staleTime: 60_000,
  })

  const hasUnsavedChanges =
    Object.keys(blenderDraft).length > 0 ||
    Object.keys(viewerDraft).length > 0 ||
    Object.keys(tessellationDraft).length > 0 ||
    Object.keys(smtpDraft).length > 0

  const TABS: { id: AdminTab; label: string }[] = [
    { id: 'overview', label: 'Overview' },
    { id: 'users', label: 'Users' },
    { id: 'render-settings', label: 'Render Settings' },
    { id: 'output-types', label: 'Output Types' },
    { id: 'templates', label: 'Templates & Positions' },
    { id: 'pricing', label: 'Pricing' },
    { id: 'libraries', label: 'Libraries' },
    { id: 'system', label: 'System Tools' },
  ]

  return (
    <div>
      {/* Tab header */}
      <div className="px-8 pt-6 pb-0 bg-surface border-b border-border-default sticky top-0 z-10">
        <div className="flex items-center justify-between mb-4">
          <h1 className="text-2xl font-bold text-content">Admin</h1>
          {hasUnsavedChanges && (
            <div className="flex items-center gap-2 px-3 py-1.5 bg-amber-50 border border-amber-200 rounded-lg text-amber-700 text-sm dark:bg-amber-950 dark:border-amber-800 dark:text-amber-300">
              <AlertCircle size={14} />
              Unsaved changes
            </div>
          )}
        </div>
        <div className="flex gap-1 -mb-px overflow-x-auto">
          {TABS.map((tab) => (
            <button
              key={tab.id}
              onClick={() => setActiveTab(tab.id)}
              className={`px-4 py-2 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${
                activeTab === tab.id
                  ? 'border-accent text-accent'
                  : 'border-transparent text-content-secondary hover:text-content hover:border-border-default'
              }`}
            >
              {tab.label}
            </button>
          ))}
        </div>
      </div>

    <div className="p-8 space-y-8">

      {/* ================================================================== */}
      {/* Overview                                                            */}
      {/* ================================================================== */}
      {activeTab === 'overview' && <PricingSummaryCard />}

      {/* ================================================================== */}
      {/* Users                                                               */}
      {/* ================================================================== */}
      {activeTab === 'users' && isAdmin && <div className="card">
        <div className="p-4 border-b border-border-default flex items-center justify-between">
          <h2 className="font-semibold text-content">Users</h2>
          <button onClick={() => setShowNewUser(!showNewUser)} className="btn-primary">
            <UserPlus size={16} />New User
          </button>
        </div>

        {showNewUser && (
          <div className="p-4 border-b border-border-light bg-surface-alt">
            <div className="grid grid-cols-2 gap-3 mb-3">
              <input
                placeholder="Full name"
                value={newUser.full_name}
                onChange={(e) => setNewUser({ ...newUser, full_name: e.target.value })}
                className="px-3 py-2 border border-border-default rounded-md text-sm"
              />
              <input
                placeholder="Email"
                type="email"
                value={newUser.email}
                onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
                className="px-3 py-2 border border-border-default rounded-md text-sm"
              />
              <input
                placeholder="Password"
                type="password"
                value={newUser.password}
                onChange={(e) => setNewUser({ ...newUser, password: e.target.value })}
                className="px-3 py-2 border border-border-default rounded-md text-sm"
              />
              <select
                value={newUser.role}
                onChange={(e) => setNewUser({ ...newUser, role: e.target.value })}
                className="px-3 py-2 border border-border-default rounded-md text-sm"
              >
                <option value="client">Client</option>
                <option value="project_manager">Project Manager</option>
                <option value="admin">Admin</option>
              </select>
            </div>
            <button
              onClick={() => createUserMut.mutate(newUser)}
              disabled={createUserMut.isPending}
              className="btn-primary"
            >
              {createUserMut.isPending ? 'Creating...' : 'Create User'}
            </button>
          </div>
        )}

        <div className="divide-y divide-border-light">
          {users?.map((u) => (
            <div key={u.id}>
              {editingUserId === u.id ? (
                <div className="px-6 py-3 bg-surface-alt space-y-3">
                  <div className="grid grid-cols-2 gap-3">
                    <div>
                      <label className="text-xs text-content-muted block mb-1">Full name</label>
                      <input
                        value={editUserDraft.full_name}
                        onChange={(e) => setEditUserDraft((d) => ({ ...d, full_name: e.target.value }))}
                        className="px-3 py-1.5 border border-border-default rounded-md text-sm w-full"
                      />
                    </div>
                    <div>
                      <label className="text-xs text-content-muted block mb-1">Role</label>
                      <select
                        value={editUserDraft.role}
                        onChange={(e) => setEditUserDraft((d) => ({ ...d, role: e.target.value }))}
                        className="px-3 py-1.5 border border-border-default rounded-md text-sm w-full"
                      >
                        <option value="client">Client</option>
                        <option value="project_manager">Project Manager</option>
                        <option value="admin">Admin</option>
                        <option value="global_admin">Global Admin</option>
                        <option value="tenant_admin">Tenant Admin</option>
                      </select>
                    </div>
                  </div>
                  <div className="flex items-center gap-4">
                    <label className="flex items-center gap-2 text-sm text-content cursor-pointer">
                      <input
                        type="checkbox"
                        checked={editUserDraft.is_active}
                        onChange={(e) => setEditUserDraft((d) => ({ ...d, is_active: e.target.checked }))}
                        className="rounded"
                      />
                      Active
                    </label>
                    <div className="flex gap-2 ml-auto">
                      <button
                        onClick={() => setEditingUserId(null)}
                        className="btn-secondary text-sm"
                      >
                        Cancel
                      </button>
                      <button
                        onClick={() => updateUserMut.mutate({ id: u.id, data: editUserDraft })}
                        disabled={updateUserMut.isPending}
                        className="btn-primary text-sm"
                      >
                        {updateUserMut.isPending ? 'Saving...' : 'Save'}
                      </button>
                    </div>
                  </div>
                </div>
              ) : (
                <div className="flex items-center px-6 py-3">
                  <div className="flex-1">
                    <p className="text-sm font-medium text-content">{u.full_name}</p>
                    <p className="text-xs text-content-muted">{u.email}</p>
                  </div>
                  <span className={`badge mr-4 ${checkIsAdmin(u) ? 'badge-green' : 'badge-gray'}`}>
                    {u.role}
                  </span>
                  <span className={`badge mr-4 ${u.is_active ? 'badge-green' : 'badge-red'}`}>
                    {u.is_active ? 'active' : 'inactive'}
                  </span>
                  <button
                    onClick={() => {
                      setEditingUserId(u.id)
                      setEditUserDraft({ full_name: u.full_name, role: u.role, is_active: u.is_active })
                    }}
                    className="text-content-muted hover:text-accent transition-colors mr-3"
                    title="Edit user"
                  >
                    <Pencil size={16} />
                  </button>
                  <button
                    onClick={() => {
                      setConfirmState({
                        open: true,
                        title: 'Delete User',
                        message: `Delete user "${u.email}"? This cannot be undone.`,
                        onConfirm: () => {
                          deleteUserMut.mutate(u.id)
                          setConfirmState((s) => ({ ...s, open: false }))
                        },
                      })
                    }}
                    className="text-content-muted hover:text-red-500 transition-colors"
                    title="Delete user"
                  >
                    <Trash2 size={16} />
                  </button>
                </div>
              )}
            </div>
          ))}
        </div>
      </div>}

      {/* ================================================================== */}
      {/* Render Settings                                                     */}
      {/* ================================================================== */}
      {activeTab === 'render-settings' && isAdmin && <>

        {/* ── Blender Engine & Quality ──────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <Settings size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Blender Engine & Quality</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Render engine selection, sample counts, smooth angle, and performance tuning for Blender 5.</p>

          <div className="card">
            <div className="p-6 space-y-6">
              {/* ── Render Quality ─────────────────────────────────────── */}
              <div className="space-y-4">
                <p className="text-xs font-semibold text-content-secondary uppercase tracking-wide">Render Quality</p>
                <div className="rounded-lg border border-border-default bg-surface-alt p-4 space-y-4">

                  {/* Engine */}
                  <div className="flex items-center gap-6 flex-wrap">
                    <span className="text-sm font-medium text-content-secondary w-28 shrink-0">Render engine</span>
                    {(['cycles', 'eevee'] as const).map((eng) => (
                      <button
                        key={eng}
                        onClick={() => setBlenderDraft((d) => ({ ...d, blender_engine: eng }))}
                        title={
                          eng === 'cycles'
                            ? 'Cycles -- path-tracing engine; photorealistic results but slower (affected by sample count)'
                            : 'EEVEE Next -- real-time rasteriser; very fast but less physically accurate'
                        }
                        className={`px-4 py-1.5 rounded-full border text-sm font-medium transition-colors ${
                          blender.blender_engine === eng
                            ? 'bg-blue-600 text-white border-blue-600'
                            : 'bg-surface text-content-secondary border-border-default hover:border-blue-400 hover:text-blue-600'
                        }`}
                      >
                        {eng === 'cycles' ? 'Cycles (ray tracing)' : 'EEVEE Next (real-time)'}
                      </button>
                    ))}
                  </div>

                  {/* Cycles device */}
                  {blender.blender_engine === 'cycles' && (
                    <div className="flex items-center gap-6 flex-wrap">
                      <span className="text-sm font-medium text-content-secondary w-28 shrink-0">Cycles device</span>
                      {(['auto', 'gpu', 'cpu'] as const).map((dev) => (
                        <button
                          key={dev}
                          onClick={() => setBlenderDraft((d) => ({ ...d, cycles_device: dev }))}
                          title={
                            dev === 'auto'
                              ? 'Auto -- tries OptiX / CUDA / HIP first; falls back to CPU if no compatible GPU is found'
                              : dev === 'gpu'
                              ? 'GPU only -- always render on GPU; logs a warning if no compatible GPU is available'
                              : 'CPU only -- always render on CPU; useful for debugging or when the GPU is busy'
                          }
                          className={`px-4 py-1.5 rounded-full border text-sm font-medium transition-colors ${
                            blender.cycles_device === dev
                              ? 'bg-blue-600 text-white border-blue-600'
                              : 'bg-surface text-content-secondary border-border-default hover:border-blue-400 hover:text-blue-600'
                          }`}
                        >
                          {dev === 'auto' ? 'Auto (GPU -> CPU)' : dev === 'gpu' ? 'GPU only' : 'CPU only'}
                        </button>
                      ))}
                      <p className="text-xs text-content-muted">
                        {blender.cycles_device === 'auto'
                          ? 'Tries OptiX / CUDA / HIP, falls back to CPU if no GPU is available.'
                          : blender.cycles_device === 'gpu'
                            ? 'Always use GPU. Logs a warning if no compatible GPU is found.'
                            : 'Always use CPU -- useful for debugging or when GPU is busy.'}
                      </p>
                    </div>
                  )}

                  {/* Sample counts */}
                  <div className="grid grid-cols-2 gap-4 max-w-sm">
                    <div>
                      <label className="flex items-center gap-1 text-xs font-medium text-content-secondary mb-1">
                        Cycles samples
                        <HelpTooltip helpKey="setting.blender_cycles_samples" position="top" size={12} />
                      </label>
                      <input
                        type="number"
                        min={1} max={4096} step={32}
                        value={blender.blender_cycles_samples ?? 256}
                        onChange={(e) => setBlenderDraft((d) => ({ ...d, blender_cycles_samples: Number(e.target.value) }))}
                        title="Number of Cycles path-tracing samples (1-4096). Higher values = better quality + longer render time. Default: 256"
                        className="w-full px-3 py-1.5 border border-border-default rounded-md text-sm focus:outline-none focus:border-blue-400"
                      />
                      <p className="text-xs text-content-muted mt-0.5">Higher = better quality, slower</p>
                    </div>
                    <div>
                      <label className="flex items-center gap-1 text-xs font-medium text-content-secondary mb-1">
                        EEVEE samples
                        <HelpTooltip helpKey="setting.blender_eevee_samples" position="top" size={12} />
                      </label>
                      <input
                        type="number"
                        min={1} max={1024} step={16}
                        value={blender.blender_eevee_samples ?? 64}
                        onChange={(e) => setBlenderDraft((d) => ({ ...d, blender_eevee_samples: Number(e.target.value) }))}
                        title="EEVEE anti-aliasing sample count (1-1024). Higher values = smoother edges + longer render time. Default: 64"
                        className="w-full px-3 py-1.5 border border-border-default rounded-md text-sm focus:outline-none focus:border-blue-400"
                      />
                      <p className="text-xs text-content-muted mt-0.5">Higher = better AA, slower</p>
                    </div>
                  </div>

                  {/* Smooth by angle */}
                  <div className="flex items-center gap-4 flex-wrap">
                    <span className="flex items-center gap-1 text-sm font-medium text-content-secondary w-28 shrink-0">
                      Smooth angle
                      <HelpTooltip helpKey="setting.blender_smooth_angle" size={12} />
                    </span>
                    <input
                      type="number"
                      min={0} max={180} step={5}
                      value={blender.blender_smooth_angle ?? 30}
                      onChange={(e) => setBlenderDraft((d) => ({ ...d, blender_smooth_angle: Number(e.target.value) }))}
                      title="Auto-smooth angle in degrees (0-180). Faces with dihedral angles below this threshold are shaded smooth; sharper edges stay hard. 30 works well for most mechanical parts."
                      className="w-24 px-3 py-1.5 border border-border-default rounded-md text-sm focus:outline-none focus:border-blue-400"
                    />
                    <span className="text-sm text-content-muted">deg</span>
                    <p className="text-xs text-content-muted">
                      {(blender.blender_smooth_angle ?? 30) === 0
                        ? '0 = flat shading on all faces.'
                        : `Faces with edges sharper than ${blender.blender_smooth_angle ?? 30} stay hard; others smooth. 30 works well for most mechanical parts.`}
                    </p>
                  </div>
                </div>
              </div>

              {/* Save button */}
              {Object.keys(blenderDraft).length > 0 && (
                <button
                  onClick={() => updateSettingsMut.mutate(blenderDraft)}
                  disabled={updateSettingsMut.isPending}
                  className="btn-primary text-sm"
                >
                  {updateSettingsMut.isPending ? 'Saving...' : 'Save Settings'}
                </button>
              )}

            </div>
          </div>
        </div>

        {/* ── Tessellation Quality ──────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <Box size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Tessellation Quality</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Controls how STEP geometry is converted to triangle meshes. Affects both the 3D viewer and Blender renders.</p>

          <div className="card">
            <div className="p-4 space-y-6">
              {/* Presets */}
              {(() => {
                const PRESETS = [
                  {
                    label: 'Draft',
                    icon: '***',
                    description: 'Fast preview -- visible faceting on curved surfaces',
                    useCase: 'Quick checks, large assemblies',
                    color: 'border-amber-400',
                    activeColor: 'border-amber-500 ring-2 ring-amber-200',
                    values: { scene_linear_deflection: 0.2, scene_angular_deflection: 0.3 },
                  },
                  {
                    label: 'Standard',
                    icon: '---',
                    description: 'Smooth curves, good quality-to-size ratio',
                    useCase: 'Recommended for most parts',
                    color: 'border-blue-400',
                    activeColor: 'border-blue-500 ring-2 ring-blue-200',
                    values: { scene_linear_deflection: 0.1, scene_angular_deflection: 0.1 },
                  },
                  {
                    label: 'Fine',
                    icon: '+++',
                    description: 'Near-perfect surfaces, 3-5x larger files',
                    useCase: 'Close-up renders, small precision parts',
                    color: 'border-emerald-400',
                    activeColor: 'border-emerald-500 ring-2 ring-emerald-200',
                    values: { scene_linear_deflection: 0.05, scene_angular_deflection: 0.05 },
                  },
                  {
                    label: 'Ultra',
                    icon: '***',
                    description: 'Maximum fidelity, very slow export',
                    useCase: 'Marketing renders, extreme close-ups',
                    color: 'border-purple-400',
                    activeColor: 'border-purple-500 ring-2 ring-purple-200',
                    values: { scene_linear_deflection: 0.02, scene_angular_deflection: 0.02 },
                  },
                ]
                const isActive = (preset: typeof PRESETS[0]) =>
                  tess.scene_linear_deflection === preset.values.scene_linear_deflection &&
                  tess.scene_angular_deflection === preset.values.scene_angular_deflection
                const isCustom = !PRESETS.some(isActive)
                return (
                  <div>
                    <p className="text-xs font-semibold text-content-secondary uppercase tracking-wide mb-2">Quality Presets</p>
                    <div className="grid grid-cols-4 gap-3">
                      {PRESETS.map(preset => (
                        <button
                          key={preset.label}
                          onClick={() => setTessellationDraft(preset.values)}
                          className={`p-3 rounded-lg border-2 text-left transition-all ${isActive(preset) ? preset.activeColor : preset.color + ' opacity-60 hover:opacity-100'}`}
                          style={isActive(preset) ? { backgroundColor: 'var(--color-bg-surface-alt)' } : undefined}
                        >
                          <div className="flex items-center gap-1.5">
                            <span className="text-base">{preset.icon}</span>
                            <span className="font-semibold text-sm">{preset.label}</span>
                          </div>
                          <div className="text-xs text-content-muted mt-1">{preset.description}</div>
                          <div className="text-xs text-content-secondary mt-1.5 italic">{preset.useCase}</div>
                        </button>
                      ))}
                    </div>
                    {isCustom && (
                      <p className="text-xs text-amber-600 mt-2">Current values don't match any preset (custom configuration)</p>
                    )}
                  </div>
                )
              })()}

              {/* Tessellation engine selector */}
              <div className="space-y-2">
                <p className="text-xs font-semibold text-content-secondary uppercase tracking-wide">Tessellation Engine</p>
                <div className="flex flex-col gap-2">
                  {[
                    { value: 'occ', label: 'OCC BRepMesh', description: 'Default engine. Fast, but produces fan-shaped triangles at cylinder seam lines.' },
                    { value: 'gmsh', label: 'GMSH Frontal-Delaunay', description: 'Uniform mesh -- eliminates fan artifacts on cylindrical parts. 10-30% slower. Recommended for bearings.' },
                  ].map(opt => (
                    <label key={opt.value} className="flex items-start gap-3 cursor-pointer p-3 rounded-lg border border-border-default hover:border-blue-400 transition-colors">
                      <input
                        type="radio"
                        name="tessellation_engine"
                        value={opt.value}
                        checked={(tess.tessellation_engine ?? 'occ') === opt.value}
                        onChange={() => setTessellationDraft(d => ({ ...d, tessellation_engine: opt.value }))}
                        className="mt-0.5 shrink-0"
                      />
                      <div>
                        <div className="text-sm font-medium">{opt.label}</div>
                        <div className="text-xs text-content-muted mt-0.5">{opt.description}</div>
                      </div>
                    </label>
                  ))}
                </div>
              </div>

              {/* Explanation of deflection parameters */}
              <div className="rounded-lg border border-border-default p-4 space-y-3" style={{ backgroundColor: 'var(--color-bg-surface-alt)' }}>
                <p className="text-xs font-semibold text-content-secondary uppercase tracking-wide">How deflection values work</p>
                <div className="grid grid-cols-2 gap-4 text-xs text-content-muted">
                  <div>
                    <p className="font-medium text-content-secondary mb-1">Linear deflection (mm)</p>
                    <p>Maximum allowed distance between the original curved surface and the generated triangles. A value of 0.1 mm means no triangle edge can deviate more than 0.1 mm from the true surface. Lower values produce smoother curves but more triangles.</p>
                  </div>
                  <div>
                    <p className="font-medium text-content-secondary mb-1">Angular deflection (rad)</p>
                    <p>Maximum angle between adjacent triangle normals. Controls how finely curved regions are subdivided. A value of 0.1 rad (~6 deg) means neighboring triangles can differ by at most ~6 deg. Primarily affects small fillets and tight curvatures.</p>
                  </div>
                </div>
              </div>

              <button
                onClick={() => setShowAdvancedTess(v => !v)}
                className="text-xs text-accent hover:underline flex items-center gap-1 mt-1"
              >
                {showAdvancedTess ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
                {showAdvancedTess ? 'Hide manual values' : 'Advanced: edit values manually'}
              </button>

              {/* Manual inputs */}
              {showAdvancedTess && (<>
              <div className="space-y-4">
                <div>
                  <p className="text-xs font-semibold text-content-secondary uppercase tracking-wide">Scene Deflection</p>
                  <p className="text-xs text-content-muted mt-0.5">Controls mesh quality for the 3D viewer GLB, USD scene, and Blender renders.</p>
                </div>
                <div className="flex items-center gap-3">
                  <label className="text-sm text-content-secondary w-36 shrink-0">Linear deflection</label>
                  <input
                    type="number"
                    step="0.01"
                    min="0.001"
                    max="10"
                    value={tess.scene_linear_deflection ?? 0.1}
                    onChange={e => setTessellationDraft(d => ({ ...d, scene_linear_deflection: parseFloat(e.target.value) }))}
                    className="w-24 px-3 py-1.5 border border-border-default rounded-md text-sm focus:outline-none focus:border-blue-400"
                  />
                  <span className="text-sm text-content-muted">mm</span>
                </div>
                <div className="flex items-center gap-3">
                  <label className="text-sm text-content-secondary w-36 shrink-0">Angular deflection</label>
                  <input
                    type="number"
                    step="0.01"
                    min="0.01"
                    max="1.5"
                    value={tess.scene_angular_deflection ?? 0.1}
                    onChange={e => setTessellationDraft(d => ({ ...d, scene_angular_deflection: parseFloat(e.target.value) }))}
                    className="w-24 px-3 py-1.5 border border-border-default rounded-md text-sm focus:outline-none focus:border-blue-400"
                  />
                  <span className="text-sm text-content-muted">rad</span>
                </div>
              </div>
              </>)}
              <div className="flex gap-2">
                <button
                  onClick={() => { updateSettingsMut.mutate(tessellationDraft); setTessellationDraft({}) }}
                  disabled={Object.keys(tessellationDraft).length === 0 || updateSettingsMut.isPending}
                  className="btn-primary disabled:opacity-40"
                >
                  Save Tessellation Settings
                </button>
                {Object.keys(tessellationDraft).length > 0 && (
                  <button onClick={() => setTessellationDraft({})} className="btn-secondary">Reset</button>
                )}
              </div>
            </div>
          </div>
        </div>

        {/* ── 3D Viewer ────────────────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <Eye size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">3D Viewer</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Camera zoom limits for the interactive 3D viewer.</p>

          <div className="card">
            <div className="p-4 space-y-4">
              <div className="grid grid-cols-2 gap-4">
                <div>
                  <label className="text-sm font-medium text-content-muted block mb-1">
                    Max Zoom-Out Distance
                  </label>
                  <input
                    type="number"
                    step="1"
                    min="1"
                    max="10000"
                    value={viewer3d.viewer_max_distance ?? 50}
                    onChange={e => setViewerDraft(d => ({ ...d, viewer_max_distance: parseFloat(e.target.value) }))}
                    title="Maximum camera distance from the model in the 3D viewer (in metres after mm to m conversion). Default: 50"
                    className="input w-full"
                  />
                  <p className="text-xs text-content-muted mt-1">Maximum camera pull-back distance in the 3D viewer (metres).</p>
                </div>
                <div>
                  <label className="text-sm font-medium text-content-muted block mb-1">
                    Min Zoom-In Distance
                  </label>
                  <input
                    type="number"
                    step="0.001"
                    min="0.0001"
                    max="1"
                    value={viewer3d.viewer_min_distance ?? 0.001}
                    onChange={e => setViewerDraft(d => ({ ...d, viewer_min_distance: parseFloat(e.target.value) }))}
                    title="Minimum camera distance from the model in the 3D viewer (in metres). Default: 0.001. Prevents clipping into the geometry."
                    className="input w-full"
                  />
                  <p className="text-xs text-content-muted mt-1">Closest the camera can zoom in (metres). Prevents clipping through geometry.</p>
                </div>
              </div>

              <div className="flex gap-2">
                <button
                  onClick={() => {
                    updateSettingsMut.mutate(viewerDraft)
                    setViewerDraft({})
                  }}
                  disabled={Object.keys(viewerDraft).length === 0 || updateSettingsMut.isPending}
                  className="btn-primary disabled:opacity-40"
                >
                  Save 3D Viewer Settings
                </button>
                {Object.keys(viewerDraft).length > 0 && (
                  <button
                    onClick={() => setViewerDraft({})}
                    className="btn-secondary"
                  >
                    Reset
                  </button>
                )}
              </div>
            </div>
          </div>
        </div>

      </>}

      {/* ================================================================== */}
      {/* Output Types                                                        */}
      {/* ================================================================== */}
      {activeTab === 'output-types' && <>
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <Layers size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Output Types</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Define what kinds of outputs orders can request (thumbnails, views, formats).</p>
        </div>
        <div className="card">
          <OutputTypeTable />
        </div>
      </>}

      {/* ================================================================== */}
      {/* Templates & Positions                                               */}
      {/* ================================================================== */}
      {activeTab === 'templates' && <>

        {/* ── Render Templates ──────────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <FileBox size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Render Templates</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Upload .blend studio setups matched by Category + Output Type. Geometry is imported into the template at render time.</p>

          <div className="card">
            <div className="p-4">
              <RenderTemplateTable />
            </div>
            <div className="border-t border-border-light p-4">
              <MaterialLibraryPanel />
            </div>
          </div>
        </div>

        {/* ── Global Render Positions ───────────────────────────────────── */}
        {isAdmin && (
          <div className="mb-6">
            <div className="flex items-center gap-2 mb-1">
              <Monitor size={18} className="text-accent" />
              <h2 className="text-base font-semibold text-content">Global Render Positions</h2>
            </div>
            <p className="text-sm text-content-muted mb-4">Camera rotation presets available to all products. Per-product positions override these.</p>

            <div className="card">
              <div className="p-4">
                <GlobalRenderPositionsPanel />
              </div>
            </div>
          </div>
        )}

        {/* ── Material Library link ─────────────────────────────────────── */}
        <div className="card p-5 flex items-center justify-between">
          <div>
            <h2 className="font-semibold text-content">Material Library</h2>
            <p className="text-xs text-content-muted mt-0.5">
              Manage shared materials for CAD part assignments.
            </p>
          </div>
          <Link to="/materials" className="btn-secondary text-sm">
            Open Material Library
          </Link>
        </div>

      </>}

      {/* ================================================================== */}
      {/* Pricing                                                             */}
      {/* ================================================================== */}
      {activeTab === 'pricing' && <>
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <DollarSign size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Pricing Tiers</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Configure price per rendering item by category and quality level.</p>
        </div>
        <div className="card">
          <PricingTierTable />
        </div>
      </>}

      {/* ================================================================== */}
      {/* Libraries                                                           */}
      {/* ================================================================== */}
      {activeTab === 'libraries' && <>
        <AssetLibraryPanel />

        {/* ── Templates (legacy editor) ─────────────────────────────────── */}
        <div className="card">
          <div className="p-4 border-b border-border-default">
            <h2 className="font-semibold text-content">Templates</h2>
            <p className="text-xs text-content-muted mt-0.5">
              Click Edit to configure standard fields and component schema for each template.
            </p>
          </div>
          <div className="divide-y divide-border-light">
            {templates?.map((t) => {
              const isEditing = editingTemplateId === t.id
              return (
                <div key={t.id}>
                  {/* Row */}
                  <div className="flex items-center px-6 py-3 gap-3">
                    <button
                      onClick={() => setEditingTemplateId(isEditing ? null : t.id)}
                      className="text-content-muted hover:text-content-secondary transition-colors shrink-0"
                      aria-label={isEditing ? 'Collapse editor' : 'Expand editor'}
                      title={isEditing ? 'Collapse template editor' : 'Expand template editor'}
                    >
                      {isEditing ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
                    </button>
                    <div className="flex-1 min-w-0">
                      <p className="text-sm font-medium text-content">{t.name}</p>
                      <p className="text-xs text-content-muted font-mono">{t.category_key}</p>
                    </div>
                    <span className={`badge ${t.is_active ? 'badge-green' : 'badge-gray'}`}>
                      {t.is_active ? 'active' : 'inactive'}
                    </span>
                    <button
                      onClick={() => setEditingTemplateId(isEditing ? null : t.id)}
                      className="flex items-center gap-1.5 px-3 py-1 rounded-md border border-border-default text-xs text-content-secondary hover:bg-surface-hover hover:border-accent hover:text-accent transition-colors"
                    >
                      <Pencil size={12} />
                      {isEditing ? 'Close' : 'Edit'}
                    </button>
                  </div>

                  {/* Inline editor panel */}
                  {isEditing && (
                    <div className="px-6 pb-6">
                      <TemplateEditor
                        template={t}
                        onClose={() => setEditingTemplateId(null)}
                      />
                    </div>
                  )}
                </div>
              )
            })}
          </div>
        </div>
      </>}

      {/* ================================================================== */}
      {/* System Tools                                                        */}
      {/* ================================================================== */}
      {activeTab === 'system' && isAdmin && <>

        {/* ── Blender Status ─────────────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <Cpu size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Render Worker Status</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Checks Blender availability on the render-worker container via Celery.</p>

          <div className="card p-5">
            <div className="flex items-center justify-between mb-4">
              <div className="flex items-center gap-3">
                {blenderStatus ? (
                  blenderStatus.available ? (
                    <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-status-success-bg text-status-success-text">
                      <CheckCircle2 size={12} /> Blender Available
                    </span>
                  ) : (
                    <span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium bg-status-error-bg text-status-error-text">
                      <XCircle size={12} /> Blender Unavailable
                    </span>
                  )
                ) : (
                  <span className="text-xs text-content-muted">Not checked yet</span>
                )}
              </div>
              <button
                onClick={() => refetchBlenderStatus()}
                disabled={blenderStatusFetching}
                className="btn-secondary text-sm flex items-center gap-1.5"
              >
                <RefreshCw size={13} className={blenderStatusFetching ? 'animate-spin' : ''} />
                Refresh
              </button>
            </div>
            {blenderStatus && (
              <div className="bg-surface-alt rounded-md p-4 space-y-2">
                {blenderStatus.version && (
                  <div className="flex items-center gap-2">
                    <span className="text-xs font-semibold text-content-secondary w-24 shrink-0">Version</span>
                    <span className="text-xs text-content font-mono">{blenderStatus.version}</span>
                  </div>
                )}
                {blenderStatus.blender_bin && (
                  <div className="flex items-center gap-2">
                    <span className="text-xs font-semibold text-content-secondary w-24 shrink-0">Binary</span>
                    <span className="text-xs text-content font-mono">{blenderStatus.blender_bin}</span>
                  </div>
                )}
                {blenderStatus.error && (
                  <div className="flex items-start gap-2">
                    <span className="text-xs font-semibold text-content-secondary w-24 shrink-0">Error</span>
                    <span className="text-xs text-status-error-text font-mono">{blenderStatus.error}</span>
                  </div>
                )}
              </div>
            )}
          </div>
        </div>

        {/* ── Reprocessing ──────────────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <RefreshCw size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Reprocessing</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Queue STEP files for reprocessing, re-render thumbnails, or re-extract metadata.</p>

          <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Stuck File Recovery</h3>
              <p className="text-xs text-content-muted mb-3">Resets files stuck in 'processing' for more than 10 minutes to 'failed'. Runs automatically every 5 min.</p>
              <div className="space-y-2">
                <button
                  onClick={() => recoverStuckMut.mutate()}
                  disabled={recoverStuckMut.isPending}
                  className="btn-secondary text-sm w-full justify-start border-amber-400/40 text-amber-600 hover:bg-amber-50"
                >
                  <RefreshCw size={14} className={recoverStuckMut.isPending ? 'animate-spin' : ''} />
                  {recoverStuckMut.isPending ? 'Recovering...' : 'Recover Stuck Processing'}
                </button>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Process Unprocessed</h3>
              <p className="text-xs text-content-muted mb-3">Queues all pending/failed STEP files for initial processing.</p>
              <div className="space-y-2">
                <div className="flex items-center gap-1.5">
                  <button
                    onClick={() => processUnprocessedMut.mutate()}
                    disabled={processUnprocessedMut.isPending}
                    className="btn-secondary text-sm flex-1 justify-start"
                  >
                    <RefreshCw size={14} className={processUnprocessedMut.isPending ? 'animate-spin' : ''} />
                    {processUnprocessedMut.isPending ? 'Queueing...' : 'Process Unprocessed'}
                  </button>
                  <HelpTooltip helpKey="action.process_unprocessed" position="left" />
                </div>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Regenerate Thumbnails</h3>
              <p className="text-xs text-content-muted mb-3">Re-renders thumbnails for all completed CAD files using current Blender settings.</p>
              <div className="space-y-2">
                <div className="flex items-center gap-1.5">
                  <button
                    onClick={() => regenerateMut.mutate()}
                    disabled={regenerateMut.isPending}
                    className="btn-secondary text-sm flex-1 justify-start"
                  >
                    <RefreshCw size={14} className={regenerateMut.isPending ? 'animate-spin' : ''} />
                    {regenerateMut.isPending ? 'Re-queuing...' : 'Regenerate All Thumbnails'}
                  </button>
                  <HelpTooltip helpKey="action.regenerate_thumbnails" position="left" />
                </div>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Re-extract CAD Metadata</h3>
              <p className="text-xs text-content-muted mb-3">Updates dimensions and edge data for existing files (no re-render).</p>
              <div className="space-y-2">
                <button
                  onClick={() => reextractMetadataMut.mutate()}
                  disabled={reextractMetadataMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <RefreshCw size={14} className={reextractMetadataMut.isPending ? 'animate-spin' : ''} />
                  {reextractMetadataMut.isPending ? 'Queueing...' : 'Re-extract CAD Metadata'}
                </button>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Seed Standard Workflows</h3>
              <p className="text-xs text-content-muted mb-3">Creates the 4 standard workflow definitions if they don't exist yet.</p>
              <div className="space-y-2">
                <button
                  onClick={() => seedWorkflowsMut.mutate()}
                  disabled={seedWorkflowsMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <RefreshCw size={14} className={seedWorkflowsMut.isPending ? 'animate-spin' : ''} />
                  {seedWorkflowsMut.isPending ? 'Seeding...' : 'Seed Standard Workflows'}
                </button>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Re-extract Rich Metadata</h3>
              <p className="text-xs text-content-muted mb-3">Re-compute volume, surface area, complexity for all products with STEP files.</p>
              <div className="space-y-2">
                <button
                  onClick={() => reextractRichMetadataMut.mutate()}
                  disabled={reextractRichMetadataMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <RefreshCw size={14} className={reextractRichMetadataMut.isPending ? 'animate-spin' : ''} />
                  {reextractRichMetadataMut.isPending ? 'Queueing...' : 'Re-extract Rich Metadata'}
                </button>
              </div>
            </div>
          </div>
        </div>

        {/* ── USD / Canonical Scenes ────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <HardDrive size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">USD / Canonical Scenes</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Manage USD master exports and canonical scene generation for CAD files.</p>

          <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Generate Missing USD Masters</h3>
              <p className="text-xs text-content-muted mb-3">Exports USD canonical scene for all completed CAD files missing one.</p>
              <div className="space-y-2">
                <button
                  onClick={() => generateMissingUsdMastersMut.mutate()}
                  disabled={generateMissingUsdMastersMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <RefreshCw size={14} className={generateMissingUsdMastersMut.isPending ? 'animate-spin' : ''} />
                  {generateMissingUsdMastersMut.isPending ? 'Queueing...' : 'Generate Missing USD Masters'}
                </button>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Generate Missing Canonical Scenes</h3>
              <p className="text-xs text-content-muted mb-3">Queues geometry GLB + USD master for all completed CAD files missing a canonical scene.</p>
              <div className="space-y-2">
                <button
                  onClick={() => generateMissingCanonicalScenesMut.mutate()}
                  disabled={generateMissingCanonicalScenesMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <RefreshCw size={14} className={generateMissingCanonicalScenesMut.isPending ? 'animate-spin' : ''} />
                  {generateMissingCanonicalScenesMut.isPending ? 'Queueing...' : 'Generate Missing Canonical Scenes'}
                </button>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Regenerate All GLB + USD</h3>
              <p className="text-xs text-content-muted mb-3">Re-exports GLB and USD for all completed CAD files, replacing existing assets.</p>
              <div className="space-y-2">
                <button
                  onClick={() => regenerateAllCanonicalScenesMut.mutate()}
                  disabled={regenerateAllCanonicalScenesMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <RefreshCw size={14} className={regenerateAllCanonicalScenesMut.isPending ? 'animate-spin' : ''} />
                  {regenerateAllCanonicalScenesMut.isPending ? 'Queueing...' : 'Regenerate All GLB + USD'}
                </button>
              </div>
            </div>
          </div>
        </div>

        {/* ── Cleanup ───────────────────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <Trash2 size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Cleanup</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Import, clean up, or purge media assets and orphaned records.</p>

          <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Import Existing Media</h3>
              <p className="text-xs text-content-muted mb-3">Registers existing renders and CAD thumbnails in the Media Browser.</p>
              <div className="space-y-2">
                <button
                  onClick={() => importMediaAssetsMut.mutate()}
                  disabled={importMediaAssetsMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <RefreshCw size={14} className={importMediaAssetsMut.isPending ? 'animate-spin' : ''} />
                  {importMediaAssetsMut.isPending ? 'Importing...' : 'Import Existing Media'}
                </button>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Clean Up Orphaned Media</h3>
              <p className="text-xs text-content-muted mb-3">Removes DB records for renders whose files no longer exist on disk.</p>
              <div className="space-y-2">
                <button
                  onClick={() => cleanupOrphanedMut.mutate()}
                  disabled={cleanupOrphanedMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <Trash2 size={14} className={cleanupOrphanedMut.isPending ? 'animate-spin' : ''} />
                  {cleanupOrphanedMut.isPending ? 'Checking files...' : 'Clean Up Orphaned Media'}
                </button>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Clean Up Orphaned STEP Files</h3>
              <p className="text-xs text-content-muted mb-3">Removes STEP files, thumbnails, and DB records not linked to any product.</p>
              <div className="space-y-2">
                <button
                  onClick={() => setConfirmState({
                    open: true,
                    title: 'Delete Orphaned STEP Files',
                    message: 'Delete all orphaned STEP files (not linked to any product)? This cannot be undone.',
                    onConfirm: () => { cleanupOrphanedCadMut.mutate(); setConfirmState(s => ({ ...s, open: false })) },
                  })}
                  disabled={cleanupOrphanedCadMut.isPending}
                  className="btn-secondary text-sm w-full justify-start"
                >
                  <Trash2 size={14} className={cleanupOrphanedCadMut.isPending ? 'animate-spin' : ''} />
                  {cleanupOrphanedCadMut.isPending ? 'Deleting...' : 'Clean Up Orphaned STEP Files'}
                </button>
              </div>
            </div>

            <div className="card p-5">
              <h3 className="text-sm font-semibold text-content mb-1">Purge All Stills & Turntables</h3>
              <p className="text-xs text-content-muted mb-3">Deletes all rendered images and animations. Thumbnails, GLBs, and USD files are preserved.</p>
              <div className="space-y-2">
                <button
                  onClick={() => setConfirmState({
                    open: true,
                    title: 'Purge All Rendered Media',
                    message: 'Delete ALL still renders and turntable animations? Thumbnails, GLBs, and USD masters are kept. This cannot be undone.',
                    onConfirm: () => { purgeRenderMediaMut.mutate(); setConfirmState(s => ({ ...s, open: false })) },
                  })}
                  disabled={purgeRenderMediaMut.isPending}
                  className="btn-secondary text-sm w-full justify-start text-red-500"
                >
                  <Trash2 size={14} className={purgeRenderMediaMut.isPending ? 'animate-spin' : ''} />
                  {purgeRenderMediaMut.isPending ? 'Purging...' : 'Purge All Stills & Turntables'}
                </button>
              </div>
            </div>
          </div>
        </div>

        {/* ── GPU Status ────────────────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <Zap size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">GPU Status</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Verify that the render-worker is using the GPU (not CPU fallback).</p>

          <div className="card">
            <button
              className="w-full p-5 flex items-center justify-between text-left"
              onClick={() => setGpuProbeExpanded((v) => !v)}
            >
              <div className="flex items-center gap-3">
                <span className="text-sm font-medium text-content">GPU Probe Details</span>
              </div>
              <div className="flex items-center gap-3">
                {gpuStatusBadge()}
                {gpuProbeExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
              </div>
            </button>

            {gpuProbeExpanded && (
              <div className="px-5 pb-5 space-y-4 border-t border-border-default pt-4">
                <div className="flex items-center gap-3">
                  <button
                    onClick={handleRunGpuCheck}
                    disabled={gpuProbing}
                    className="btn-primary flex items-center gap-2"
                  >
                    {gpuProbing ? (
                      <><RefreshCw size={14} className="animate-spin" /> Running probe...</>
                    ) : (
                      <><Zap size={14} /> Run GPU Check</>
                    )}
                  </button>
                  {gpuProbeResult && (
                    <span className="text-xs text-content-muted">
                      Last checked: {gpuProbeResult.timestamp ? new Date(gpuProbeResult.timestamp).toLocaleString() : '--'}
                    </span>
                  )}
                </div>

                {gpuProbeResult && (
                  <div className="bg-surface-alt rounded-md p-4 space-y-2">
                    <div className="flex items-center gap-2">
                      <span className="text-xs font-semibold text-content-secondary w-28 shrink-0">Status</span>
                      {gpuStatusBadge()}
                    </div>
                    {gpuProbeResult.device_type && (
                      <div className="flex items-center gap-2">
                        <span className="text-xs font-semibold text-content-secondary w-28 shrink-0">Device type</span>
                        <span className="text-xs text-content">{gpuProbeResult.device_type}</span>
                      </div>
                    )}
                    {gpuProbeResult.devices && gpuProbeResult.devices.length > 0 && (
                      <div className="flex items-start gap-2">
                        <span className="text-xs font-semibold text-content-secondary w-28 shrink-0">Devices</span>
                        <div className="space-y-0.5">
                          {gpuProbeResult.devices.map((d: string, i: number) => (
                            <span key={i} className="block text-xs text-content">{d}</span>
                          ))}
                        </div>
                      </div>
                    )}
                    {gpuProbeResult.render_time_s != null && (
                      <div className="flex items-center gap-2">
                        <span className="text-xs font-semibold text-content-secondary w-28 shrink-0">Render time</span>
                        <span className="text-xs text-content">{gpuProbeResult.render_time_s.toFixed(2)}s</span>
                      </div>
                    )}
                    {gpuProbeResult.error && (
                      <div className="flex items-start gap-2">
                        <span className="text-xs font-semibold text-content-secondary w-28 shrink-0">Error</span>
                        <span className="text-xs text-status-error-text font-mono">{gpuProbeResult.error}</span>
                      </div>
                    )}
                  </div>
                )}

                {!gpuProbeResult && !gpuProbing && (
                  <p className="text-xs text-content-muted">No probe result yet. Click "Run GPU Check" to trigger a test render.</p>
                )}
              </div>
            )}
          </div>
        </div>

        {/* ── SMTP Settings ─────────────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <Mail size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">E-Mail Notifications (SMTP)</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Configure outbound SMTP for email notifications. Enable only when credentials are set.</p>

          <div className="card">
            <div className="p-6 space-y-4">
              <div className="flex items-center gap-3">
                <label className="flex items-center gap-2 cursor-pointer">
                  <input
                    type="checkbox"
                    checked={smtp.smtp_enabled ?? false}
                    onChange={(e) => setSmtpDraft(d => ({ ...d, smtp_enabled: e.target.checked }))}
                    className="w-4 h-4 rounded"
                  />
                  <span className="text-sm font-medium text-content">Enable email sending</span>
                </label>
                {smtp.smtp_enabled && (
                  <span className="badge badge-green text-xs">Active</span>
                )}
              </div>
              <div className="grid grid-cols-2 gap-4">
                <div>
                  <label className="block text-xs font-medium text-content-secondary mb-1">SMTP Host</label>
                  <input
                    type="text"
                    value={smtp.smtp_host ?? ''}
                    onChange={(e) => setSmtpDraft(d => ({ ...d, smtp_host: e.target.value }))}
                    placeholder="smtp.example.com"
                    className="w-full px-3 py-1.5 rounded-md border border-border-default text-sm bg-surface text-content focus:outline-none focus:ring-2 focus:ring-accent"
                  />
                </div>
                <div>
                  <label className="block text-xs font-medium text-content-secondary mb-1">Port</label>
                  <input
                    type="number"
                    value={smtp.smtp_port ?? 587}
                    onChange={(e) => setSmtpDraft(d => ({ ...d, smtp_port: parseInt(e.target.value) || 587 }))}
                    className="w-full px-3 py-1.5 rounded-md border border-border-default text-sm bg-surface text-content focus:outline-none focus:ring-2 focus:ring-accent"
                  />
                </div>
                <div>
                  <label className="block text-xs font-medium text-content-secondary mb-1">Username</label>
                  <input
                    type="text"
                    value={smtp.smtp_user ?? ''}
                    onChange={(e) => setSmtpDraft(d => ({ ...d, smtp_user: e.target.value }))}
                    placeholder="user@example.com"
                    className="w-full px-3 py-1.5 rounded-md border border-border-default text-sm bg-surface text-content focus:outline-none focus:ring-2 focus:ring-accent"
                  />
                </div>
                <div>
                  <label className="block text-xs font-medium text-content-secondary mb-1">Password</label>
                  <input
                    type="password"
                    value={smtp.smtp_password ?? ''}
                    onChange={(e) => setSmtpDraft(d => ({ ...d, smtp_password: e.target.value }))}
                    placeholder="--------"
                    className="w-full px-3 py-1.5 rounded-md border border-border-default text-sm bg-surface text-content focus:outline-none focus:ring-2 focus:ring-accent"
                  />
                </div>
                <div className="col-span-2">
                  <label className="block text-xs font-medium text-content-secondary mb-1">From Address</label>
                  <input
                    type="email"
                    value={smtp.smtp_from_address ?? ''}
                    onChange={(e) => setSmtpDraft(d => ({ ...d, smtp_from_address: e.target.value }))}
                    placeholder="noreply@hartomat.com"
                    className="w-full px-3 py-1.5 rounded-md border border-border-default text-sm bg-surface text-content focus:outline-none focus:ring-2 focus:ring-accent"
                  />
                </div>
              </div>
              <button
                onClick={() => { updateSettingsMut.mutate(smtpDraft as any); setSmtpDraft({}) }}
                disabled={updateSettingsMut.isPending || Object.keys(smtpDraft).length === 0}
                className="btn-primary text-sm"
              >
                Save SMTP Settings
              </button>
            </div>
          </div>
        </div>

        {/* ── Dashboard Config ──────────────────────────────────────────── */}
        <div className="mb-6">
          <div className="flex items-center gap-2 mb-1">
            <LayoutDashboard size={18} className="text-accent" />
            <h2 className="text-base font-semibold text-content">Dashboard Configuration</h2>
          </div>
          <p className="text-sm text-content-muted mb-4">Sets the default widget layout for all users of this tenant. Users can customize their own layout individually.</p>

          <div className="card">
            <div className="p-4 flex items-center gap-4">
              <div className="flex-1">
                <p className="text-sm text-content-secondary">
                  Tenant default:{' '}
                  <span className="font-medium text-content">
                    {tenantDefaultWidgets && tenantDefaultWidgets.length > 0
                      ? `${tenantDefaultWidgets.length} Widget${tenantDefaultWidgets.length !== 1 ? 's' : ''} configured`
                      : 'No default set yet (system default active)'}
                  </span>
                </p>
              </div>
              <button
                onClick={() => setShowTenantDashboardModal(true)}
                className="btn-secondary text-sm flex items-center gap-2"
              >
                <LayoutDashboard size={14} />
                Edit Tenant Default Dashboard
              </button>
            </div>
          </div>
        </div>

      </>}

      {showTenantDashboardModal && (
        <DashboardCustomizeModal
          currentWidgets={tenantDefaultWidgets ?? []}
          onClose={() => setShowTenantDashboardModal(false)}
          tenantMode={true}
        />
      )}

      <ConfirmModal
        open={confirmState.open}
        title={confirmState.title}
        message={confirmState.message}
        onConfirm={confirmState.onConfirm}
        onCancel={() => setConfirmState((s) => ({ ...s, open: false }))}
      />
    </div>
    </div>
  )
}


function MaterialLibraryPanel() {
  return (
    <div className="space-y-2">
      <h4 className="text-sm font-semibold text-content-secondary">Material Library</h4>
      <p className="text-xs text-content-muted">
        Materials for "Material Replace" are now managed via Asset Libraries. The active asset library's materials are used at render time.
      </p>
      <Link
        to="/asset-libraries"
        className="inline-flex items-center gap-1 text-sm text-accent hover:text-accent-hover"
      >
        <Layers size={14} />
        Manage Asset Libraries
      </Link>
    </div>
  )
}


function PricingSummaryCard() {
  const { data: tiers } = useQuery({
    queryKey: ['pricing-tiers'],
    queryFn: listPricingTiers,
  })
  const { data: outputTypes } = useQuery({
    queryKey: ['output-types-admin'],
    queryFn: () => listOutputTypes(true),
  })

  const defaultTier = tiers?.find((t) => t.category_key === 'default' && t.is_active)
  const activeTiers = tiers?.filter((t) => t.is_active).length ?? 0
  const totalOTs = outputTypes?.length ?? 0
  const otsWithTier = outputTypes?.filter((ot) => ot.pricing_tier_id != null).length ?? 0

  return (
    <div className="card">
      <div className="p-4 border-b border-border-default flex items-center gap-2">
        <DollarSign size={16} className="text-content-muted" />
        <h2 className="font-semibold text-content">Pricing Overview</h2>
      </div>
      <div className="p-4 grid grid-cols-2 md:grid-cols-4 gap-4">
        <div className="text-center">
          <p className="text-2xl font-bold text-content">
            {defaultTier ? `${Number(defaultTier.price_per_item).toFixed(2)}` : '--'}
          </p>
          <p className="text-xs text-content-muted">Global default price</p>
          {!defaultTier && (
            <p className="text-xs text-amber-600 flex items-center justify-center gap-1 mt-1">
              <AlertTriangle size={10} /> Not configured
            </p>
          )}
        </div>
        <div className="text-center">
          <p className="text-2xl font-bold text-content">{activeTiers}</p>
          <p className="text-xs text-content-muted">Active pricing tiers</p>
        </div>
        <div className="text-center">
          <p className="text-2xl font-bold text-content">{otsWithTier} / {totalOTs}</p>
          <p className="text-xs text-content-muted">Output types with explicit tier</p>
        </div>
        <div className="text-center">
          <p className="text-2xl font-bold text-content">{totalOTs - otsWithTier}</p>
          <p className="text-xs text-content-muted">Using category default</p>
        </div>
      </div>
    </div>
  )
}


// -- Asset Library Panel -------------------------------------------------------------------

function AssetLibraryPanel() {
  const qc = useQueryClient()
  const [showCreate, setShowCreate] = useState(false)
  const [newName, setNewName] = useState('')
  const [newDesc, setNewDesc] = useState('')
  const [newFile, setNewFile] = useState<File | null>(null)
  const [expanded, setExpanded] = useState<Set<string>>(new Set())
  const [confirmState, setConfirmState] = useState<{ open: boolean; title: string; message: string; onConfirm: () => void }>({ open: false, title: '', message: '', onConfirm: () => {} })

  const { data: libraries = [] } = useQuery({
    queryKey: ['asset-libraries'],
    queryFn: listAssetLibraries,
  })

  const createMut = useMutation({
    mutationFn: () => createAssetLibrary({
      name: newName.trim(),
      description: newDesc.trim() || undefined,
      blend_file: newFile!,
    }),
    onSuccess: () => {
      toast.success('Asset library created')
      qc.invalidateQueries({ queryKey: ['asset-libraries'] })
      setShowCreate(false)
      setNewName('')
      setNewDesc('')
      setNewFile(null)
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed to create'),
  })

  const deleteMut = useMutation({
    mutationFn: (id: string) => deleteAssetLibrary(id),
    onSuccess: () => {
      toast.success('Asset library deleted')
      qc.invalidateQueries({ queryKey: ['asset-libraries'] })
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed to delete'),
  })

  const refreshMut = useMutation({
    mutationFn: (id: string) => refreshAssetLibraryCatalog(id),
    onSuccess: () => {
      toast.success('Catalog refresh queued')
      setTimeout(() => qc.invalidateQueries({ queryKey: ['asset-libraries'] }), 3000)
    },
    onError: (e: any) => toast.error(e.response?.data?.detail || 'Failed to refresh'),
  })

  const toggle = (id: string) =>
    setExpanded((s) => { const n = new Set(s); n.has(id) ? n.delete(id) : n.add(id); return n })

  return (
    <div className="card">
      <div className="p-4 border-b border-border-light flex items-center justify-between">
        <div className="flex items-center gap-2">
          <Layers size={16} className="text-content-muted" />
          <div>
            <h2 className="font-semibold text-content">Asset Libraries</h2>
            <p className="text-xs text-content-muted mt-0.5">
              Upload Blender .blend files containing production materials and node groups.
            </p>
          </div>
        </div>
        <button className="btn-primary" onClick={() => setShowCreate(!showCreate)}>
          <Plus size={14} />New Library
        </button>
      </div>

      {showCreate && (
        <div className="p-4 border-b border-border-light bg-surface-alt space-y-3">
          <div className="flex gap-3">
            <input
              className="input-base flex-1"
              placeholder="Library name"
              value={newName}
              onChange={(e) => setNewName(e.target.value)}
            />
            <input
              className="input-base flex-1"
              placeholder="Description (optional)"
              value={newDesc}
              onChange={(e) => setNewDesc(e.target.value)}
            />
          </div>
          <div className="flex items-center gap-3 flex-wrap">
            <input
              type="file"
              accept=".blend,application/octet-stream"
              className="input-base min-w-[18rem] flex-1 file:mr-3 file:rounded-md file:border-0 file:bg-accent file:px-3 file:py-1.5 file:text-sm file:font-medium file:text-accent-text hover:file:bg-accent-hover"
              onChange={(e) => setNewFile(e.target.files?.[0] ?? null)}
            />
            {newFile && (
              <span className="text-xs text-content-muted font-mono">
                {newFile.name}
              </span>
            )}
            <button
              className="btn-primary"
              disabled={!newName.trim() || !newFile || createMut.isPending}
              onClick={() => createMut.mutate()}
            >
              {createMut.isPending ? 'Creating...' : 'Create'}
            </button>
            <button className="btn-secondary" onClick={() => setShowCreate(false)}>
              <X size={14} />
            </button>
          </div>
        </div>
      )}

      {libraries.length === 0 ? (
        <div className="p-8 text-center text-content-muted text-sm">
          No asset libraries yet. Upload a .blend file to get started.
        </div>
      ) : (
        <div className="divide-y divide-border-light">
          {(libraries as AssetLibrary[]).map((lib) => {
            const isExpanded = expanded.has(lib.id)
            const matCount = lib.catalog.materials.length
            const ngCount = lib.catalog.node_groups.length
            return (
              <div key={lib.id}>
                <div className="p-4 flex items-center gap-3">
                  <button
                    onClick={() => toggle(lib.id)}
                    className="text-content-muted hover:text-content"
                  >
                    {isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
                  </button>
                  <div className="flex-1 min-w-0">
                    <p className="font-medium text-content text-sm">{lib.name}</p>
                    {lib.description && (
                      <p className="text-xs text-content-muted">{lib.description}</p>
                    )}
                  </div>
                  <span className="text-xs text-content-muted">
                    {lib.original_filename ?? '--'}
                  </span>
                  <span className="badge-neutral text-xs">{matCount} materials</span>
                  <span className="badge-neutral text-xs">{ngCount} node groups</span>
                  <button
                    className="btn-secondary text-xs"
                    onClick={() => refreshMut.mutate(lib.id)}
                    disabled={refreshMut.isPending}
                    title="Re-scan catalog from .blend"
                  >
                    <RefreshCw size={12} />Refresh
                  </button>
                  <button
                    className="btn-danger text-xs"
                    onClick={() => {
                      setConfirmState({
                        open: true,
                        title: 'Delete Asset Library',
                        message: `Delete "${lib.name}"?`,
                        onConfirm: () => {
                          deleteMut.mutate(lib.id)
                          setConfirmState((s) => ({ ...s, open: false }))
                        },
                      })
                    }}
                  >
                    <Trash2 size={12} />
                  </button>
                </div>

                {isExpanded && (
                  <div className="px-10 pb-4 space-y-3">
                    {matCount > 0 && (
                      <div>
                        <p className="text-xs font-medium text-content-muted mb-1">Materials</p>
                        <div className="flex flex-wrap gap-1">
                          {lib.catalog.materials.map((m) => {
                            const name = typeof m === 'string' ? m : m.name
                            return (
                              <span key={name} className="text-xs px-2 py-0.5 rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
                                {name}
                              </span>
                            )
                          })}
                        </div>
                      </div>
                    )}
                    {ngCount > 0 && (
                      <div>
                        <p className="text-xs font-medium text-content-muted mb-1">Node Groups</p>
                        <div className="flex flex-wrap gap-1">
                          {lib.catalog.node_groups.map((ng) => (
                            <span key={ng} className="text-xs px-2 py-0.5 rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
                              {ng}
                            </span>
                          ))}
                        </div>
                      </div>
                    )}
                    {matCount === 0 && ngCount === 0 && (
                      <p className="text-xs text-content-muted italic">
                        No assets found. Click "Refresh" to scan the .blend for marked assets.
                      </p>
                    )}
                  </div>
                )}
              </div>
            )
          })}
        </div>
      )}

      <ConfirmModal
        open={confirmState.open}
        title={confirmState.title}
        message={confirmState.message}
        onConfirm={confirmState.onConfirm}
        onCancel={() => setConfirmState((s) => ({ ...s, open: false }))}
      />
    </div>
  )
}
