import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import type { ComponentType, MouseEvent as ReactMouseEvent, ReactNode } from 'react'
import {
  AlertTriangle,
  Archive,
  ArrowDownToLine,
  ArrowLeft,
  ArrowRight,
  Box,
  Check,
  CheckCircle2,
  CheckSquare,
  ChevronDown,
  ChevronRight,
  Circle,
  CircleDot,
  Clock3,
  Code2,
  Command,
  Copy,
  Download,
  FileBox,
  FilePlus2,
  FileText,
  FolderOpen,
  HardDrive,
  HelpCircle,
  Info,
  Layers3,
  LayoutGrid,
  Library,
  ListTree,
  Menu,
  Maximize2,
  MoreHorizontal,
  MousePointer2,
  PanelBottom,
  PanelLeft,
  PanelRight,
  Pause,
  Pencil,
  Plus,
  Rotate3D,
  Save,
  Search,
  Settings2,
  SlidersHorizontal,
  Sparkles,
  SquareStack,
  Trash2,
  Undo2,
  Redo2,
  RefreshCw,
  Upload,
  UserRound,
  WandSparkles,
  X,
  ZoomIn,
  ZoomOut,
} from 'lucide-react'
import { menuDefinitions, workbenchDefinitions, type MenuName, type WorkbenchId } from './freecadManifest'
import { createLinuxcncIframeAdapter, createProductionFacade, createPumpHousingDemoDocument, NativeOcctHistoryWorkerProvider, prepareCamPipeline, submitCamPipelineToLinuxcnc, type BitBybitViewportAdapter, type BitBybitWebCadFacade, type CamAxisConfiguration, type CamCoolantMode, type CamCutDirection, type CamDressupKind, type CamKinematicOptions, type CamOperationKind, type CamPostprocessor, type CamPropertyValue, type CamRotaryAxis, type CamSnapshot, type CamStockMode, type DiagnosticTreeNode, type DocumentSnapshot, type FcstdInspection, type ModelTreeItem, type MultiTransformStep, type MultiTransformValue, type ObjectPropertySnapshot, type PlacementValue, type ProjectSummary, type PropertyValue, type ShapeHandle } from './facade'
import type { ColorListValue, ColorValue, IncludedFileValue, LinkSubListValue, LinkSubValue, ShapeResourceValue, TopoRefValue } from './facade/types'
import { isFreecadPropertyHidden, isFreecadPropertyReadOnly } from './facade/propertyStatus'

type Page = 'start' | 'projects' | 'workspace' | 'import' | 'export' | 'settings' | 'help' | 'diagnostics' | 'sync'
type Workbench = WorkbenchId
type DialogKind = 'command-search' | 'preferences' | 'about' | 'macro' | 'close-document' | null

type Icon = ComponentType<{ size?: string | number; strokeWidth?: string | number; className?: string }>

const pageFromPath = (): Page => {
  const path = window.location.pathname
  if (path.startsWith('/projects')) return 'projects'
  if (path.startsWith('/workspace')) return 'workspace'
  if (path.startsWith('/import')) return 'import'
  if (path.startsWith('/export')) return 'export'
  if (path.startsWith('/settings')) return 'settings'
  if (path.startsWith('/help')) return 'help'
  if (path.startsWith('/diagnostics')) return 'diagnostics'
  if (path.startsWith('/sync')) return 'sync'
  return 'start'
}

const iconMap: Record<string, Icon> = {
  box: Box, 'circle-dot': CircleDot, 'square-stack': SquareStack, 'file-text': FileText, 'layout-grid': LayoutGrid,
  layers: Layers3, archive: Archive, pencil: Pencil, copy: Copy, 'arrow-up': ArrowDownToLine, 'arrow-down': ArrowDownToLine,
  rotate: Rotate3D, 'sliders-horizontal': SlidersHorizontal, search: Search, upload: Upload, download: Download, 'file-plus': FilePlus2,
  'code-2': Code2, 'settings-2': Settings2, 'circle-check': CheckCircle2, 'triangle-alert': AlertTriangle, eye: CircleDot,
  'zoom-in': ZoomIn, 'grid-3x3': LayoutGrid, play: Check, plus: Plus, minus: X, circle: Circle, route: Rotate3D, 'git-branch': Layers3,
  'layers-2': Layers3, 'flip-horizontal': ArrowLeft, repeat: Rotate3D, 'refresh-cw': Rotate3D, shuffle: MoreHorizontal,
  'corner-down-right': SlidersHorizontal, scissors: X, triangle: Box, paperclip: Copy, 'move-horizontal': ArrowRight, 'move-vertical': ArrowDownToLine,
  magnet: CircleDot, move: ArrowRight, type: FileText, 'panels-top-left': LayoutGrid, maximize: PanelRight, table: LayoutGrid, tag: FileText,
  'rows-3': ListTree, link: Copy, paintbrush: WandSparkles, pin: PanelLeft, anchor: CircleDot, split: ArrowRight, target: CircleDot,
  protractor: SlidersHorizontal, activity: Rotate3D, lock: Circle, spline: Rotate3D, crosshair: CircleDot, 'file-box': FileBox,
  library: Library, 'mouse-pointer-2': MousePointer2, 'check-square': CheckSquare, 'wand-sparkles': WandSparkles,
}

const workbenchIconKeys: Record<Workbench, string> = {
  'Part Design': 'box', Part: 'box', Sketcher: 'circle-dot', Draft: 'square-stack', BIM: 'layers', TechDraw: 'file-text', Spreadsheet: 'layout-grid', Assembly: 'layers', CAM: 'settings-2', FEM: 'activity', Mesh: 'archive', Surface: 'spline', Inspection: 'search',
}

const workbenchMeta: Record<Workbench, { icon: Icon; accent: string; description: string }> = Object.fromEntries(
  Object.entries(workbenchDefinitions).map(([id, definition]) => [id, { icon: iconMap[workbenchIconKeys[id as Workbench]] || MoreHorizontal, accent: id === 'Sketcher' ? 'violet' : id === 'Part' ? 'amber' : 'cyan', description: definition.description }]),
) as Record<Workbench, { icon: Icon; accent: string; description: string }>

function CuboidIcon({ size = 18, strokeWidth = 1.8, className }: { size?: string | number; strokeWidth?: string | number; className?: string }) {
  return <Box size={size} strokeWidth={strokeWidth} className={className} />
}

const projects = [
  { name: 'Pump Housing', path: 'Local / Mechanical / Pump Housing', modified: 'Just now', objects: 18, state: 'Unsaved' },
  { name: 'Mounting Bracket', path: 'Local / Concepts / Mounting Bracket', modified: 'Yesterday', objects: 9, state: 'Saved' },
  { name: 'Motor Assembly', path: 'Local / Assemblies / Motor Assembly', modified: '28 Jul 2026', objects: 142, state: 'Saved' },
  { name: 'Valve Body Study', path: 'Shared / Review / Valve Body Study', modified: '22 Jul 2026', objects: 31, state: 'Read only' },
]

const IconButton = ({ icon: IconComponent, label, onClick, active = false, disabled = false }: { icon: Icon; label: string; onClick?: () => void; active?: boolean; disabled?: boolean }) => (
  <button className={`icon-button ${active ? 'is-active' : ''}`} title={label} aria-label={label} onClick={onClick} disabled={disabled}>
    <IconComponent size={16} strokeWidth={1.8} />
  </button>
)

const Badge = ({ tone, children }: { tone: 'cyan' | 'green' | 'amber' | 'red' | 'muted'; children: ReactNode }) => (
  <span className={`badge badge-${tone}`}>
    <span className="badge-dot" />
    {children}
  </span>
)

function App() {
  const [page, setPage] = useState<Page>(pageFromPath)
  const [leftTab, setLeftTab] = useState<'model' | 'tasks'>('model')
  const [rightTab, setRightTab] = useState<'data' | 'view'>('data')
  const [bottomOpen, setBottomOpen] = useState(true)
  const [notice, setNotice] = useState('')
  const facade = useMemo(() => createProductionFacade({ initialDocument: createPumpHousingDemoDocument(), initialSelectedObjectIds: ['pad'] }), [])
  const nativeHistory = useMemo(() => new NativeOcctHistoryWorkerProvider(), [])
  const [facadeState, setFacadeState] = useState(() => facade.getState())
  const [diagnosticFlash, setDiagnosticFlash] = useState<{ objectId: string; token: number } | null>(null)

  useEffect(() => facade.subscribe((event) => {
    if (event.type === 'state.changed') setFacadeState(event.state)
    if (event.type === 'diagnostic.focused') setDiagnosticFlash({ objectId: event.objectId, token: Date.now() })
    if (event.type === 'notice') {
      setNotice(event.message)
      window.setTimeout(() => setNotice(''), 2600)
    }
  }), [facade])

  useEffect(() => facade.project.subscribeExternalChanges((notice) => {
    setNotice(`Project saved in another tab · v${notice.documentVersion}`)
    window.setTimeout(() => setNotice(''), 2600)
  }), [facade])

  useEffect(() => () => facade.geometry.dispose(), [facade])

  useEffect(() => {
    facade.geometry.configureNativeHistory?.(nativeHistory)
    void nativeHistory.initialize().catch((error: unknown) => {
      setNotice(`Native history unavailable: ${error instanceof Error ? error.message : String(error)}`)
    })
    return () => {
      facade.geometry.configureNativeHistory?.(null)
      nativeHistory.dispose()
    }
  }, [facade, nativeHistory])

  useEffect(() => {
    const handlePopState = () => setPage(pageFromPath())
    window.addEventListener('popstate', handlePopState)
    return () => window.removeEventListener('popstate', handlePopState)
  }, [])

  const navigate = (next: Page, replace = false) => {
    const paths: Record<Page, string> = {
      start: '/start',
      projects: '/projects',
      workspace: '/workspace/pump-housing',
      import: '/import',
      export: '/export',
      settings: '/settings',
      help: '/help',
      diagnostics: '/diagnostics',
      sync: '/sync',
    }
    window.history[replace ? 'replaceState' : 'pushState']({}, '', paths[next])
    setPage(next)
  }

  const openWorkspace = () => navigate('workspace')
  const workbench = facadeState.activeWorkbench
  const selectedObject = facadeState.selectedObjectId
  const selectedObjects = facadeState.selectedObjectIds ?? (selectedObject ? [selectedObject] : [])
  const currentWorkbench = workbenchMeta[workbench]
  const CurrentIcon = currentWorkbench.icon

  const showNotice = useCallback((message: string) => facade.notify(message), [facade])
  const setWorkbench = useCallback((value: Workbench) => facade.gui.workbench.setActive(value), [facade])
  const setSelectedObject = useCallback((id: string) => id ? facade.selection.select(id) : facade.selection.clear(), [facade])

  return (
    <div className="app-shell" data-persistence-mode={facadeState.persistence.mode}>
      <TopBar page={page} workbench={workbench} facade={facade} onNavigate={navigate} onOpenWorkspace={openWorkspace} />
      {page === 'workspace' ? (
        <Workspace
          workbench={workbench}
          onNavigate={navigate}
          setWorkbench={(value) => { setWorkbench(value); setLeftTab('tasks') }}
          leftTab={leftTab}
          setLeftTab={setLeftTab}
          rightTab={rightTab}
          setRightTab={setRightTab}
          bottomOpen={bottomOpen}
          setBottomOpen={setBottomOpen}
          selectedObject={selectedObject}
          selectedObjects={selectedObjects}
          setSelectedObject={setSelectedObject}
          diagnosticFlash={diagnosticFlash}
          document={facadeState.document}
          facade={facade}
          workbenchIcon={CurrentIcon}
          showNotice={showNotice}
        />
      ) : (
        <PageFrame page={page} onNavigate={navigate} onOpenWorkspace={openWorkspace} showNotice={showNotice} facade={facade} />
      )}
      {notice && <div className="toast"><CheckCircle2 size={16} />{notice}</div>}
    </div>
  )
}

function TopBar({ page, workbench, facade, onNavigate, onOpenWorkspace }: { page: Page; workbench: Workbench; facade: BitBybitWebCadFacade; onNavigate: (page: Page) => void; onOpenWorkspace: () => void }) {
  const [openMenu, setOpenMenu] = useState<MenuName | null>(null)
  const [dialog, setDialog] = useState<DialogKind>(null)
  const [commandQuery, setCommandQuery] = useState('')
  const activeDocument = facade.getState().document
  const camSnapshot = useCamSnapshot(facade)
  const canUndo = workbench === 'CAM' ? camSnapshot.version >= 0 && facade.cam.canUndo() : facade.history.canUndo()
  const canRedo = workbench === 'CAM' ? camSnapshot.version >= 0 && facade.cam.canRedo() : facade.history.canRedo()
  const runUndo = () => workbench === 'CAM' ? facade.cam.undo() : facade.history.undo()
  const runRedo = () => workbench === 'CAM' ? facade.cam.redo() : facade.history.redo()
  const commandEntries = useMemo(() => (Object.keys(menuDefinitions) as MenuName[]).flatMap((menu) => menuDefinitions[menu].map((item) => ({ ...item, group: menu }))), [])
  const filteredCommands = useMemo(() => {
    const query = commandQuery.trim().toLowerCase()
    const workbenchCommands = Object.values(workbenchDefinitions).flatMap((definition) => definition.groups.flatMap((group) => group.commands.map((item) => ({ ...item, command: item.id, group: definition.id }))))
    return [...commandEntries, ...workbenchCommands]
      .filter((item, index, entries) => entries.findIndex((candidate) => candidate.command === item.command) === index)
      .filter((item) => !query || `${item.label} ${item.group} ${'shortcut' in item ? item.shortcut || '' : ''}`.toLowerCase().includes(query))
      .slice(0, 18)
  }, [commandEntries, commandQuery])
  useEffect(() => {
    const closeMenu = (event: PointerEvent) => {
      const target = event.target
      if (!(target instanceof Element) || !target.closest('.menu-wrapper')) setOpenMenu(null)
    }
    const closeOnEscape = (event: KeyboardEvent) => {
      if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
        event.preventDefault()
        setOpenMenu(null)
        setCommandQuery('')
        setDialog('command-search')
        return
      }
      const target = event.target
      const editingText = target instanceof HTMLElement && (target.isContentEditable || ['INPUT', 'TEXTAREA'].includes(target.tagName))
      if (!editingText && (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'z') {
        event.preventDefault()
        if (event.shiftKey) runRedo()
        else runUndo()
        return
      }
      if (!editingText && (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'y') {
        event.preventDefault()
        runRedo()
        return
      }
      if (event.key === 'Escape') {
        setOpenMenu(null)
        setDialog(null)
      }
    }
    window.document.addEventListener('pointerdown', closeMenu)
    window.document.addEventListener('keydown', closeOnEscape)
    return () => {
      window.document.removeEventListener('pointerdown', closeMenu)
      window.document.removeEventListener('keydown', closeOnEscape)
    }
  }, [facade, workbench])
  const handleMenuCommand = (command: string) => {
    setOpenMenu(null)
    if (command === 'help' || command === 'shortcuts') onNavigate('help')
    else if (command === 'diagnostics') onNavigate('diagnostics')
    else if (command === 'preferences' || command === 'project-settings' || command === 'parameters' || command === 'customize') setDialog('preferences')
    else if (command === 'import') onNavigate('import')
    else if (command === 'export') onNavigate('export')
    else if (command === 'open') onNavigate('projects')
    else if (command === 'about') setDialog('about')
    else if (command === 'macros' || command === 'record-macro' || command === 'stop-macro' || command === 'execute-macro') setDialog('macro')
    else if (command === 'close' || command === 'close-all') setDialog('close-document')
    else if (command === 'undo') runUndo()
    else if (command === 'redo') runRedo()
    else if (command === 'save') facade.gui.command.execute({ commandId: 'save' })
    else if (command === 'new-document') {
      facade.gui.command.execute({ commandId: 'new-document' })
      onOpenWorkspace()
    }
    else if (command.startsWith('CAM_')) {
      facade.gui.command.execute({ commandId: command })
      onOpenWorkspace()
    }
    else onOpenWorkspace()
  }
  return (
    <header className={`topbar ${page === 'workspace' ? 'topbar-workspace' : ''}`} aria-label="FreeCAD application chrome">
      <div className="brand-lockup" onClick={() => onNavigate('start')} role="button" tabIndex={0} aria-label="BitBybit Web FreeCAD start page">
        <div className="brand-mark"><span /><span /><span /></div>
        <div><div className="brand-name">BitBybit Web FreeCAD</div><div className="brand-product">CAD studio</div></div>
      </div>
      <nav className="top-menu" aria-label="Application menu">
        {(Object.keys(menuDefinitions) as MenuName[]).filter((menu) => menu !== 'CAM' || workbench === 'CAM').map((menu) => <div className="menu-wrapper" key={menu}><button className={openMenu === menu ? 'is-open' : ''} onMouseEnter={() => openMenu && setOpenMenu(menu)} onClick={() => setOpenMenu(openMenu === menu ? null : menu)} aria-expanded={openMenu === menu} aria-haspopup="menu">{menu}</button>{openMenu === menu && <div className={`menu-popover ${menu === 'CAM' ? 'menu-popover-cam' : ''}`} role="menu">{menuDefinitions[menu].map((item, index, entries) => { const group = 'group' in item ? item.group : undefined; const previous = entries[index - 1]; const previousGroup = previous && 'group' in previous ? previous.group : undefined; return <button role="menuitem" autoFocus={index === 0} className={group && group !== previousGroup ? 'menu-group-start' : ''} key={item.command} onClick={() => handleMenuCommand(item.command)}>{group && group !== previousGroup && <small className="menu-section-label">{group}</small>}<span>{item.label}</span>{'shortcut' in item && <kbd>{item.shortcut}</kbd>}</button> })}</div>}</div>)}
      </nav>
      <div className="topbar-spacer" />
      <div className="topbar-context">
        <div className="context-document"><span className="context-dot" />{activeDocument.label} <span className="context-separator">/</span> {workbench}</div>
        <span className="save-state"><span className="save-dot" />{activeDocument.dirty ? 'Unsaved changes' : 'Saved locally'}</span>
      </div>
      <IconButton icon={Undo2} label="Undo" onClick={runUndo} disabled={!canUndo} />
      <IconButton icon={Redo2} label="Redo" onClick={runRedo} disabled={!canRedo} />
      <div className="topbar-divider" />
      <button className="command-search" onClick={() => { setCommandQuery(''); setDialog('command-search') }}><Search size={15} /><span>Search commands</span><kbd>⌘ K</kbd></button>
      <IconButton icon={HelpCircle} label="Help" onClick={() => onNavigate('help')} />
      <button className="avatar-button" title="Account and sync" onClick={() => onNavigate('sync')}><UserRound size={16} /><span>MS</span></button>
      {dialog === 'command-search' && <CommandSearchDialog query={commandQuery} setQuery={setCommandQuery} commands={filteredCommands} onRun={(command) => { setDialog(null); setCommandQuery(''); handleMenuCommand(command) }} onClose={() => setDialog(null)} />}
      {dialog === 'preferences' && <PreferencesDialog facade={facade} onClose={() => setDialog(null)} />}
      {dialog === 'about' && <AboutDialog onClose={() => setDialog(null)} />}
      {dialog === 'macro' && <MacroDialog facade={facade} onClose={() => setDialog(null)} />}
      {dialog === 'close-document' && <CloseDocumentDialog facade={facade} onClose={() => setDialog(null)} />}
    </header>
  )
}

function FreeCadDialog({ title, subtitle, icon: DialogIcon, onClose, children, wide = false }: { title: string; subtitle: string; icon: Icon; onClose: () => void; children: ReactNode; wide?: boolean }) {
  const dialogRef = useRef<HTMLElement>(null)
  useEffect(() => {
    const dialog = dialogRef.current
    const preferred = dialog?.querySelector<HTMLElement>('input, select, textarea, button:not(.dialog-close)')
    ;(preferred ?? dialog?.querySelector<HTMLElement>('.dialog-close'))?.focus()
  }, [])
  return <div className="modal-backdrop" role="presentation" onMouseDown={(event) => { if (event.target === event.currentTarget) onClose() }}>
    <section className={`freecad-dialog ${wide ? 'is-wide' : ''}`} role="dialog" aria-modal="true" aria-labelledby="freecad-dialog-title" ref={dialogRef}>
      <div className="dialog-titlebar"><span className="dialog-title-icon"><DialogIcon size={15} /></span><span id="freecad-dialog-title">{title}</span><button className="dialog-close" aria-label="Close dialog" title="Close" onClick={onClose}><X size={15} /></button></div>
      <div className="dialog-subtitle">{subtitle}</div>
      {children}
    </section>
  </div>
}

function CommandSearchDialog({ query, setQuery, commands, onRun, onClose }: { query: string; setQuery: (value: string) => void; commands: Array<{ command: string; label: string; group: string; shortcut?: string }>; onRun: (command: string) => void; onClose: () => void }) {
  return <FreeCadDialog title="Command search" subtitle="Search menus and workbench commands." icon={Search} onClose={onClose} wide>
    <div className="command-palette"><div className="command-palette-input"><Search size={16} /><input autoFocus value={query} onChange={(event) => setQuery(event.target.value)} placeholder="Type a command or workbench" aria-label="Command search" /></div><div className="command-palette-list" role="listbox">{commands.map((item) => <button key={`${item.group}-${item.command}`} role="option" onClick={() => onRun(item.command)}><span><strong>{item.label}</strong><small>{item.group}</small></span>{item.shortcut && <kbd>{item.shortcut}</kbd>}</button>)}{commands.length === 0 && <div className="command-palette-empty">No matching command</div>}</div></div>
  </FreeCadDialog>
}

function PreferencesDialog({ facade, onClose }: { facade: BitBybitWebCadFacade; onClose: () => void }) {
  return <FreeCadDialog title="Preferences" subtitle="FreeCAD-compatible workspace preferences." icon={Settings2} onClose={onClose}>
    <div className="dialog-form"><label>Unit system<select defaultValue="Metric (mm, kg, s)"><option>Metric (mm, kg, s)</option><option>Imperial (in, lb, s)</option></select></label><label>Navigation style<select defaultValue="FreeCAD CAD"><option>FreeCAD CAD</option><option>Trackpad</option><option>Blender</option></select></label><label>Display mode<select defaultValue="Flat lines"><option>Flat lines</option><option>Shaded</option><option>Wireframe</option></select></label><label className="dialog-check"><input type="checkbox" defaultChecked />Show selection edges</label><label className="dialog-check"><input type="checkbox" defaultChecked />Restore dock layout on startup</label></div>
    <div className="dialog-footer"><button className="button button-quiet" onClick={onClose}>Cancel</button><button className="button button-primary" onClick={() => { facade.notify('Preferences saved'); onClose() }}><Save size={14} />OK</button></div>
  </FreeCadDialog>
}

function AboutDialog({ onClose }: { onClose: () => void }) {
  return <FreeCadDialog title="About BitBybit Web FreeCAD" subtitle="Version and runtime information." icon={Info} onClose={onClose}>
    <div className="dialog-body"><div className="dialog-mark"><CuboidIcon size={28} /></div><div><h2>BitBybit Web FreeCAD</h2><p>FreeCAD-compatible browser workspace</p><p className="dialog-muted">Version 0.1 · FreeCAD 1.1.1 interaction baseline</p><p className="dialog-muted">BitBybit Facade · OCCT WASM · Three.js · SQLite WASM + OPFS</p></div></div>
    <div className="dialog-footer"><button className="button button-primary" onClick={onClose}>Close</button></div>
  </FreeCadDialog>
}

function MacroDialog({ facade, onClose }: { facade: BitBybitWebCadFacade; onClose: () => void }) {
  return <FreeCadDialog title="Macros" subtitle="Manage sandboxed document automation." icon={Code2} onClose={onClose}>
    <div className="macro-dialog"><div className="macro-status"><span className="status-dot amber" />No macro recording active</div><p>Scripts run with explicit document and network permissions. Geometry changes remain undoable document transactions.</p></div>
    <div className="dialog-footer"><button className="button button-quiet" onClick={onClose}>Cancel</button><button className="button button-primary" onClick={() => { facade.notify('Macro workspace opened'); onClose() }}>Open script workspace</button></div>
  </FreeCadDialog>
}

function CloseDocumentDialog({ facade, onClose }: { facade: BitBybitWebCadFacade; onClose: () => void }) {
  return <FreeCadDialog title="Close document" subtitle="The active document contains unsaved changes." icon={FileBox} onClose={onClose}>
    <div className="dialog-warning"><AlertTriangle size={18} /><span>Save before closing to keep the current feature history and properties.</span></div>
    <div className="dialog-footer"><button className="button button-quiet" onClick={onClose}>Cancel</button><button className="button button-outline" onClick={() => { facade.notify('Close cancelled: discard is disabled in this preview'); onClose() }}>Discard</button><button className="button button-primary" onClick={() => { void facade.project.save().then(() => { facade.notify('Saved to local workspace'); onClose() }) }}><Save size={14} />Save</button></div>
  </FreeCadDialog>
}

function Workspace({ workbench, onNavigate, setWorkbench, leftTab, setLeftTab, rightTab, setRightTab, bottomOpen, setBottomOpen, selectedObject, selectedObjects, setSelectedObject, diagnosticFlash, document, facade, workbenchIcon: WorkbenchIcon, showNotice }: {
  workbench: Workbench
  onNavigate: (page: Page) => void
  setWorkbench: (value: Workbench) => void
  leftTab: 'model' | 'tasks'
  setLeftTab: (tab: 'model' | 'tasks') => void
  rightTab: 'data' | 'view'
  setRightTab: (tab: 'data' | 'view') => void
  bottomOpen: boolean
  setBottomOpen: (open: boolean) => void
  selectedObject: string
  selectedObjects: string[]
  setSelectedObject: (id: string) => void
  diagnosticFlash: { objectId: string; token: number } | null
  document: DocumentSnapshot
  facade: BitBybitWebCadFacade
  workbenchIcon: Icon
  showNotice: (message: string) => void
}) {
  const [leftOpen, setLeftOpen] = useState(true)
  const [rightOpen, setRightOpen] = useState(true)
  const [bottomTab, setBottomTab] = useState<'report' | 'jobs' | 'diagnostics'>('report')
  return (
    <main className="workspace-page">
      <div className="workspace-toolbar standard-toolbar" role="toolbar" aria-label="Standard toolbar">
        <div className="workbench-picker"><WorkbenchIcon size={17} /><select value={workbench} onChange={(event) => setWorkbench(event.target.value as Workbench)} aria-label="Workbench">{Object.keys(workbenchDefinitions).map((option) => <option key={option}>{option}</option>)}</select><ChevronDown size={14} /></div>
        <div className="toolbar-divider" />
        <IconButton icon={FilePlus2} label="New document" onClick={() => { facade.gui.command.execute({ commandId: 'new-document' }); showNotice('New document prepared') }} />
        <IconButton icon={FolderOpen} label="Open project" onClick={() => onNavigate('projects')} />
        <IconButton icon={Save} label="Save project" onClick={() => facade.gui.command.execute({ commandId: 'save' })} />
        <IconButton icon={RefreshCw} label="Recompute document" disabled={document.readOnly} onClick={() => { void facade.app.document.recomputeAsync().then((result) => showNotice(result.status === 'completed' ? 'Recompute completed' : `Recompute ${result.status}`)) }} />
        <div className="toolbar-divider" />
        <IconButton icon={Box} label="Create body" onClick={() => { facade.gui.command.execute({ commandId: 'create-body' }); showNotice('Create Body task opened') }} active={workbench === 'Part Design'} />
        <IconButton icon={CircleDot} label="Create sketch" onClick={() => { facade.gui.command.execute({ commandId: 'create-sketch' }); showNotice('Select a plane to create a sketch') }} />
        <IconButton icon={Rotate3D} label="Transform" />
        <IconButton icon={SlidersHorizontal} label="Measure" />
        <div className="toolbar-spacer" />
        <div className="toolbar-status"><span className="status-pulse" />Local workspace</div>
        <IconButton icon={PanelLeft} label="Toggle Combo View" active={leftOpen} onClick={() => setLeftOpen(!leftOpen)} />
        <IconButton icon={PanelRight} label="Toggle Selection View" active={rightOpen} onClick={() => setRightOpen(!rightOpen)} />
      </div>
      <WorkbenchCommandBar workbench={workbench} facade={facade} showNotice={showNotice} setLeftTab={setLeftTab} />
      <div className="document-tabs">
        <button className="doc-tab is-active"><FileBox size={15} /><span>{document.label}</span>{document.dirty && <span className="doc-unsaved" />}<X size={13} /></button>
        <button className="doc-tab" onClick={() => showNotice('Open Mounting Bracket') }><FileBox size={15} /><span>Mounting Bracket</span><X size={13} /></button>
        <button className="new-tab" title="New document"><Plus size={16} /></button>
        <div className="document-tab-spacer" />
        <span className="document-meta">{document.units} · {document.objects.length} objects · v{document.version}</span>
      </div>
      <div className={`workspace-content ${leftOpen ? '' : 'combo-collapsed'} ${rightOpen ? '' : 'selection-collapsed'}`}>
        {leftOpen && <aside className="combo-panel left-panel" aria-label="Combo View">
          <div className="panel-tabs"><button className={leftTab === 'model' ? 'is-active' : ''} onClick={() => setLeftTab('model')}><ListTree size={14} />Model</button><button className={leftTab === 'tasks' ? 'is-active' : ''} onClick={() => setLeftTab('tasks')}><SlidersHorizontal size={14} />Tasks <span className="tab-count">1</span></button></div>
          {leftTab === 'model' ? workbench === 'CAM' ? <CamJobPanel facade={facade} showNotice={showNotice} /> : <div className="combo-model"><ModelTree document={document} facade={facade} selectedObjects={selectedObjects} setSelectedObject={setSelectedObject} showNotice={showNotice} /><div className="combo-property"><div className="property-heading"><div><span className="eyebrow">Property view</span><h2>{document.tree.find((item) => item.id === selectedObject)?.label || 'No selection'}</h2></div><IconButton icon={MoreHorizontal} label="More object actions" /></div><div className="panel-tabs property-tabs"><button className={rightTab === 'data' ? 'is-active' : ''} onClick={() => setRightTab('data')}>Data</button><button className={rightTab === 'view' ? 'is-active' : ''} onClick={() => setRightTab('view')}>View</button></div><PropertyPanel key={rightTab} facade={facade} objectId={selectedObject} scope={rightTab} showNotice={showNotice} /></div></div> : <TaskPanel workbench={workbench} facade={facade} showNotice={showNotice} />}
        </aside>}
        <section className="viewport-region">
          <Viewport selectedObject={selectedObject} selectedObjects={selectedObjects} setSelectedObject={setSelectedObject} diagnosticFlash={diagnosticFlash} workbench={workbench} facade={facade} documentVersion={document.version} recomputeGeneration={document.recompute?.generation ?? 0} showNotice={showNotice} />
          <div className="viewport-bottom-left"><div className="view-chip"><Circle size={8} fill="currentColor" /> Perspective</div><div className="view-chip">Grid 10 mm</div></div>
          <div className="viewport-bottom-right"><div className="axis-widget"><span className="axis-x">X</span><span className="axis-y">Y</span><span className="axis-z">Z</span><div className="axis-origin" /></div></div>
        </section>
        {rightOpen && <SelectionViewPanel selectedObject={selectedObject} document={document} facade={facade} setSelectedObject={setSelectedObject} showNotice={showNotice} />}
      </div>
      <div className={`bottom-drawer ${bottomOpen ? 'is-open' : ''}`}>
        <div className="bottom-drawer-header"><div className="drawer-tabs"><button className={bottomTab === 'report' ? 'is-active' : ''} onClick={() => { setBottomTab('report'); setBottomOpen(true) }}><PanelBottom size={14} />Report view <span className="tab-count">2</span></button><button className={bottomTab === 'jobs' ? 'is-active' : ''} onClick={() => { setBottomTab('jobs'); setBottomOpen(true) }}><Clock3 size={14} />Jobs <span className="tab-count tab-count-green">1</span></button><button className={bottomTab === 'diagnostics' ? 'is-active' : ''} onClick={() => { setBottomTab('diagnostics'); setBottomOpen(true) }}><Code2 size={14} />Diagnostics</button></div><button className="drawer-toggle" onClick={() => setBottomOpen(!bottomOpen)} title={bottomOpen ? 'Collapse bottom panel' : 'Expand bottom panel'}>{bottomOpen ? <ChevronDown size={15} /> : <ChevronRight size={15} />}</button></div>
        {bottomOpen && <div className="bottom-drawer-content">{bottomTab === 'report' && <><div className="report-line">{document.recompute?.status === 'failed' ? <AlertTriangle size={14} className="icon-amber" /> : <CheckCircle2 size={14} className="icon-green" />}<span>Recompute {document.recompute?.status || 'idle'}</span><span className="report-context">{document.recompute?.order.length || 0} objects in plan</span><span className="report-time">generation {document.recompute?.generation || 0}</span></div>{(document.recompute?.errors || []).slice(0, 2).map((error) => <div className="report-line" key={`${error.objectId}-${error.code}`}><AlertTriangle size={14} className="icon-amber" /><span>{error.message}</span><button onClick={() => showNotice(`Dependency review: ${error.objectId}`)}>Review dependency</button><span className="report-time">{error.code}</span></div>)}</>}{bottomTab === 'jobs' && <div className="report-line"><CheckCircle2 size={14} className="icon-green" /><span>Document recompute job</span><span className="report-context">{document.recompute?.status || 'idle'}</span><span className="report-time">generation {document.recompute?.generation || 0}</span></div>}{bottomTab === 'diagnostics' && <div className="report-line"><Code2 size={14} className="icon-cyan" /><span>Diagnostics stream ready</span><button onClick={() => onNavigate('diagnostics')}>Open diagnostics</button></div>}</div>}
      </div>
      <div className="freecad-statusbar" role="status"><span className="status-ready"><span className="status-pulse" />Ready</span><span className="status-message">{selectedObject ? `Selected: ${selectedObject}` : 'Select an object or start a command'}</span><span className="status-spacer" /><span>{document.units}</span><span>CAD navigation</span><span>GPU: WebGL2</span></div>
    </main>
  )
}

function WorkbenchCommandBar({ workbench, facade, showNotice, setLeftTab }: { workbench: Workbench; facade: BitBybitWebCadFacade; showNotice: (message: string) => void; setLeftTab: (tab: 'model' | 'tasks') => void }) {
  const groups = workbenchDefinitions[workbench].groups
  const camImmediateCommands = new Set(['CAM_SimTools', 'CAM_Simulator', 'CAM_SimulatorGL', 'CAM_Camotics', 'CAM_Sanity', 'CAM_Inspect', 'CAM_SelectLoop', 'CAM_Stop', 'CAM_OpActiveToggle', 'CAM_OperationCopy', 'CAM_Copy', 'CAM_SimpleCopy', 'CAM_SetStartPoint'])
  return <div className="workbench-commandbar" role="toolbar" aria-label={`${workbench} workbench toolbar`}>{groups.map((group) => <div className="command-tool-group" key={group.label}><div className="command-tool-buttons">{group.commands.map((item) => { const IconComponent = iconMap[item.icon] || MoreHorizontal; const state = facade.gui.command.getState(item.id); const title = state.status === 'disabled' && state.reason ? `${item.label}: ${state.reason}` : `${item.label}${item.shortcut ? ` (${item.shortcut})` : ''}`; return <button key={item.id} title={title} aria-label={title} disabled={state.status === 'disabled'} onClick={() => { facade.gui.command.execute({ commandId: item.id }); if (!camImmediateCommands.has(item.id)) { setLeftTab('tasks'); if (!item.id.startsWith('CAM_')) showNotice(`${item.label} command staged`) } }}><IconComponent size={17} /></button> })}</div><span className="command-tool-label">{group.label}</span></div>)}</div>
}

function SelectionViewPanel({ selectedObject, document, facade, setSelectedObject, showNotice }: { selectedObject: string; document: DocumentSnapshot; facade: BitBybitWebCadFacade; setSelectedObject: (id: string) => void; showNotice: (message: string) => void }) {
  const selected = selectedObject ? facade.app.document.getObject(selectedObject) : null
  const treeItem = document.tree.find((item) => item.id === selectedObject)
  const topologyEntries = selected?.topology?.entries ?? []
  return <aside className="selection-panel right-panel" aria-label="Selection View">
    <div className="selection-panel-title"><div><span className="eyebrow">Selection View</span><h2>{treeItem?.label || selected?.id || 'No selection'}</h2></div><IconButton icon={MoreHorizontal} label="Selection actions" /></div>
    {!selected ? <div className="selection-empty"><CircleDot size={22} /><span>Nothing selected</span><small>Select an object or sub-element in the 3D view.</small></div> : <div className="selection-content">
      <div className="selection-summary"><span className="selection-object-icon"><Layers3 size={17} /></span><div><strong>{treeItem?.label || selected.id}</strong><span>{selected.typeId}</span></div></div>
      <div className="selection-table"><div><span>Object</span><strong>{selected.id}</strong></div><div><span>State</span><strong>{treeItem?.state || 'valid'}</strong></div><div><span>Sub-elements</span><strong>{topologyEntries.length}</strong></div><div><span>Document</span><strong>{document.label}</strong></div></div>
      <div className="selection-section-title">Preselection and sub-elements</div>
      <div className="selection-elements">{topologyEntries.slice(0, 8).map((entry) => <button key={entry.ref.persistentId} onClick={() => showNotice(`${entry.ref.kind}: ${entry.ref.persistentId}`)}><span>{entry.ref.kind}</span><small>{entry.ref.status}</small></button>)}{topologyEntries.length === 0 && <span className="selection-no-elements">No sub-element selection</span>}</div>
      <button className="button button-outline selection-clear" onClick={() => setSelectedObject('')}><X size={14} />Clear selection</button>
    </div>}
  </aside>
}

function ModelTree({ document, facade, selectedObjects, setSelectedObject, showNotice }: { document: DocumentSnapshot; facade: BitBybitWebCadFacade; selectedObjects: string[]; setSelectedObject: (id: string) => void; showNotice: (message: string) => void }) {
  const [expanded, setExpanded] = useState<Record<string, boolean>>({ origin: false, body: true, reference: false })
  const [contextMenu, setContextMenu] = useState<{ objectId: string; x: number; y: number } | null>(null)
  const [deleteObjectId, setDeleteObjectId] = useState<string | null>(null)
  const itemsById = new Map(document.tree.map((item) => [item.id, item]))
  const childIds = new Set(document.tree.flatMap((item) => item.children || []))
  useEffect(() => {
    const close = () => setContextMenu(null)
    window.addEventListener('pointerdown', close)
    window.addEventListener('blur', close)
    return () => { window.removeEventListener('pointerdown', close); window.removeEventListener('blur', close) }
  }, [])
  const openContextMenu = (event: ReactMouseEvent, objectId: string) => { event.preventDefault(); event.stopPropagation(); setSelectedObject(objectId); setContextMenu({ objectId, x: event.clientX, y: event.clientY }) }
  const selectTreeObject = (event: ReactMouseEvent, objectId: string) => {
    if (event.shiftKey || event.ctrlKey || event.metaKey) {
      const next = selectedObjects.includes(objectId) ? selectedObjects.filter((candidate) => candidate !== objectId) : [...selectedObjects, objectId]
      facade.selection.selectObjects(next)
    } else setSelectedObject(objectId)
  }
  const confirmDelete = () => {
    if (!deleteObjectId) return
    try { const removed = facade.app.document.removeObject({ objectId: deleteObjectId, cascade: true }); showNotice(`Removed ${removed.length} object${removed.length === 1 ? '' : 's'}`) } catch (error) { showNotice(error instanceof Error ? error.message : String(error)) }
    setDeleteObjectId(null)
  }
  const toggleVisibility = (objectId: string) => {
    const object = facade.app.document.getObject(objectId)
    const visibility = object?.properties.find((property) => property.name === 'Visibility')
    if (!visibility || typeof visibility.value !== 'boolean') { showNotice('This object has no visibility property'); return }
    try {
      facade.app.document.setProperty({ objectId, propertyName: 'Visibility', value: !visibility.value })
      showNotice(`${objectId} ${visibility.value ? 'hidden' : 'shown'}`)
    } catch (error) { showNotice(error instanceof Error ? error.message : String(error)) }
  }
  return <div className="model-tree" onKeyDown={(event) => { if (event.key === ' ' && selectedObjects.length > 0) { event.preventDefault(); selectedObjects.forEach(toggleVisibility) } }}>
    <div className="tree-toolbar"><div className="tree-search"><Search size={14} /><input placeholder="Filter objects" /></div><IconButton icon={MoreHorizontal} label="Model tree actions" /></div>
    <div className="tree-root"><div className="tree-document-row"><ChevronDown size={14} /><FileBox size={15} className="icon-cyan" /><span>{document.label}</span><span className="tree-version">v{document.version}</span></div>
      {document.tree.filter((item) => !childIds.has(item.id)).map((item) => <TreeItem key={item.id} item={item} level={1} expanded={expanded[item.id]} onToggle={() => setExpanded((current) => ({ ...current, [item.id]: !current[item.id] }))} itemsById={itemsById} selectedObjects={selectedObjects} onSelect={selectTreeObject} showNotice={showNotice} onContextMenu={openContextMenu} />)}
    </div>
    <div className="tree-footer"><span><span className="legend-dot valid" />Valid</span><span><span className="legend-dot warning" />Needs review</span></div>
    {contextMenu && <div className="tree-context-menu" role="menu" style={{ left: contextMenu.x, top: contextMenu.y }} onPointerDown={(event) => event.stopPropagation()}><button role="menuitem" onClick={() => { toggleVisibility(contextMenu.objectId); setContextMenu(null) }}><CheckSquare size={14} />Toggle visibility <kbd>Space</kbd></button><button role="menuitem" onClick={() => { showNotice('Rename editor opened'); setContextMenu(null) }}><Pencil size={14} />Rename <kbd>F2</kbd></button><button role="menuitem" onClick={() => { void facade.app.document.recomputeAsync(); setContextMenu(null) }}><RefreshCw size={14} />Recompute object</button><span className="context-separator-line" /><button role="menuitem" className="is-danger" onClick={() => { setDeleteObjectId(contextMenu.objectId); setContextMenu(null) }}><Trash2 size={14} />Delete</button></div>}
    {deleteObjectId && <FreeCadDialog title="Delete object" subtitle="Review dependent objects before changing the feature history." icon={Trash2} onClose={() => setDeleteObjectId(null)}><div className="dialog-warning"><AlertTriangle size={18} /><span>Deleting {itemsById.get(deleteObjectId)?.label || deleteObjectId} also removes dependent features. This action remains available to Edit &gt; Undo.</span></div><div className="dialog-footer"><button className="button button-quiet" onClick={() => setDeleteObjectId(null)}>Cancel</button><button className="button button-primary button-danger" onClick={confirmDelete}><Trash2 size={14} />Delete</button></div></FreeCadDialog>}
  </div>
}

function TreeItem({ item, level, expanded, onToggle, itemsById, selectedObjects, onSelect, showNotice, onContextMenu }: { item: ModelTreeItem; level: number; expanded?: boolean; onToggle: () => void; itemsById: Map<string, ModelTreeItem>; selectedObjects: string[]; onSelect: (event: ReactMouseEvent, objectId: string) => void; showNotice: (message: string) => void; onContextMenu: (event: ReactMouseEvent, objectId: string) => void }) {
  const hasChildren = item.children && item.children.length > 0
  const itemIcon = item.type === 'body' ? Box : item.type === 'sketch' ? CircleDot : item.type === 'feature' ? SquareStack : Layers3
  const ItemIcon = itemIcon
  return <>
    <button className={`tree-row ${selectedObjects.includes(item.id) ? 'is-selected' : ''} ${item.state === 'active' ? 'is-active' : ''}`} style={{ paddingLeft: `${level * 14}px` }} onClick={(event) => { onSelect(event, item.id); if (item.state === 'warning') showNotice('This feature has a reference warning') }} onContextMenu={(event) => onContextMenu(event, item.id)} onDoubleClick={() => showNotice(`Editing ${item.label}`)}>
      {hasChildren ? <span onClick={(event) => { event.stopPropagation(); onToggle() }}>{expanded ? <ChevronDown size={13} /> : <ChevronRight size={13} />}</span> : <span className="tree-spacer" />}
      <ItemIcon size={15} className={item.type === 'body' ? 'icon-cyan' : item.state === 'warning' ? 'icon-amber' : 'icon-muted'} />
      <span className="tree-label">{item.label}</span>
      {item.state === 'warning' && <AlertTriangle size={13} className="icon-amber" />}
      {item.state === 'active' && <span className="active-marker" />}
    </button>
    {expanded && hasChildren && <div className="tree-children">{item.children?.map((childId) => { const child = itemsById.get(childId); const ChildIcon = child?.type === 'sketch' ? CircleDot : child?.type === 'feature' ? SquareStack : Circle; return child ? <button className={`tree-row tree-child ${selectedObjects.includes(child.id) ? 'is-selected' : ''}`} key={child.id} onClick={(event) => { onSelect(event, child.id); if (child.state === 'warning') showNotice('This feature has a reference warning') }} onContextMenu={(event) => onContextMenu(event, child.id)} onDoubleClick={() => showNotice(`Editing ${child.label}`)}><span className="tree-spacer" /><ChildIcon size={child.type === 'feature' ? 15 : 13} className={child.state === 'warning' ? 'icon-amber' : 'icon-muted'} /><span className="tree-label">{child.label}</span>{child.state === 'warning' && <AlertTriangle size={13} className="icon-amber" />}</button> : <div className="tree-row tree-child" key={childId}><span className="tree-spacer" /><Circle size={8} className="icon-muted" /><span className="tree-label">{childId}</span></div> })}</div>}
  </>
}

const defaultMultiTransform: MultiTransformValue = { steps: [{ id: 'linear-1', type: 'linear', occurrences: 2, length: 20, direction: 'Horizontal' }] }

function multiTransformValue(value: unknown): MultiTransformValue {
  if (!value || typeof value !== 'object' || Array.isArray(value) || !('steps' in value) || !Array.isArray(value.steps) || value.steps.length === 0) return { steps: defaultMultiTransform.steps.map((step) => ({ ...step })) }
  return { steps: value.steps.map((step) => ({ ...(step as MultiTransformStep) })) }
}

function MultiTransformEditor({ value, onChange }: { value: MultiTransformValue; onChange: (value: MultiTransformValue) => void }) {
  const updateStep = (index: number, update: Partial<MultiTransformStep>) => onChange({ steps: value.steps.map((step, stepIndex) => stepIndex === index ? { ...step, ...update } as MultiTransformStep : { ...step }) })
  const replaceStep = (index: number, type: MultiTransformStep['type']) => {
    const used = new Set(value.steps.map((step) => step.id))
    let sequence = index + 1
    while (used.has(`${type}-${sequence}`) && value.steps[index].id !== `${type}-${sequence}`) sequence += 1
    const id = `${type}-${sequence}`
    const step: MultiTransformStep = type === 'linear'
      ? { id, type, occurrences: 2, length: 20, direction: 'Horizontal' }
      : type === 'polar'
        ? { id, type, occurrences: 3, angle: 360, axis: 'Normal' }
        : { id, type, plane: 'YZ plane' }
    onChange({ steps: value.steps.map((candidate, stepIndex) => stepIndex === index ? step : { ...candidate }) })
  }
  const addStep = () => {
    if (value.steps.length >= 6) return
    const used = new Set(value.steps.map((step) => step.id))
    let sequence = value.steps.length + 1
    while (used.has(`linear-${sequence}`)) sequence += 1
    onChange({ steps: [...value.steps.map((step) => ({ ...step })), { id: `linear-${sequence}`, type: 'linear', occurrences: 2, length: 20, direction: 'Horizontal' }] })
  }
  return <div className="multi-transform-editor">
    {value.steps.map((step, index) => <div className="multi-transform-step" key={step.id}>
      <div className="multi-transform-step-head"><span>Step {index + 1}</span><select aria-label={`Transformation step ${index + 1} type`} value={step.type} onChange={(event) => replaceStep(index, event.target.value as MultiTransformStep['type'])}><option value="linear">Linear</option><option value="polar">Polar</option><option value="mirrored">Mirrored</option></select><button type="button" className="multi-transform-remove" title="Remove transformation step" aria-label={`Remove transformation step ${index + 1}`} disabled={value.steps.length === 1} onClick={() => onChange({ steps: value.steps.filter((_, stepIndex) => stepIndex !== index).map((candidate) => ({ ...candidate })) })}><X size={14} /></button></div>
      {step.type === 'linear' && <div className="multi-transform-fields"><label>Count<input type="number" min={2} max={10} step={1} value={step.occurrences} onChange={(event) => updateStep(index, { occurrences: Number(event.target.value) })} /></label><label>Length<input type="number" min={0.001} step={0.1} value={step.length} onChange={(event) => updateStep(index, { length: Number(event.target.value) })} /></label><label>Direction<select value={step.direction} onChange={(event) => updateStep(index, { direction: event.target.value as 'Horizontal' | 'Vertical' | 'Normal' })}><option>Horizontal</option><option>Vertical</option><option>Normal</option></select></label></div>}
      {step.type === 'polar' && <div className="multi-transform-fields"><label>Count<input type="number" min={2} max={10} step={1} value={step.occurrences} onChange={(event) => updateStep(index, { occurrences: Number(event.target.value) })} /></label><label>Angle<input type="number" min={0.001} max={360} step={1} value={step.angle} onChange={(event) => updateStep(index, { angle: Number(event.target.value) })} /></label><label>Axis<select value={step.axis} onChange={(event) => updateStep(index, { axis: event.target.value as 'Horizontal' | 'Vertical' | 'Normal' })}><option>Normal</option><option>Horizontal</option><option>Vertical</option></select></label></div>}
      {step.type === 'mirrored' && <div className="multi-transform-fields multi-transform-fields-single"><label>Plane<select value={step.plane} onChange={(event) => updateStep(index, { plane: event.target.value as 'XY plane' | 'XZ plane' | 'YZ plane' })}><option>XY plane</option><option>XZ plane</option><option>YZ plane</option></select></label></div>}
    </div>)}
    <button type="button" className="multi-transform-add" disabled={value.steps.length >= 6} onClick={addStep}><Plus size={14} />Add step</button>
  </div>
}

const useCamSnapshot = (facade: BitBybitWebCadFacade) => {
  const [snapshot, setSnapshot] = useState<CamSnapshot>(() => facade.cam.snapshot())
  useEffect(() => { setSnapshot(facade.cam.snapshot()); return facade.cam.subscribe(setSnapshot) }, [facade])
  return snapshot
}

function CamJobPanel({ facade, showNotice }: { facade: BitBybitWebCadFacade; showNotice: (message: string) => void }) {
  const snapshot = useCamSnapshot(facade)
  const [selected, setSelected] = useState('job')
  const sanity = facade.cam.sanityCheck()
  const stockSize = snapshot.stock.max.map((value, index) => value - snapshot.stock.min[index])
  const selectedTool = snapshot.tools.find((tool) => `tool:${tool.id}` === selected)
  const selectedController = snapshot.toolControllers.find((controller) => `controller:${controller.id}` === selected)
  const selectedOperation = snapshot.operations.find((operation) => `operation:${operation.id}` === selected)
  const properties: Array<[string, string]> = selected === 'job'
    ? [['Type', 'Path::Job'], ['Label', snapshot.label], ['Version', String(snapshot.version)], ['Sanity', sanity.status]]
    : selected === 'stock'
      ? [['Type', 'Path::FeaturePython'], ['Mode', snapshot.stock.mode || 'create-box'], ['Length', `${stockSize[0]} mm`], ['Width', `${stockSize[1]} mm`], ['Height', `${stockSize[2]} mm`]]
      : selected === 'setup'
        ? [['Type', 'Path::FeaturePython'], ['Safe Height Offset', `${snapshot.setupSheet.safeHeightOffset} mm`], ['Clearance Height Offset', `${snapshot.setupSheet.clearanceHeightOffset} mm`], ['Coolant Mode', snapshot.setupSheet.coolantMode]]
        : selectedTool
          ? [['Type', 'Path::ToolBit'], ['Shape', selectedTool.shape || 'endmill'], ['Diameter', `${selectedTool.diameter} mm`], ['Length', `${selectedTool.length} mm`]]
          : selectedController
            ? [['Type', 'Path::ToolController'], ['Tool', selectedController.toolId], ['Tool Number', String(selectedController.toolNumber)], ['Spindle', `${selectedController.spindleSpeed} rpm`], ['Horizontal Feed', `${selectedController.horizontalFeed} mm/min`], ['Vertical Feed', `${selectedController.verticalFeed} mm/min`]]
            : selectedOperation
              ? [['Type', 'Path::FeaturePython'], ['Operation', selectedOperation.kind], ['Tool Controller', selectedOperation.controllerId], ['Status', selectedOperation.status], ['Path Points', String(selectedOperation.path.length)], ['Final Depth', `${selectedOperation.depth} mm`]]
              : []
  const run = (commandId: string) => { facade.gui.command.execute({ commandId }); showNotice(`${commandId} opened`) }
  const row = (id: string, icon: ReactNode, label: string, detail?: string, state?: string) => <button key={id} role="treeitem" aria-selected={selected === id} className={`cam-tree-row ${selected === id ? 'is-selected' : ''}`} onClick={() => setSelected(id)}>{icon}<span>{label}</span>{detail && <small>{detail}</small>}{state && <span className={`cam-state ${state}`}>{state}</span>}</button>
  return <div className="cam-job-panel" data-testid="cam-job-tree">
    <div className="cam-tree" role="tree" aria-label="CAM Job tree">
      {row('job', <Settings2 size={14} />, snapshot.label, 'Path::Job', sanity.status)}
      <div className="cam-tree-children">
        {row('model', <Box size={13} />, 'Model', 'Base geometry')}
        {row('stock', <Archive size={13} />, 'Stock', `${stockSize.map((value) => value.toFixed(1)).join(' x ')} mm`)}
        {row('setup', <SlidersHorizontal size={13} />, 'SetupSheet', `${snapshot.setupSheet.safeHeightOffset}/${snapshot.setupSheet.clearanceHeightOffset} mm`)}
        <div className="cam-tree-group"><span><Settings2 size={12} />Tools</span><small>{snapshot.toolControllers.length}</small></div>
        {snapshot.toolControllers.map((controller) => row(`controller:${controller.id}`, <Settings2 size={13} />, controller.label, `T${controller.toolNumber} - ${controller.toolId}`))}
        {snapshot.tools.map((tool) => row(`tool:${tool.id}`, <CircleDot size={13} />, tool.name, `${tool.diameter} mm ${tool.shape || 'endmill'}`))}
        <div className="cam-tree-group"><span><Rotate3D size={12} />Operations</span><small>{snapshot.operations.length}</small></div>
        {snapshot.operations.map((operation) => row(`operation:${operation.id}`, operation.active ? <CheckCircle2 size={13} /> : <Pause size={13} />, operation.label, `${operation.kind} - ${operation.path.length} pts`, operation.status))}
      </div>
      {snapshot.operations.length === 0 && <div className="cam-tree-empty"><span>No toolpaths</span><button onClick={() => run('CAM_Profile')}>Create Profile</button></div>}
    </div>
    <div className="cam-property-view">
      <div className="cam-property-title"><span>Property</span><button title="Edit CAM Job" onClick={() => run(selected === 'stock' || selected === 'setup' ? 'CAM_Job' : selectedController || selectedTool ? 'CAM_ToolController' : selectedOperation ? 'CAM_DressupTools' : 'CAM_Job')}><Pencil size={13} /></button></div>
      {properties.map(([name, value]) => <div className="cam-property-row" key={name}><span>{name}</span><strong>{value}</strong></div>)}
    </div>
  </div>
}

const camOperationForCommand: Partial<Record<string, CamOperationKind>> = {
  CAM_Profile: 'profile',
  CAM_Pocket_Shape: 'pocket',
  CAM_MillFace: 'mill-face',
  CAM_Helix: 'helix',
  CAM_Adaptive: 'adaptive',
  CAM_Slot: 'slot',
  CAM_Area: 'area',
  CAM_Area_Workplane: 'area-workplane',
  CAM_Drilling: 'drilling',
  CAM_Tapping: 'tapping',
  CAM_ThreadMilling: 'thread-milling',
  CAM_Engrave: 'engrave',
  CAM_Deburr: 'deburr',
  CAM_Vcarve: 'v-carve',
  CAM_Probe: 'probe',
  CAM_Shape: 'shape',
  CAM_Custom: 'custom',
  CAM_PathShapeTC: 'path-shape-tool-controller',
  CAM_Pocket3D: 'pocket-3d',
}

const camDressupForCommand: Partial<Record<string, CamDressupKind>> = {
  CAM_Array: 'array', CAM_DressupArray: 'array', CAM_DressupAxisMap: 'axis-map', CAM_DressupDogbone: 'dogbone', CAM_DressupDragKnife: 'drag-knife', CAM_DressupLeadInOut: 'lead-in-out', CAM_DressupPathBoundary: 'boundary', CAM_DressupRampEntry: 'ramp-entry', CAM_DressupTag: 'holding-tags', CAM_DressupZCorrect: 'z-correct',
}

function CamTaskPanel({ facade, showNotice }: { facade: BitBybitWebCadFacade; showNotice: (message: string) => void }) {
  const snapshot = useCamSnapshot(facade)
  const task = facade.task.getActive()
  const activeTask = task?.status === 'preview' && task.commandId.startsWith('CAM_') ? task : null
  const commandId = activeTask?.commandId || ''
  const draft = activeTask?.draft || {}
  const numberValue = (key: string, fallback: number) => typeof draft[key] === 'number' && Number.isFinite(draft[key]) ? Number(draft[key]) : fallback
  const stringValue = (key: string, fallback: string) => typeof draft[key] === 'string' ? String(draft[key]) : fallback
  const stockSize = snapshot.stock.max.map((value, index) => value - snapshot.stock.min[index])
  const isToolTask = ['CAM_ToolController', 'CAM_ToolBitLibraryOpen', 'CAM_ToolBitCreate', 'CAM_ToolBitDock'].includes(commandId)
  const isToolLoadTask = commandId === 'CAM_ToolBitLoad'
  const isToolSaveTask = commandId === 'CAM_ToolBitSave' || commandId === 'CAM_ToolBitSaveAs'
  const isOperationTask = Boolean(camOperationForCommand[commandId]) || ['CAM_DrillingTools', 'CAM_EngraveTools', 'CAM_3dTools'].includes(commandId)
  const isDressupTask = commandId === 'CAM_DressupTools' || Boolean(camDressupForCommand[commandId])
  const isFixtureTask = commandId === 'CAM_Fixture'
  const operationKind = (camOperationForCommand[commandId] || stringValue('operationKind', commandId === 'CAM_DrillingTools' ? 'drilling' : commandId === 'CAM_EngraveTools' ? 'engrave' : commandId === 'CAM_3dTools' ? 'pocket-3d' : 'profile')) as CamOperationKind
  const dressupKind = (camDressupForCommand[commandId] || stringValue('dressupKind', 'lead-in-out')) as CamDressupKind
  const axisConfiguration = stringValue('axisConfiguration', '3-axis') as CamAxisConfiguration
  const isMetadataTask = ['CAM_Compound', 'CAM_PropertyBag', 'CAM_Comment'].includes(commandId)
  const setDraft = (update: Record<string, unknown>) => facade.task.update(update)
  const finish = (message: string) => { facade.task.apply(); showNotice(message) }
  const ensureDefaultTool = () => {
    const current = facade.cam.snapshot()
    if (current.tools.length > 0) return current.tools[0]
    return facade.cam.addTool({ id: 'T1', name: '6 mm End Mill', shape: 'endmill', diameter: 6, length: 50, cuttingEdgeHeight: 20, shankDiameter: 6 })
  }
  const downloadText = (text: string, filename: string, type = 'application/json') => {
    const url = URL.createObjectURL(new Blob([text], { type }))
    const link = document.createElement('a')
    link.href = url
    link.download = filename
    link.click()
    URL.revokeObjectURL(url)
    return new TextEncoder().encode(text).byteLength
  }
  const accept = async () => {
    if (!activeTask) { showNotice('No active CAM task'); return }
    try {
      if (commandId === 'CAM_Job') {
        const length = numberValue('length', stockSize[0]); const width = numberValue('width', stockSize[1]); const height = numberValue('height', stockSize[2])
        facade.cam.updateStock({ mode: stringValue('stockMode', snapshot.stock.mode || 'from-base-bound-box') as CamStockMode, min: [0, 0, 0], max: [length, width, height] })
        facade.cam.updateSetupSheet({ safeHeightOffset: numberValue('safeHeightOffset', snapshot.setupSheet.safeHeightOffset), clearanceHeightOffset: numberValue('clearanceHeightOffset', snapshot.setupSheet.clearanceHeightOffset), coolantMode: stringValue('coolantMode', snapshot.setupSheet.coolantMode) as CamCoolantMode })
        finish('CAM Job and SetupSheet updated')
      } else if (isToolTask) {
        const sequence = snapshot.tools.length + 1
        const toolId = stringValue('toolId', `T${sequence}`)
        facade.cam.addTool({ id: toolId, name: stringValue('toolName', `${numberValue('diameter', 6)} mm End Mill`), shape: stringValue('toolShape', 'endmill') as 'endmill', diameter: numberValue('diameter', 6), length: numberValue('toolLength', 50), cuttingEdgeHeight: numberValue('cuttingEdgeHeight', 20), shankDiameter: numberValue('shankDiameter', numberValue('diameter', 6)), holderDiameter: numberValue('holderDiameter', 20), holderLength: numberValue('holderLength', 70) })
        facade.cam.addToolController({ id: `TC-${toolId}`, label: stringValue('controllerLabel', `Tool Controller ${toolId}`), toolId, toolNumber: numberValue('toolNumber', sequence), spindleSpeed: numberValue('spindleSpeed', 12000), spindleDirection: stringValue('spindleDirection', 'forward') as 'forward', horizontalFeed: numberValue('horizontalFeed', 300), verticalFeed: numberValue('verticalFeed', 100), horizontalRapid: numberValue('horizontalRapid', 0), verticalRapid: numberValue('verticalRapid', 0) })
        finish(`ToolBit and Tool Controller ${toolId} created`)
      } else if (isToolLoadTask) {
        const loaded = facade.cam.importToolBit(stringValue('toolJson', ''), stringValue('toolIdOverride', '').trim() || undefined)
        finish(`ToolBit ${loaded.id} loaded`)
      } else if (isToolSaveTask) {
        const toolId = stringValue('selectedToolId', snapshot.tools[0]?.id || '')
        if (!toolId) throw new RangeError('Create or load a ToolBit before saving it.')
        const bytes = downloadText(facade.cam.exportToolBit(toolId), `${toolId}.fctb`)
        finish(`ToolBit ${toolId} saved (${bytes} bytes)`)
      } else if (isOperationTask) {
        const tool = ensureDefaultTool()
        const current = facade.cam.snapshot()
        let sequence = current.operations.length + 1
        let operationId = `${operationKind}-${sequence}`
        while (current.operations.some((operation) => operation.id === operationId)) { sequence += 1; operationId = `${operationKind}-${sequence}` }
        const matchingController = current.toolControllers.find((controller) => controller.toolId === tool.id)
        facade.cam.addOperation({ id: operationId, label: `${operationKind.replace(/-/g, ' ')} ${sequence}`, kind: operationKind, toolId: tool.id, controllerId: matchingController?.id, depth: numberValue('depth', Math.min(2, stockSize[2])), feed: numberValue('horizontalFeed', matchingController?.horizontalFeed || 300), verticalFeed: numberValue('verticalFeed', matchingController?.verticalFeed || 100), stepDown: numberValue('stepDown', Math.min(tool.diameter, 2)), stepOver: numberValue('stepOver', 50), direction: stringValue('direction', 'climb') as CamCutDirection, coolantMode: stringValue('coolantMode', snapshot.setupSheet.coolantMode) as CamCoolantMode })
        const operation = facade.cam.generatePath(operationId)
        finish(`${operation.label} generated with ${operation.path.length} path points`)
      } else if (isDressupTask) {
        const target = snapshot.operations.at(-1)
        if (!target) throw new RangeError('Create a CAM operation before adding a dress-up.')
        const kind = dressupKind
        facade.cam.applyDressup(target.id, { kind, parameters: kind === 'array' ? { count: numberValue('count', 2), offsetX: numberValue('offsetX', 0), offsetY: numberValue('offsetY', 0) } : kind === 'lead-in-out' ? { length: numberValue('leadLength', 1) } : kind === 'dogbone' ? { radius: numberValue('dogboneRadius', 0.5) } : kind === 'drag-knife' ? { offset: numberValue('knifeOffset', 0.5) } : kind === 'holding-tags' ? { height: numberValue('tagHeight', 0.5) } : kind === 'z-correct' ? { offset: numberValue('zOffset', 0) } : {} })
        const operation = facade.cam.generatePath(target.id)
        finish(`${kind} dress-up applied to ${operation.label}`)
      } else if (isFixtureTask) {
        const fixtureId = stringValue('fixtureId', `Fixture-${snapshot.collisionFixtures.length + 1}`).trim()
        const fixture = { id: fixtureId, kind: 'fixture' as const, min: [numberValue('fixtureMinX', 0), numberValue('fixtureMinY', 0), numberValue('fixtureMinZ', 0)] as [number, number, number], max: [numberValue('fixtureMaxX', 1), numberValue('fixtureMaxY', 1), numberValue('fixtureMaxZ', stockSize[2])] as [number, number, number] }
        facade.cam.updateCollisionFixtures([...snapshot.collisionFixtures.filter((entry) => entry.id !== fixtureId), fixture])
        finish(`CAM fixture ${fixtureId} updated`)
      } else if (commandId === 'CAM_Post') {
        const post = stringValue('postprocessor', 'linuxcnc') as CamPostprocessor
        const target = snapshot.operations.at(-1)
        if (!target) throw new RangeError('Create a CAM operation before generating G-code.')
        const kinematics: CamKinematicOptions | undefined = axisConfiguration === '3-axis' ? undefined : { configuration: axisConfiguration, rotary: { axis: stringValue('rotaryAxis', post === 'linuxcnc' ? 'C' : 'A') as CamRotaryAxis, startDegrees: numberValue('rotaryStart', 0), endDegrees: numberValue('rotaryEnd', 90) }, ...(axisConfiguration === '5-axis' ? { tilt: { axis: stringValue('tiltAxis', 'B') as CamRotaryAxis, startDegrees: numberValue('tiltStart', 0), endDegrees: numberValue('tiltEnd', 45) } } : {}) }
        const prepared = await prepareCamPipeline(facade.cam, target.id, { postprocessor: post, kinematics })
        const bytes = downloadText(prepared.gcode, `${snapshot.id}-${axisConfiguration}-${post}.nc`, 'text/x.gcode')
        const machineUrl = import.meta.env.VITE_LINUXCNC_WASM_URL || '/linuxcnc-machine/?case=axis-vismach-5axis-table-rotary-tilting-xyzbc-trt'
        const submitted = await submitCamPipelineToLinuxcnc(prepared, createLinuxcncIframeAdapter({ url: machineUrl, name: `${snapshot.id}.ngc`, run: true }))
        if (submitted.linuxcnc?.status !== 'accepted') throw new Error(submitted.linuxcnc?.message || `LinuxCNC WASM did not accept the generated program (${submitted.linuxcnc?.status || 'unknown'}).`)
        finish(`${post} ${axisConfiguration} G-code accepted by LinuxCNC WASM (${bytes} bytes)`)
      } else if (commandId === 'CAM_ExportTemplate') {
        const bytes = downloadText(facade.cam.exportJob(), `${snapshot.id}-template.json`)
        finish(`CAM Job template exported (${bytes} bytes)`)
      } else if (commandId === 'CAM_Comment') {
        const comment = facade.cam.addComment(stringValue('comment', ''))
        finish(`CAM comment added (${comment.length} characters)`)
      } else if (commandId === 'CAM_PropertyBag') {
        const propertyType = stringValue('propertyType', 'string')
        const rawValue = stringValue('propertyValue', '')
        const value: CamPropertyValue = propertyType === 'number' ? Number(rawValue) : propertyType === 'boolean' ? rawValue === 'true' : rawValue
        facade.cam.setPropertyBagValue(stringValue('propertyName', ''), value)
        finish(`CAM property ${stringValue('propertyName', '').trim()} updated`)
      } else if (commandId === 'CAM_Compound') {
        const operationIds = stringValue('operationIds', snapshot.operations.map((operation) => operation.id).join(',')).split(',').map((value) => value.trim()).filter(Boolean)
        const compound = facade.cam.createCompound(operationIds, stringValue('compoundId', '').trim() || undefined, stringValue('compoundLabel', 'Operation Compound'))
        finish(`CAM compound ${compound.label} created from ${compound.operationIds.length} operations`)
      } else finish(`${commandId} completed`)
    } catch (error) { showNotice(error instanceof Error ? error.message : String(error)) }
  }
  if (!activeTask) return <div className="task-panel cam-task-panel" data-testid="cam-task-panel"><div className="task-header"><div className="task-icon"><Settings2 size={16} /></div><div><span className="eyebrow">CAM Job</span><h2>{snapshot.label}</h2></div><Badge tone={facade.cam.sanityCheck().status === 'pass' ? 'green' : 'amber'}>{facade.cam.sanityCheck().status}</Badge></div><div className="task-body"><div className="cam-task-summary"><strong>{snapshot.operations.length} operations</strong><span>{snapshot.tools.length} ToolBits - {snapshot.toolControllers.length} controllers</span><span>Stock {stockSize.map((value) => value.toFixed(1)).join(' x ')} mm</span></div><button className="button button-primary cam-task-command" onClick={() => facade.gui.command.execute({ commandId: 'CAM_Job' })}><Plus size={14} />Create or edit Job</button><button className="button button-outline cam-task-command" onClick={() => facade.gui.command.execute({ commandId: 'CAM_ToolController' })}><Settings2 size={14} />Add Tool Controller</button><button className="button button-outline cam-task-command" onClick={() => facade.gui.command.execute({ commandId: 'CAM_Profile' })}><Rotate3D size={14} />Create Profile</button></div></div>
  return <div className="task-panel cam-task-panel" data-testid="cam-task-panel">
    <div className="task-actions-top"><button className="button button-primary" onClick={accept}>OK</button><button className="button button-outline" onClick={() => showNotice('CAM preview parameters updated')}>Apply</button><button className="button button-quiet" onClick={() => { facade.task.cancel(); showNotice('CAM task cancelled') }}>Cancel</button></div>
    <div className="task-header"><div className="task-icon"><Settings2 size={16} /></div><div><span className="eyebrow">Active CAM command</span><h2>{workbenchDefinitions.CAM.groups.flatMap((group) => group.commands).find((command) => command.id === commandId)?.label || commandId}</h2></div><Badge tone="cyan">Preview</Badge></div>
    <div className="task-body">
      {commandId === 'CAM_Job' && <><label className="field-label">Stock<select className="field-input" value={stringValue('stockMode', snapshot.stock.mode || 'from-base-bound-box')} onChange={(event) => setDraft({ stockMode: event.target.value })}><option value="from-base-bound-box">Extend Model's Bound Box</option><option value="create-box">Create Box</option><option value="create-cylinder">Create Cylinder</option><option value="existing-solid">Use Existing Solid</option></select></label>{[['length', 'Length', stockSize[0]], ['width', 'Width', stockSize[1]], ['height', 'Height', stockSize[2]]].map(([key, label, fallback]) => <label className="field-label" key={String(key)}>{label} <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={numberValue(String(key), Number(fallback))} onChange={(event) => setDraft({ [String(key)]: Number(event.target.value) })} /></label>)}<div className="cam-task-section">SetupSheet</div><label className="field-label">Safe Height Offset <span className="field-unit">mm</span><input className="field-input" type="number" min={0} step={0.1} value={numberValue('safeHeightOffset', snapshot.setupSheet.safeHeightOffset)} onChange={(event) => setDraft({ safeHeightOffset: Number(event.target.value) })} /></label><label className="field-label">Clearance Height Offset <span className="field-unit">mm</span><input className="field-input" type="number" min={0} step={0.1} value={numberValue('clearanceHeightOffset', snapshot.setupSheet.clearanceHeightOffset)} onChange={(event) => setDraft({ clearanceHeightOffset: Number(event.target.value) })} /></label><label className="field-label">Coolant Mode<select className="field-input" value={stringValue('coolantMode', snapshot.setupSheet.coolantMode)} onChange={(event) => setDraft({ coolantMode: event.target.value })}><option value="none">None</option><option value="flood">Flood</option><option value="mist">Mist</option></select></label></>}
      {isToolTask && <>{[['toolId', 'Tool ID', `T${snapshot.tools.length + 1}`], ['toolName', 'Label', '6 mm End Mill'], ['controllerLabel', 'Controller Label', `Tool Controller T${snapshot.tools.length + 1}`]].map(([key, label, fallback]) => <label className="field-label" key={key}>{label}<input className="field-input" value={stringValue(key, fallback)} onChange={(event) => setDraft({ [key]: event.target.value })} /></label>)}<label className="field-label">Tool Shape<select className="field-input" value={stringValue('toolShape', 'endmill')} onChange={(event) => setDraft({ toolShape: event.target.value })}>{['endmill', 'ballend', 'bullnose', 'drill', 'v-bit', 'thread-mill', 'probe'].map((shape) => <option key={shape}>{shape}</option>)}</select></label>{[['diameter', 'Diameter', 6, 'mm'], ['toolLength', 'Overall Length', 50, 'mm'], ['cuttingEdgeHeight', 'Cutting Edge Height', 20, 'mm'], ['shankDiameter', 'Shank Diameter', 6, 'mm'], ['holderDiameter', 'Holder Diameter', 20, 'mm'], ['holderLength', 'Holder Length', 70, 'mm'], ['toolNumber', 'Tool Number', snapshot.tools.length + 1, ''], ['spindleSpeed', 'Spindle Speed', 12000, 'rpm'], ['horizontalFeed', 'Horizontal Feed', 300, 'mm/min'], ['verticalFeed', 'Vertical Feed', 100, 'mm/min']].map(([key, label, fallback, unit]) => <label className="field-label" key={String(key)}>{label} <span className="field-unit">{unit}</span><input className="field-input" type="number" min={Number(key) === 0 ? 0 : 0.001} step={1} value={numberValue(String(key), Number(fallback))} onChange={(event) => setDraft({ [String(key)]: Number(event.target.value) })} /></label>)}<label className="field-label">Spindle Direction<select className="field-input" value={stringValue('spindleDirection', 'forward')} onChange={(event) => setDraft({ spindleDirection: event.target.value })}><option value="forward">Forward</option><option value="reverse">Reverse</option><option value="none">None</option></select></label></>}
      {isToolLoadTask && <><label className="field-label">ID Override<input className="field-input" value={stringValue('toolIdOverride', '')} onChange={(event) => setDraft({ toolIdOverride: event.target.value })} /></label><label className="field-label cam-tool-json-label">ToolBit JSON<textarea className="field-input cam-tool-json" value={stringValue('toolJson', '{\n  "schemaVersion": 1,\n  "tool": { "id": "T1", "name": "6 mm End Mill", "diameter": 6, "length": 50, "shape": "endmill" }\n}')} onChange={(event) => setDraft({ toolJson: event.target.value })} /></label></>}
      {isToolSaveTask && <label className="field-label">ToolBit<select className="field-input" value={stringValue('selectedToolId', snapshot.tools[0]?.id || '')} onChange={(event) => setDraft({ selectedToolId: event.target.value })}>{snapshot.tools.map((tool) => <option value={tool.id} key={tool.id}>{tool.name} ({tool.id})</option>)}</select></label>}
      {isFixtureTask && <><label className="field-label">Fixture ID<input className="field-input" value={stringValue('fixtureId', `Fixture-${snapshot.collisionFixtures.length + 1}`)} onChange={(event) => setDraft({ fixtureId: event.target.value })} /></label><div className="cam-task-section">Fixture Bounds</div>{[['fixtureMinX', 'Min X', 0], ['fixtureMinY', 'Min Y', 0], ['fixtureMinZ', 'Min Z', 0], ['fixtureMaxX', 'Max X', 1], ['fixtureMaxY', 'Max Y', 1], ['fixtureMaxZ', 'Max Z', stockSize[2]]].map(([key, label, fallback]) => <label className="field-label" key={String(key)}>{label}<input className="field-input" type="number" step={0.1} value={numberValue(String(key), Number(fallback))} onChange={(event) => setDraft({ [String(key)]: Number(event.target.value) })} /></label>)}</>}
      {isOperationTask && <>{commandId === 'CAM_DrillingTools' && <label className="field-label">Operation<select className="field-input" value={operationKind} onChange={(event) => setDraft({ operationKind: event.target.value })}><option value="drilling">Drilling</option><option value="tapping">Tapping</option><option value="thread-milling">Thread Milling</option></select></label>}{commandId === 'CAM_EngraveTools' && <label className="field-label">Operation<select className="field-input" value={operationKind} onChange={(event) => setDraft({ operationKind: event.target.value })}><option value="engrave">Engrave</option><option value="deburr">Deburr</option><option value="v-carve">V-carve</option></select></label>}{commandId === 'CAM_3dTools' && <label className="field-label">Operation<select className="field-input" value={operationKind} onChange={(event) => setDraft({ operationKind: event.target.value })}><option value="pocket-3d">Pocket 3D</option><option value="surface">Surface</option><option value="waterline">Waterline</option></select></label>}{[['depth', 'Final Depth', Math.min(2, stockSize[2]), 'mm'], ['stepDown', 'Step Down', 2, 'mm'], ['stepOver', 'Step Over', 50, '%'], ['horizontalFeed', 'Horizontal Feed', 300, 'mm/min'], ['verticalFeed', 'Vertical Feed', 100, 'mm/min']].map(([key, label, fallback, unit]) => <label className="field-label" key={String(key)}>{label} <span className="field-unit">{unit}</span><input className="field-input" type="number" min={0.001} max={key === 'stepOver' ? 100 : undefined} step={0.1} value={numberValue(String(key), Number(fallback))} onChange={(event) => setDraft({ [String(key)]: Number(event.target.value) })} /></label>)}<label className="field-label">Direction<select className="field-input" value={stringValue('direction', 'climb')} onChange={(event) => setDraft({ direction: event.target.value })}><option value="climb">Climb</option><option value="conventional">Conventional</option><option value="either">Either</option></select></label><label className="field-label">Coolant Mode<select className="field-input" value={stringValue('coolantMode', snapshot.setupSheet.coolantMode)} onChange={(event) => setDraft({ coolantMode: event.target.value })}><option value="none">None</option><option value="flood">Flood</option><option value="mist">Mist</option></select></label></>}
      {isDressupTask && <><label className="field-label">Base Operation<select className="field-input" value={snapshot.operations.at(-1)?.id || ''} disabled>{snapshot.operations.map((operation) => <option value={operation.id} key={operation.id}>{operation.label}</option>)}</select></label>{commandId === 'CAM_DressupTools' && <label className="field-label">Dress-up<select className="field-input" value={dressupKind} onChange={(event) => setDraft({ dressupKind: event.target.value })}>{['array', 'axis-map', 'boundary', 'dogbone', 'drag-knife', 'lead-in-out', 'ramp-entry', 'holding-tags', 'z-correct'].map((kind) => <option key={kind}>{kind}</option>)}</select></label>}{dressupKind === 'array' && <>{[['count', 'Count', 2], ['offsetX', 'X Offset', 0], ['offsetY', 'Y Offset', 0]].map(([key, label, fallback]) => <label className="field-label" key={String(key)}>{label}<input className="field-input" type="number" min={key === 'count' ? 1 : undefined} step={key === 'count' ? 1 : 0.1} value={numberValue(String(key), Number(fallback))} onChange={(event) => setDraft({ [String(key)]: Number(event.target.value) })} /></label>)}</>}{dressupKind === 'dogbone' && <label className="field-label">Relief Radius <span className="field-unit">mm</span><input className="field-input" type="number" min={0} step={0.1} value={numberValue('dogboneRadius', 0.5)} onChange={(event) => setDraft({ dogboneRadius: Number(event.target.value) })} /></label>}{dressupKind === 'drag-knife' && <label className="field-label">Knife Offset <span className="field-unit">mm</span><input className="field-input" type="number" min={0} step={0.1} value={numberValue('knifeOffset', 0.5)} onChange={(event) => setDraft({ knifeOffset: Number(event.target.value) })} /></label>}{dressupKind === 'lead-in-out' && <label className="field-label">Lead Length <span className="field-unit">mm</span><input className="field-input" type="number" min={0} step={0.1} value={numberValue('leadLength', 1)} onChange={(event) => setDraft({ leadLength: Number(event.target.value) })} /></label>}{dressupKind === 'holding-tags' && <label className="field-label">Tag Height <span className="field-unit">mm</span><input className="field-input" type="number" min={0} step={0.1} value={numberValue('tagHeight', 0.5)} onChange={(event) => setDraft({ tagHeight: Number(event.target.value) })} /></label>}{dressupKind === 'z-correct' && <label className="field-label">Z Offset <span className="field-unit">mm</span><input className="field-input" type="number" step={0.1} value={numberValue('zOffset', 0)} onChange={(event) => setDraft({ zOffset: Number(event.target.value) })} /></label>}</>}
      {commandId === 'CAM_Post' && <><label className="field-label">Post Processor<select className="field-input" value={stringValue('postprocessor', 'linuxcnc')} onChange={(event) => setDraft({ postprocessor: event.target.value })}>{facade.cam.postprocessors().map((post) => <option key={post}>{post}</option>)}</select></label><label className="field-label">Axis Configuration<select className="field-input" value={axisConfiguration} onChange={(event) => setDraft({ axisConfiguration: event.target.value })}><option value="3-axis">3-axis</option><option value="4-axis">4-axis</option><option value="5-axis">5-axis</option></select></label>{axisConfiguration !== '3-axis' && <><label className="field-label">Rotary Axis<select className="field-input" value={stringValue('rotaryAxis', stringValue('postprocessor', 'linuxcnc') === 'linuxcnc' ? 'C' : 'A')} onChange={(event) => setDraft({ rotaryAxis: event.target.value })}>{['A', 'B', 'C'].map((axis) => <option key={axis}>{axis}</option>)}</select></label><label className="field-label">Rotary Start <span className="field-unit">deg</span><input className="field-input" type="number" step={1} value={numberValue('rotaryStart', 0)} onChange={(event) => setDraft({ rotaryStart: Number(event.target.value) })} /></label><label className="field-label">Rotary End <span className="field-unit">deg</span><input className="field-input" type="number" step={1} value={numberValue('rotaryEnd', 90)} onChange={(event) => setDraft({ rotaryEnd: Number(event.target.value) })} /></label></>}{axisConfiguration === '5-axis' && <><label className="field-label">Tilt Axis<select className="field-input" value={stringValue('tiltAxis', 'B')} onChange={(event) => setDraft({ tiltAxis: event.target.value })}>{['A', 'B', 'C'].map((axis) => <option key={axis}>{axis}</option>)}</select></label><label className="field-label">Tilt Start <span className="field-unit">deg</span><input className="field-input" type="number" step={1} value={numberValue('tiltStart', 0)} onChange={(event) => setDraft({ tiltStart: Number(event.target.value) })} /></label><label className="field-label">Tilt End <span className="field-unit">deg</span><input className="field-input" type="number" step={1} value={numberValue('tiltEnd', 45)} onChange={(event) => setDraft({ tiltEnd: Number(event.target.value) })} /></label></>}<div className="task-note"><Info size={14} /><span>LinuxCNC WASM - XYZBC RTCP; rotary limit checks are enforced before posting.</span></div></>}
      {commandId === 'CAM_ExportTemplate' && <div className="task-note"><Info size={14} /><span>The current Job, Stock, SetupSheet, ToolBits, controllers, operations and paths will be exported as a deterministic JSON template.</span></div>}
      {commandId === 'CAM_Comment' && <label className="field-label">Comment<textarea className="field-input" rows={4} value={stringValue('comment', '')} onChange={(event) => setDraft({ comment: event.target.value })} /></label>}
      {commandId === 'CAM_PropertyBag' && <><label className="field-label">Property name<input className="field-input" value={stringValue('propertyName', '')} onChange={(event) => setDraft({ propertyName: event.target.value })} /></label><label className="field-label">Value type<select className="field-input" value={stringValue('propertyType', 'string')} onChange={(event) => setDraft({ propertyType: event.target.value })}><option value="string">String</option><option value="number">Number</option><option value="boolean">Boolean</option></select></label><label className="field-label">Value{stringValue('propertyType', 'string') === 'boolean' ? <select className="field-input" value={stringValue('propertyValue', 'false')} onChange={(event) => setDraft({ propertyValue: event.target.value })}><option value="false">False</option><option value="true">True</option></select> : <input className="field-input" type={stringValue('propertyType', 'string') === 'number' ? 'number' : 'text'} value={stringValue('propertyValue', '')} onChange={(event) => setDraft({ propertyValue: event.target.value })} />}</label></>}
      {commandId === 'CAM_Compound' && <><label className="field-label">Compound ID<input className="field-input" value={stringValue('compoundId', '')} onChange={(event) => setDraft({ compoundId: event.target.value })} /></label><label className="field-label">Label<input className="field-input" value={stringValue('compoundLabel', 'Operation Compound')} onChange={(event) => setDraft({ compoundLabel: event.target.value })} /></label><label className="field-label">Operation IDs<textarea className="field-input" rows={3} value={stringValue('operationIds', snapshot.operations.map((operation) => operation.id).join(','))} onChange={(event) => setDraft({ operationIds: event.target.value })} /></label></>}
      {isMetadataTask && <div className="task-note"><Info size={14} /><span>Job comments, typed properties and operation compounds are persisted in the CAM Job asset and participate in Undo/Redo.</span></div>}
    </div>
  </div>
}

function TaskPanel({ workbench, facade, showNotice }: { workbench: Workbench; facade: BitBybitWebCadFacade; showNotice: (message: string) => void }) {
  if (workbench === 'CAM') return <CamTaskPanel facade={facade} showNotice={showNotice} />
  const definition = workbenchDefinitions[workbench]
  const isSketch = workbench === 'Sketcher'
  const activeTask = facade.task.getActive()
  const primitiveType = activeTask?.commandId === 'primitive' && typeof activeTask.draft.primitiveType === 'string' ? activeTask.draft.primitiveType : 'Box'
  const primitiveFields = primitiveType === 'Box' ? ['width', 'length', 'height'] : primitiveType === 'Cylinder' ? ['radius', 'height', 'angle'] : primitiveType === 'Sphere' ? ['radius', 'angle1', 'angle2', 'angle3'] : primitiveType === 'Ellipsoid' ? ['radius1', 'radius2', 'radius3', 'angle1', 'angle2', 'angle3'] : primitiveType === 'Torus' ? ['radius1', 'radius2', 'angle1', 'angle2', 'angle3'] : primitiveType === 'Prism' ? ['polygon', 'circumradius', 'height', 'firstAngle', 'secondAngle'] : primitiveType === 'Wedge' ? ['xmin', 'ymin', 'zmin', 'x2min', 'z2min', 'xmax', 'ymax', 'zmax', 'x2max', 'z2max'] : ['radius1', 'radius2', 'height', 'angle']
  const primitiveUnits: Record<string, string> = { width: 'mm', length: 'mm', height: 'mm', radius: 'mm', radius1: 'mm', radius2: 'mm', radius3: 'mm', circumradius: 'mm', polygon: '', angle: 'deg', angle1: 'deg', angle2: 'deg', angle3: 'deg', firstAngle: 'deg', secondAngle: 'deg', xmin: 'mm', ymin: 'mm', zmin: 'mm', x2min: 'mm', z2min: 'mm', xmax: 'mm', ymax: 'mm', zmax: 'mm', x2max: 'mm', z2max: 'mm' }
  const primitiveFallbacks: Record<string, number> = { width: 10, length: 10, height: 10, radius: 5, radius1: 5, radius2: 0, radius3: 0, circumradius: 2, polygon: 6, angle: 360, angle1: -90, angle2: 90, angle3: 360, firstAngle: 0, secondAngle: 0, xmin: 0, ymin: 0, zmin: 0, x2min: 2, z2min: 2, xmax: 10, ymax: 10, zmax: 10, x2max: 8, z2max: 8 }
  const primitiveLabel = (field: string) => primitiveType === 'Ellipsoid' && field === 'radius1' ? 'Z radius' : primitiveType === 'Ellipsoid' && field === 'radius2' ? 'X radius' : primitiveType === 'Ellipsoid' && field === 'radius3' ? 'Y radius' : field === 'radius1' ? 'Bottom radius' : field === 'radius2' ? 'Top radius' : field === 'polygon' ? 'Polygon sides' : field === 'circumradius' ? 'Circumradius' : field === 'firstAngle' ? 'First angle' : field === 'secondAngle' ? 'Second angle' : field === 'xmin' ? 'X minimum' : field === 'ymin' ? 'Y minimum' : field === 'zmin' ? 'Z minimum' : field === 'x2min' ? 'X2 minimum' : field === 'z2min' ? 'Z2 minimum' : field === 'xmax' ? 'X maximum' : field === 'ymax' ? 'Y maximum' : field === 'zmax' ? 'Z maximum' : field === 'x2max' ? 'X2 maximum' : field === 'z2max' ? 'Z2 maximum' : field[0].toUpperCase() + field.slice(1)
  const primitiveMin = (field: string) => primitiveType === 'Wedge' ? undefined : field === 'polygon' ? 3 : field === 'radius2' ? 0 : primitiveUnits[field] === 'deg' ? -89.99999 : 0.001
  const featureFields: Record<string, { key: string; label: string; unit: string; fallback: number }> = {
    pad: { key: 'length', label: 'Length', unit: 'mm', fallback: 42 },
    pocket: { key: 'length', label: 'Length', unit: 'mm', fallback: 18 },
    revolution: { key: 'angle', label: 'Angle', unit: 'deg', fallback: 360 },
    groove: { key: 'angle', label: 'Angle', unit: 'deg', fallback: 360 },
    fillet: { key: 'radius', label: 'Radius', unit: 'mm', fallback: 3 },
    chamfer: { key: 'distance', label: 'Distance', unit: 'mm', fallback: 2 },
    'fillet-part': { key: 'radius', label: 'Radius', unit: 'mm', fallback: 1 },
    'chamfer-part': { key: 'size', label: 'Distance', unit: 'mm', fallback: 1 },
    draft: { key: 'angle', label: 'Angle', unit: 'deg', fallback: 5 },
    thickness: { key: 'value', label: 'Value', unit: 'mm', fallback: 1 },
  }
  const currentFeatureField = activeTask?.commandId ? featureFields[activeTask.commandId] : undefined
  const activeCommand = activeTask?.commandId
  const helixFields = [
    { key: 'pitch', label: 'Pitch', unit: 'mm', fallback: 1, min: 0.001 },
    { key: 'height', label: 'Height', unit: 'mm', fallback: 2, min: 0.001 },
    { key: 'radius', label: 'Radius', unit: 'mm', fallback: 1, min: 0.001 },
    { key: 'angle', label: 'Cone angle', unit: 'deg', fallback: 0, min: -89.99998, max: 89.99998 },
    { key: 'segmentLength', label: 'Segment length', unit: '', fallback: 0, min: 0 },
  ]
  const loftCommand = ['loft-part', 'additive-loft', 'subtractive-loft'].includes(activeCommand || '')
  const pipeCommand = ['sweep-part', 'additive-pipe', 'subtractive-pipe'].includes(activeCommand || '')
  const transformCommand = ['mirrored', 'multi-transform', 'linear-pattern', 'polar-pattern'].includes(activeCommand || '')
  const multiTransform = multiTransformValue(activeTask?.draft.transformations)
  const activeDocument = facade.app.document.getActive()
  const booleanCommand = activeCommand === 'union' || activeCommand === 'cut' || activeCommand === 'intersection'
  const shapeObjects = activeDocument.objects.filter((object) => object.typeId.startsWith('Part::') || ['PartDesign::Feature', 'PartDesign::Pad', 'PartDesign::Pocket', 'PartDesign::Revolution', 'PartDesign::AdditiveLoft', 'PartDesign::SubtractiveLoft', 'PartDesign::AdditivePipe', 'PartDesign::SubtractivePipe', 'PartDesign::Fillet', 'PartDesign::Chamfer', 'PartDesign::Draft', 'PartDesign::Thickness', 'PartDesign::Mirrored', 'PartDesign::MultiTransform', 'PartDesign::LinearPattern', 'PartDesign::PolarPattern', 'PartDesign::Hole'].includes(object.typeId))
  const transformFeatureObjects = shapeObjects.filter((object) => ['PartDesign::Pad', 'PartDesign::Pocket', 'PartDesign::Revolution', 'PartDesign::Groove', 'PartDesign::AdditiveLoft', 'PartDesign::SubtractiveLoft', 'PartDesign::AdditivePipe', 'PartDesign::SubtractivePipe', 'PartDesign::Hole'].includes(object.typeId))
  const transformOriginals = Array.isArray(activeTask?.draft.originals) ? activeTask.draft.originals.filter((value): value is string => typeof value === 'string') : []
  const sketchObjects = activeDocument.objects.filter((object) => object.typeId === 'Sketcher::SketchObject')
  const loftSections = Array.isArray(activeTask?.draft.sections) ? activeTask.draft.sections.filter((entry): entry is string => typeof entry === 'string') : []
  const objectLabel = (object: DocumentSnapshot['objects'][number]) => { const value = object.properties.find((property) => property.name === 'Label')?.value; return typeof value === 'string' || typeof value === 'number' ? String(value) : object.id }
  const sketchTaskLabel = (object: DocumentSnapshot['objects'][number]) => `${objectLabel(object)} (${object.id})`
  const draftLink = (name: string) => typeof activeTask?.draft[name] === 'string' ? String(activeTask.draft[name]) : ''
  const acceptTask = () => {
    if (!activeTask || activeTask.status !== 'preview') { showNotice('No active task'); return }
    facade.task.apply()
    void facade.app.document.recomputeAsync().then((result) => showNotice(result.status === 'completed' ? 'Task accepted and recompute completed' : `Task accepted; recompute ${result.status}`))
  }
  return <div className="task-panel">
    <div className="task-actions-top">
      <button className="button button-primary" onClick={acceptTask}>OK</button>
      <button className="button button-outline" onClick={() => { facade.task.update({ preview: true }); showNotice('Preview applied') }}>Apply</button>
      <button className="button button-quiet" onClick={() => { facade.task.cancel(); showNotice('Task cancelled') }}>Cancel</button>
    </div>
    <div className="task-header">
      <div className="task-icon"><Pencil size={16} /></div>
      <div><span className="eyebrow">Active command</span><h2>{isSketch ? 'Edit Sketch' : definition.taskTitle}</h2></div>
      <Badge tone="cyan">Preview</Badge>
    </div>
    <div className="task-body">
      <div className="task-step"><span className="step-index">1</span><div><strong>{isSketch ? 'Geometry and constraints' : definition.objectType}</strong><span>{definition.taskSummary}</span></div></div>
      {activeCommand === 'primitive' && <>
        <label className="field-label">Primitive type<select className="field-input" value={primitiveType} onChange={(event) => facade.task.update({ primitiveType: event.target.value })}><option>Box</option><option>Cylinder</option><option>Sphere</option><option>Ellipsoid</option><option>Cone</option><option>Torus</option><option>Prism</option><option>Wedge</option></select></label>
        {primitiveFields.map((field) => <label className="field-label" key={field}>{primitiveLabel(field)} <span className="field-unit">{primitiveUnits[field]}</span><input className="field-input" type="number" min={primitiveMin(field)} max={field === 'polygon' ? 10000 : primitiveUnits[field] === 'deg' && (field === 'firstAngle' || field === 'secondAngle') ? 89.99999 : primitiveUnits[field] === 'deg' ? 360 : undefined} step={field === 'polygon' ? 1 : primitiveUnits[field] === 'deg' ? 1 : 0.1} value={typeof activeTask?.draft[field] === 'number' ? Number(activeTask.draft[field]) : primitiveFallbacks[field]} onChange={(event) => facade.task.update({ [field]: Number(event.target.value) })} /></label>)}
      </>}
      {activeCommand === 'helix' && <>
        {helixFields.map((field) => <label className="field-label" key={field.key}>{field.label} <span className="field-unit">{field.unit}</span><input className="field-input" type="number" min={field.min} max={field.max} step={field.unit === 'deg' ? 1 : 0.1} value={typeof activeTask?.draft[field.key] === 'number' ? Number(activeTask.draft[field.key]) : field.fallback} onChange={(event) => facade.task.update({ [field.key]: Number(event.target.value) })} /></label>)}
        <label className="field-label">Handedness<select className="field-input" value={typeof activeTask?.draft.localCoord === 'string' ? activeTask.draft.localCoord : 'Right-handed'} onChange={(event) => facade.task.update({ localCoord: event.target.value })}><option>Right-handed</option><option>Left-handed</option></select></label>
      </>}
      {booleanCommand && <>
        <label className="field-label">Base<select className="field-input" value={draftLink('base')} onChange={(event) => facade.task.update({ base: event.target.value })}><option value="">Select base</option>{shapeObjects.map((object) => <option value={object.id} key={object.id}>{objectLabel(object)}</option>)}</select></label>
        <label className="field-label">Tool<select className="field-input" value={draftLink('tool')} onChange={(event) => facade.task.update({ tool: event.target.value })}><option value="">Select tool</option>{shapeObjects.map((object) => <option value={object.id} key={object.id}>{objectLabel(object)}</option>)}</select></label>
      </>}
      {loftCommand && <>
        <span className="field-label">Sections</span>
        <div className="task-link-list">
          {sketchObjects.map((object) => <label className="check-row" key={object.id}><input type="checkbox" checked={loftSections.includes(object.id)} onChange={(event) => {
            const sections = event.target.checked ? [...loftSections, object.id] : loftSections.filter((id) => id !== object.id)
            facade.task.update({ sections, source: sections[0] || null })
          }} /><span>{sketchTaskLabel(object)}</span></label>)}
        </div>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.ruled === true} onChange={(event) => facade.task.update({ ruled: event.target.checked })} /><span>Ruled</span></label>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.closed === true} onChange={(event) => facade.task.update({ closed: event.target.checked })} /><span>Closed</span></label>
      </>}
      {pipeCommand && <>
        <label className="field-label">Profile<select className="field-input" value={draftLink('profile')} onChange={(event) => facade.task.update({ profile: event.target.value, source: event.target.value })}><option value="">Select profile</option>{sketchObjects.map((object) => <option value={object.id} key={object.id}>{sketchTaskLabel(object)}</option>)}</select></label>
        <label className="field-label">Path<select className="field-input" value={draftLink('spine')} onChange={(event) => facade.task.update({ spine: event.target.value })}><option value="">Select path</option>{sketchObjects.map((object) => <option value={object.id} key={object.id}>{sketchTaskLabel(object)}</option>)}</select></label>
        <label className="field-label">Transition<select className="field-input" value={typeof activeTask?.draft.transition === 'string' ? activeTask.draft.transition : 'Transformed'} onChange={(event) => facade.task.update({ transition: event.target.value })}><option>Transformed</option><option>Right corner</option><option>Round corner</option></select></label>
        <label className="field-label">Orientation<select className="field-input" value={typeof activeTask?.draft.mode === 'string' ? activeTask.draft.mode : 'Standard'} onChange={(event) => facade.task.update({ mode: event.target.value })}><option>Standard</option><option>Fixed</option><option>Frenet</option><option>Auxiliary</option></select></label>
      </>}
      {transformCommand && <>
        <label className="field-label">Transform mode<select className="field-input" value={typeof activeTask?.draft.transformMode === 'string' ? activeTask.draft.transformMode : 'Whole shape'} onChange={(event) => facade.task.update({ transformMode: event.target.value })}><option>Features</option><option>Whole shape</option></select></label>
        <label className="field-label">Support shape<select className="field-input" value={draftLink('base') || draftLink('source')} onChange={(event) => facade.task.update({ base: event.target.value, source: event.target.value })}>{shapeObjects.map((object) => <option value={object.id} key={object.id}>{objectLabel(object)}</option>)}</select></label>
        {(activeTask?.draft.transformMode === 'Features') && <><span className="field-label">Original features</span><div className="task-link-list">{transformFeatureObjects.map((object) => <label className="check-row" key={object.id}><input type="checkbox" checked={transformOriginals.includes(object.id)} onChange={(event) => facade.task.update({ originals: event.target.checked ? [...transformOriginals, object.id] : transformOriginals.filter((id) => id !== object.id) })} /><span>{objectLabel(object)}</span></label>)}</div></>}
      </>}
      {activeCommand === 'mirrored' && <>
        <label className="field-label">Mirror plane<select className="field-input" value={typeof activeTask?.draft.plane === 'string' ? activeTask.draft.plane : 'YZ plane'} onChange={(event) => facade.task.update({ plane: event.target.value })}><option>XY plane</option><option>XZ plane</option><option>YZ plane</option></select></label>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.fuse !== false} onChange={(event) => facade.task.update({ fuse: event.target.checked })} /><span>Fuse result</span></label>
      </>}
      {activeCommand === 'multi-transform' && <MultiTransformEditor value={multiTransform} onChange={(transformations) => facade.task.update({ transformations })} />}
      {currentFeatureField && <label className="field-label">{currentFeatureField.label} <span className="field-unit">{currentFeatureField.unit}</span><input className="field-input" type="number" min={0.001} max={currentFeatureField.unit === 'deg' ? 360 : undefined} step={currentFeatureField.unit === 'deg' ? 1 : 0.1} value={typeof activeTask?.draft[currentFeatureField.key] === 'number' ? Number(activeTask.draft[currentFeatureField.key]) : currentFeatureField.fallback} onChange={(event) => facade.task.update({ [currentFeatureField.key]: Number(event.target.value) })} /></label>}
      {['pad', 'pocket'].includes(activeCommand || '') && <>
        <label className="field-label">Side definition<select className="field-input" value={typeof activeTask?.draft.sideType === 'string' ? activeTask.draft.sideType : 'One side'} onChange={(event) => facade.task.update({ sideType: event.target.value })}><option>One side</option><option>Two sides</option><option>Symmetric</option></select></label>
        {activeTask?.draft.sideType === 'Two sides' && <label className="field-label">Length 2 <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask.draft.length2 === 'number' ? Number(activeTask.draft.length2) : 10} onChange={(event) => facade.task.update({ length2: Number(event.target.value) })} /></label>}
      </>}
      {activeCommand === 'linear-pattern' && <>
        <label className="field-label">Occurrences<input className="field-input" type="number" min={2} max={100} step={1} value={typeof activeTask?.draft.occurrences === 'number' ? Number(activeTask.draft.occurrences) : 2} onChange={(event) => facade.task.update({ occurrences: Number(event.target.value) })} /></label>
        <label className="field-label">Length <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask?.draft.length === 'number' ? Number(activeTask.draft.length) : 20} onChange={(event) => facade.task.update({ length: Number(event.target.value) })} /></label>
        <label className="field-label">Spacing <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask?.draft.offset === 'number' ? Number(activeTask.draft.offset) : 20} onChange={(event) => facade.task.update({ offset: Number(event.target.value) })} /></label>
        <label className="field-label">Direction<select className="field-input" value={typeof activeTask?.draft.direction === 'string' ? activeTask.draft.direction : 'Horizontal'} onChange={(event) => facade.task.update({ direction: event.target.value })}><option>Horizontal</option><option>Vertical</option><option>Normal</option></select></label>
        <label className="field-label">Mode<select className="field-input" value={typeof activeTask?.draft.mode === 'string' ? activeTask.draft.mode : 'Extent'} onChange={(event) => facade.task.update({ mode: event.target.value })}><option>Extent</option><option>Spacing</option></select></label>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.reversed === true} onChange={(event) => facade.task.update({ reversed: event.target.checked })} /><span>Reversed</span></label>
        <label className="field-label">Direction 2<select className="field-input" value={typeof activeTask?.draft.direction2 === 'string' ? activeTask.draft.direction2 : 'None'} onChange={(event) => facade.task.update({ direction2: event.target.value })}><option>None</option><option>Horizontal</option><option>Vertical</option><option>Normal</option></select></label>
        <label className="field-label">Occurrences 2<input className="field-input" type="number" min={1} max={100} step={1} value={typeof activeTask?.draft.occurrences2 === 'number' ? Number(activeTask.draft.occurrences2) : 1} onChange={(event) => facade.task.update({ occurrences2: Number(event.target.value) })} /></label>
        <label className="field-label">Length 2 <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask?.draft.length2 === 'number' ? Number(activeTask.draft.length2) : 20} onChange={(event) => facade.task.update({ length2: Number(event.target.value) })} /></label>
        <label className="field-label">Mode 2<select className="field-input" value={typeof activeTask?.draft.mode2 === 'string' ? activeTask.draft.mode2 : 'Extent'} onChange={(event) => facade.task.update({ mode2: event.target.value })}><option>Extent</option><option>Spacing</option></select></label>
        <label className="field-label">Spacing 2 <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask?.draft.offset2 === 'number' ? Number(activeTask.draft.offset2) : 20} onChange={(event) => facade.task.update({ offset2: Number(event.target.value) })} /></label>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.reversed2 === true} onChange={(event) => facade.task.update({ reversed2: event.target.checked })} /><span>Reversed 2</span></label>
      </>}
      {activeCommand === 'polar-pattern' && <>
        <label className="field-label">Occurrences<input className="field-input" type="number" min={2} max={100} step={1} value={typeof activeTask?.draft.occurrences === 'number' ? Number(activeTask.draft.occurrences) : 3} onChange={(event) => facade.task.update({ occurrences: Number(event.target.value) })} /></label>
        <label className="field-label">Angle <span className="field-unit">deg</span><input className="field-input" type="number" min={0.001} max={360} step={1} value={typeof activeTask?.draft.angle === 'number' ? Number(activeTask.draft.angle) : 360} onChange={(event) => facade.task.update({ angle: Number(event.target.value) })} /></label>
        <label className="field-label">Axis<select className="field-input" value={typeof activeTask?.draft.axis === 'string' ? activeTask.draft.axis : 'Normal'} onChange={(event) => facade.task.update({ axis: event.target.value })}><option>Normal</option><option>Horizontal</option><option>Vertical</option></select></label>
        <label className="field-label">Mode<select className="field-input" value={typeof activeTask?.draft.mode === 'string' ? activeTask.draft.mode : 'Extent'} onChange={(event) => facade.task.update({ mode: event.target.value })}><option>Extent</option><option>Spacing</option></select></label>
        <label className="field-label">Spacing <span className="field-unit">deg</span><input className="field-input" type="number" min={0.001} max={360} step={1} value={typeof activeTask?.draft.offset === 'number' ? Number(activeTask.draft.offset) : 30} onChange={(event) => facade.task.update({ offset: Number(event.target.value) })} /></label>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.reversed === true} onChange={(event) => facade.task.update({ reversed: event.target.checked })} /><span>Reversed</span></label>
      </>}
      {activeCommand === 'hole' && <>
        <label className="field-label">Diameter <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask?.draft.diameter === 'number' ? Number(activeTask.draft.diameter) : 5} onChange={(event) => facade.task.update({ diameter: Number(event.target.value) })} /></label>
        <label className="field-label">Depth <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask?.draft.depth === 'number' ? Number(activeTask.draft.depth) : 10} onChange={(event) => facade.task.update({ depth: Number(event.target.value) })} /></label>
        <label className="field-label">Type<select className="field-input" value={typeof activeTask?.draft.type === 'string' ? activeTask.draft.type : 'Dimension'} onChange={(event) => facade.task.update({ type: event.target.value, depthType: event.target.value === 'Through all' ? 'ThroughAll' : 'Dimension' })}><option>Dimension</option><option>Through all</option></select></label>
        <label className="field-label">Drill point<select className="field-input" value={typeof activeTask?.draft.drillPoint === 'string' ? activeTask.draft.drillPoint : 'Flat'} onChange={(event) => facade.task.update({ drillPoint: event.target.value })}><option>Flat</option><option>Angled</option></select></label>
        <label className="field-label">Drill angle <span className="field-unit">deg</span><input className="field-input" type="number" min={0.001} max={179.999} step={1} value={typeof activeTask?.draft.drillPointAngle === 'number' ? Number(activeTask.draft.drillPointAngle) : 118} onChange={(event) => facade.task.update({ drillPointAngle: Number(event.target.value) })} /></label>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.drillForDepth === true} onChange={(event) => facade.task.update({ drillForDepth: event.target.checked })} /><span>Include drill point in depth</span></label>
        <span className="field-label">Position <span className="field-unit">mm</span></span>
        <div className="task-vector-fields">{(['x', 'y', 'z'] as const).map((axis) => <label key={`hole-position-${axis}`}>{axis.toUpperCase()}<input className="field-input" type="number" step={0.1} value={typeof activeTask?.draft.position === 'object' && activeTask.draft.position && axis in activeTask.draft.position ? Number((activeTask.draft.position as { x: number; y: number; z: number })[axis]) : 0} onChange={(event) => { const value = typeof activeTask?.draft.position === 'object' && activeTask.draft.position ? { ...(activeTask.draft.position as { x: number; y: number; z: number }) } : { x: 0, y: 0, z: 0 }; facade.task.update({ position: { ...value, [axis]: Number(event.target.value) } }) }} /></label>)}</div>
        <span className="field-label">Direction</span>
        <div className="task-vector-fields">{(['x', 'y', 'z'] as const).map((axis) => <label key={`hole-direction-${axis}`}>{axis.toUpperCase()}<input className="field-input" type="number" step={0.1} value={typeof activeTask?.draft.direction === 'object' && activeTask.draft.direction && axis in activeTask.draft.direction ? Number((activeTask.draft.direction as { x: number; y: number; z: number })[axis]) : axis === 'z' ? 1 : 0} onChange={(event) => { const value = typeof activeTask?.draft.direction === 'object' && activeTask.draft.direction ? { ...(activeTask.draft.direction as { x: number; y: number; z: number }) } : { x: 0, y: 0, z: 1 }; facade.task.update({ direction: { ...value, [axis]: Number(event.target.value) } }) }} /></label>)}</div>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.reversed === true} onChange={(event) => facade.task.update({ reversed: event.target.checked })} /><span>Reversed</span></label>
        <label className="field-label">Cut type<select className="field-input" value={typeof activeTask?.draft.holeCutType === 'string' ? activeTask.draft.holeCutType : 'None'} onChange={(event) => facade.task.update({ holeCutType: event.target.value })}><option>None</option><option>Counterbore</option><option>Countersink</option><option>Counterdrill</option></select></label>
        <label className="field-label">Cut diameter <span className="field-unit">mm</span><input className="field-input" type="number" min={0} step={0.1} value={typeof activeTask?.draft.holeCutDiameter === 'number' ? Number(activeTask.draft.holeCutDiameter) : 8} onChange={(event) => facade.task.update({ holeCutDiameter: Number(event.target.value) })} /></label>
        <label className="field-label">Cut depth <span className="field-unit">mm</span><input className="field-input" type="number" min={0} step={0.1} value={typeof activeTask?.draft.holeCutDepth === 'number' ? Number(activeTask.draft.holeCutDepth) : 2} onChange={(event) => facade.task.update({ holeCutDepth: Number(event.target.value) })} /></label>
        <label className="field-label">Countersink angle <span className="field-unit">deg</span><input className="field-input" type="number" min={0.001} max={179.999} step={1} value={typeof activeTask?.draft.holeCutCountersinkAngle === 'number' ? Number(activeTask.draft.holeCutCountersinkAngle) : 90} onChange={(event) => facade.task.update({ holeCutCountersinkAngle: Number(event.target.value) })} /></label>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.tapered === true} onChange={(event) => facade.task.update({ tapered: event.target.checked })} /><span>Tapered</span></label>
        <label className="field-label">Taper angle <span className="field-unit">deg</span><input className="field-input" type="number" min={0.001} max={179.999} step={0.5} value={typeof activeTask?.draft.taperedAngle === 'number' ? Number(activeTask.draft.taperedAngle) : 90} onChange={(event) => facade.task.update({ taperedAngle: Number(event.target.value) })} /></label>
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.threaded === true} onChange={(event) => facade.task.update({ threaded: event.target.checked, threadType: event.target.checked && !activeTask?.draft.threadType ? 'ISOMetricProfile' : activeTask?.draft.threadType })} /><span>Threaded</span></label>
        {activeTask?.draft.threaded === true && <>
          <label className="field-label">Thread standard<select className="field-input" value={typeof activeTask.draft.threadType === 'string' ? activeTask.draft.threadType : 'ISOMetricProfile'} onChange={(event) => facade.task.update({ threadType: event.target.value })}>{['ISOMetricProfile', 'ISOMetricFineProfile', 'UNC', 'UNF', 'UNEF', 'NPT', 'BSP', 'BSW', 'BSF', 'ISOTyre'].map((value) => <option key={value}>{value}</option>)}</select></label>
          <label className="field-label">Thread size<select className="field-input" value={typeof activeTask.draft.threadSize === 'string' ? activeTask.draft.threadSize : 'M6x1.0'} onChange={(event) => facade.task.update({ threadSize: event.target.value })}>{['M1x0.25', 'M2x0.4', 'M3x0.5', 'M4x0.7', 'M5x0.8', 'M6x1.0', 'M8x1.25', 'M10x1.5', '#4', '#6', '#8', '#10', '1/4'].map((value) => <option key={value}>{value}</option>)}</select></label>
          <label className="field-label">Major diameter <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask.draft.threadDiameter === 'number' ? Number(activeTask.draft.threadDiameter) : 6} onChange={(event) => facade.task.update({ threadDiameter: Number(event.target.value) })} /></label>
          <label className="field-label">Pitch <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.05} value={typeof activeTask.draft.threadPitch === 'number' ? Number(activeTask.draft.threadPitch) : 1} onChange={(event) => facade.task.update({ threadPitch: Number(event.target.value) })} /></label>
          <label className="field-label">Direction<select className="field-input" value={typeof activeTask.draft.threadDirection === 'string' ? activeTask.draft.threadDirection : 'Right'} onChange={(event) => facade.task.update({ threadDirection: event.target.value })}><option>Right</option><option>Left</option></select></label>
          <label className="check-row"><input type="checkbox" checked={activeTask.draft.modelThread === true} onChange={(event) => facade.task.update({ modelThread: event.target.checked })} /><span>Model thread geometry</span></label>
          {activeTask.draft.modelThread === true && <><label className="field-label">Thread depth type<select className="field-input" value={typeof activeTask.draft.threadDepthType === 'string' ? activeTask.draft.threadDepthType : 'Hole Depth'} onChange={(event) => facade.task.update({ threadDepthType: event.target.value })}><option>Hole Depth</option><option>Dimension</option><option>Tapped (DIN76)</option></select></label>{activeTask.draft.threadDepthType === 'Dimension' && <label className="field-label">Thread depth <span className="field-unit">mm</span><input className="field-input" type="number" min={0.001} step={0.1} value={typeof activeTask.draft.threadDepth === 'number' ? Number(activeTask.draft.threadDepth) : 10} onChange={(event) => facade.task.update({ threadDepth: Number(event.target.value) })} /></label>}</>}
        </>}
      </>}
      {activeCommand === 'pocket' && <label className="field-label">Type<select className="field-input" value={typeof activeTask?.draft.type === 'string' ? activeTask.draft.type : 'Through all'} onChange={(event) => facade.task.update({ type: event.target.value })}><option>Dimension</option><option>Through all</option><option>Up to face</option><option>TwoLengths</option></select></label>}
      {['pad', 'pocket', 'revolution', 'groove'].includes(activeCommand || '') && <label className="check-row"><input type="checkbox" checked={activeTask?.draft.reversed === true} onChange={(event) => facade.task.update({ reversed: event.target.checked })} /><span>Reversed</span></label>}
      {activeCommand === 'pad' && <label className="check-row"><input type="checkbox" checked={activeTask?.draft.midplane === true} onChange={(event) => facade.task.update({ midplane: event.target.checked })} /><span>Symmetric to plane</span></label>}
      {['revolution', 'groove'].includes(activeCommand || '') && <>
        <label className="field-label">Type<select className="field-input" value={typeof activeTask?.draft.type === 'string' ? activeTask.draft.type : 'Angle'} onChange={(event) => facade.task.update({ type: event.target.value })}><option>Angle</option><option>Two angles</option><option>To last</option><option>To first</option><option>Up to face</option></select></label>
        {activeTask?.draft.type === 'Two angles' && <label className="field-label">Reverse angle <span className="field-unit">deg</span><input className="field-input" type="number" min={0} max={360} step={1} value={typeof activeTask.draft.angle2 === 'number' ? Number(activeTask.draft.angle2) : 0} onChange={(event) => facade.task.update({ angle2: Number(event.target.value) })} /></label>}
        <label className="check-row"><input type="checkbox" checked={activeTask?.draft.midplane === true} onChange={(event) => facade.task.update({ midplane: event.target.checked })} /><span>Symmetric to plane</span></label>
      </>}
      {!activeCommand || activeCommand === 'create-sketch' ? <>
        <label className="field-label">Primary value <span className="field-unit">mm</span><input className="field-input" value={isSketch ? 'Fully constrained' : '42.00'} readOnly /></label>
        <label className="field-label">Operation<select className="field-input"><option>{workbench === 'Part Design' ? 'Dimension' : 'Contextual preview'}</option><option>Through all</option><option>Up to face</option></select></label>
      </> : null}
      <label className="check-row"><input type="checkbox" defaultChecked /><span>Preview result in viewport</span></label>
      <div className="task-note"><AlertTriangle size={14} /><span>Changes remain local until the document is recomputed.</span></div>
    </div>
  </div>
}

function PropertyPanel({ facade, objectId, scope, showNotice }: { facade: BitBybitWebCadFacade; objectId: string; scope: 'data' | 'view'; showNotice: (message: string) => void }) {
  const object = facade.app.document.getObject(objectId)
  if (!object) return <div className="properties-empty">No object selected</div>
  const properties = object.properties.filter((property) => property.scope === scope && !property.hidden && !isFreecadPropertyHidden(property.nativeStatus))
  const groups = new Map<string, ObjectPropertySnapshot[]>()
  properties.forEach((property) => groups.set(property.group, [...(groups.get(property.group) ?? []), property]))
  return <div className="properties-scroll">{[...groups].map(([group, entries]) => <div className="property-group" key={group}><div className="property-group-title">{group}<ChevronDown size={14} /></div>{entries.map((property) => <PropertyEditor key={`${objectId}-${property.name}-${String(property.value)}`} facade={facade} objectId={objectId} property={property} showNotice={showNotice} />)}</div>)}</div>
}

const colorChannelByte = (value: number) => Math.max(0, Math.min(255, Math.round(value * 255)))
const colorValueHex = (value: ColorValue) => `#${value.slice(0, 3).map((channel) => colorChannelByte(channel).toString(16).padStart(2, '0')).join('')}`
const colorHexChannels = (value: string): [number, number, number] => [1, 3, 5].map((offset) => Number.parseInt(value.slice(offset, offset + 2), 16) / 255) as [number, number, number]

function ColorListEditor({ label, value, commit }: { label: string; value: ColorListValue; commit: (value: ColorListValue) => boolean }) {
  const replace = (index: number, color: ColorValue) => commit(value.map((entry, entryIndex) => entryIndex === index ? [...color] as ColorValue : [...entry] as ColorValue))
  return <div className="property-color-list">
    {value.map((color, index) => {
      const hex = colorValueHex(color)
      return <div className="property-color-list-item" key={`${index}-${color.join(':')}`}>
        <input type="color" value={hex} aria-label={`${label} ${index + 1} RGB`} onChange={(event) => { const [red, green, blue] = colorHexChannels(event.target.value); replace(index, [red, green, blue, color[3]]) }} />
        <input className="property-control property-color-alpha" type="number" step={0.01} defaultValue={color[3]} aria-label={`${label} ${index + 1} alpha`} title="Alpha" onBlur={(event) => { const alpha = Number(event.target.value); if (!Number.isFinite(alpha) || !replace(index, [color[0], color[1], color[2], alpha])) event.target.value = String(color[3]) }} onKeyDown={(event) => { if (event.key === 'Enter') event.currentTarget.blur() }} />
        <IconButton icon={Trash2} label={`Remove ${label} ${index + 1}`} onClick={() => commit(value.filter((_, entryIndex) => entryIndex !== index).map((entry) => [...entry] as ColorValue))} />
      </div>
    })}
    <div className="property-color-list-actions"><IconButton icon={Plus} label={`Add ${label} color`} onClick={() => commit([...value.map((entry) => [...entry] as ColorValue), [0, 0, 0, 1]])} /></div>
  </div>
}

export function PropertyEditor({ facade, objectId, property, showNotice }: { facade: BitBybitWebCadFacade; objectId: string; property: ObjectPropertySnapshot; showNotice: (message: string) => void }) {
  const commit = (value: PropertyValue) => {
    try {
      facade.app.document.setProperty({ objectId, propertyName: property.name, value })
      return true
    } catch (error) {
      showNotice(error instanceof Error ? error.message : String(error))
      return false
    }
  }
  const includedFile = property.type === 'App::PropertyFileIncluded' && property.value && typeof property.value === 'object' && !Array.isArray(property.value) ? property.value as IncludedFileValue : null
  const formatted = `${property.type === 'App::PropertyFileIncluded' ? includedFile?.archiveName ?? 'Empty' : String(property.value ?? '')}${property.unit ? ` ${property.unit}` : ''}`
  let editor: ReactNode
  if (property.readOnly || isFreecadPropertyReadOnly(property.nativeStatus)) editor = <span className="property-readonly">{formatted}</span>
  else if (property.type === 'App::PropertyFileIncluded') editor = <label className="property-included-file"><span title={includedFile?.resourceHash}>{includedFile?.archiveName ?? 'Empty'}</span><input className="property-control" type="file" aria-label={property.label} onChange={(event) => { const file = event.target.files?.[0]; event.target.value = ''; if (!file) return; void file.arrayBuffer().then((buffer) => facade.app.document.setIncludedFile({ objectId, propertyName: property.name, fileName: file.name, bytes: new Uint8Array(buffer), mediaType: file.type || undefined })).then((value) => showNotice(`${value.archiveName} embedded`)).catch((error: unknown) => showNotice(error instanceof Error ? error.message : String(error))) }} /></label>
  else if (property.type === 'App::PropertyBool') editor = <input className="property-checkbox" type="checkbox" checked={Boolean(property.value)} aria-label={property.label} onChange={(event) => commit(event.target.checked)} />
  else if (property.type === 'App::PropertyFont') editor = <input className="property-control" type="text" autoComplete="off" defaultValue={String(property.value)} aria-label={property.label} style={{ fontFamily: String(property.value) }} onBlur={(event) => { if (!commit(event.target.value)) event.target.value = String(property.value) }} onKeyDown={(event) => { if (event.key === 'Enter') event.currentTarget.blur() }} />
  else if (property.type === 'App::PropertyEnumeration') editor = <select className="property-control" value={String(property.value)} aria-label={property.label} onChange={(event) => commit(event.target.value)}>{property.options?.map((option) => <option key={option}>{option}</option>)}</select>
  else if (property.type === 'App::PropertyLink') {
    const document = facade.app.document.getActive()
    const ids = [...new Set(document.tree.flatMap((item) => [item.id, ...(item.children ?? [])]))]
    editor = <select className="property-control property-link-control" value={String(property.value ?? '')} aria-label={property.label} onChange={(event) => commit(event.target.value || null)}><option value="">None</option>{ids.filter((id) => id !== objectId).map((id) => <option value={id} key={id}>{document.tree.find((item) => item.id === id)?.label ?? id}</option>)}</select>
  } else if (property.type === 'Part::PropertyPartShape') {
    const value = property.value && typeof property.value === 'object' && !Array.isArray(property.value) && 'path' in property.value ? property.value as ShapeResourceValue : null
    editor = <span className="property-readonly" title={value?.path}>{value?.path ?? 'Empty Shape'}</span>
  } else if (property.type === 'App::PropertyLinkSubList') {
    const value = property.value && typeof property.value === 'object' && !Array.isArray(property.value) && 'entries' in property.value ? property.value as LinkSubListValue : null
    editor = value ? <div className="property-link-sub property-link-sub-list"><div className="property-link-sub-items">{value.entries.map((entry, index) => <div className="property-link-sub-item" key={`${entry.objectId}:${typeof entry.subElement === 'string' ? entry.subElement : entry.subElement?.persistentId ?? index}`}><span title={typeof entry.subElement === 'string' ? entry.subElement : entry.subElement?.persistentId}>{entry.objectId} · {typeof entry.subElement === 'string' ? entry.subElement || 'whole object' : entry.subElement ? `${entry.subElement.kind} · ${entry.subElement.status}` : 'whole object'}</span></div>)}</div><IconButton icon={X} label="Clear subshape links" onClick={() => commit({ schemaVersion: 1, entries: [] })} /></div> : <span className="property-readonly">None</span>
  } else if (property.type === 'App::PropertyLinkSub') {
    const value = property.value && typeof property.value === 'object' && !Array.isArray(property.value) && 'schemaVersion' in property.value ? property.value as TopoRefValue | LinkSubValue : null
    const refs = value ? ('subElements' in value ? value.subElements.filter((entry): entry is TopoRefValue => typeof entry !== 'string') : [value]) : []
    const legacyNames = value && 'subElements' in value ? value.subElements.filter((entry): entry is string => typeof entry === 'string') : []
    const sourceId = value?.objectId
    const source = sourceId ? facade.app.document.getObject(sourceId) : null
    const replace = (ref: TopoRefValue, candidatePersistentId: string) => {
      if (!candidatePersistentId) return
      try {
        facade.app.document.resolveTopologyReference({ ownerObjectId: objectId, referenceName: property.name, candidatePersistentId, currentPersistentId: ref.persistentId })
        void facade.app.document.recomputeAsync().then((result) => showNotice(result.status === 'completed' ? `${property.label} reference replaced` : `Reference replaced; recompute ${result.status}`))
      } catch (error) { showNotice(error instanceof Error ? error.message : String(error)) }
    }
    editor = value ? <div className="property-link-sub property-link-sub-list"><div className="property-link-sub-items">{refs.map((ref) => {
      const candidates = ref.candidates?.length ? ref.candidates : source?.topology?.entries.filter((entry) => entry.ref.kind === ref.kind && entry.ref.status !== 'deleted').map((entry) => entry.ref.persistentId) ?? []
      return <div className="property-link-sub-item" key={`${ref.kind}:${ref.persistentId}`}><span title={ref.persistentId}>{ref.kind} · {ref.status}</span>{(ref.status === 'ambiguous' || ref.status === 'deleted') && candidates.length > 0 ? <select className="property-control property-topology-candidates" aria-label={`${property.label} ${ref.persistentId} replacement`} defaultValue="" onChange={(event) => replace(ref, event.target.value)}><option value="">Replace</option>{candidates.map((candidate) => <option value={candidate} key={candidate}>{candidate}</option>)}</select> : null}</div>
    })}{legacyNames.map((name) => <div className="property-link-sub-item" key={`legacy:${name}`}><span title={name}>{name} · unbound</span></div>)}</div><IconButton icon={X} label="Clear subshape references" onClick={() => commit(null)} /></div> : <span className="property-readonly">None</span>
  } else if (property.type === 'App::PropertyMultiTransform') {
    editor = <MultiTransformEditor value={multiTransformValue(property.value)} onChange={commit} />
  } else if (property.type === 'App::PropertyPlacement') {
    const placement = property.value as PlacementValue
    const updatePlacement = (path: 'position' | 'axis' | 'angle', key: 'x' | 'y' | 'z' | null, next: number) => {
      const value: PlacementValue = { position: { ...placement.position }, rotation: { axis: { ...placement.rotation.axis }, angle: placement.rotation.angle } }
      if (path === 'position' && key) value.position[key] = next
      else if (path === 'axis' && key) value.rotation.axis[key] = next
      else value.rotation.angle = next
      return commit(value)
    }
    editor = <details className="property-placement"><summary>{placement.position.x}, {placement.position.y}, {placement.position.z}</summary><div className="property-placement-grid"><span>Position</span>{(['x', 'y', 'z'] as const).map((key) => <input key={`p-${key}`} type="number" aria-label={`${property.label} position ${key}`} defaultValue={placement.position[key]} onBlur={(event) => updatePlacement('position', key, Number(event.target.value))} />)}<span>Axis</span>{(['x', 'y', 'z'] as const).map((key) => <input key={`a-${key}`} type="number" aria-label={`${property.label} axis ${key}`} defaultValue={placement.rotation.axis[key]} onBlur={(event) => updatePlacement('axis', key, Number(event.target.value))} />)}<span>Angle</span><input type="number" aria-label={`${property.label} angle`} defaultValue={placement.rotation.angle} onBlur={(event) => updatePlacement('angle', null, Number(event.target.value))} /><span /><span /></div></details>
  } else if (property.type === 'App::PropertyVector' || property.type === 'App::PropertyDirection') {
    const vector = property.value as { x: number; y: number; z: number }
    editor = <div className="property-vector">{(['x', 'y', 'z'] as const).map((key) => <input key={key} type="number" aria-label={`${property.label} ${key}`} defaultValue={vector[key]} onBlur={(event) => commit({ ...vector, [key]: Number(event.target.value) })} />)}</div>
  } else if (property.type === 'App::PropertyLinkList' || property.type === 'App::PropertyStringList') editor = <input className="property-control" defaultValue={(property.value as string[]).join(', ')} aria-label={property.label} onBlur={(event) => commit(event.target.value.split(',').map((entry) => entry.trim()).filter(Boolean))} />
  else if (property.type === 'App::PropertyBoolList') editor = <input className="property-control" defaultValue={(property.value as boolean[]).map((entry) => entry ? 'true' : 'false').join(', ')} aria-label={property.label} onBlur={(event) => { const raw = event.target.value.split(',').map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0); const values = raw.map((entry) => entry === 'true' || entry === '1' ? true : entry === 'false' || entry === '0' ? false : null); if (values.some((value) => value === null) || !commit(values as boolean[])) event.target.value = (property.value as boolean[]).map((entry) => entry ? 'true' : 'false').join(', ') }} />
  else if (property.type === 'App::PropertyIntegerSet') editor = <input className="property-control" defaultValue={(property.value as number[]).join(', ')} aria-label={property.label} onBlur={(event) => { const raw = event.target.value.split(',').map((entry) => entry.trim()).filter(Boolean); const values = raw.map(Number); if (!values.every(Number.isSafeInteger) || !commit(values)) event.target.value = (property.value as number[]).join(', ') }} />
  else if (property.type === 'App::PropertyFloatList') editor = <input className="property-control" defaultValue={(property.value as number[]).join(', ')} aria-label={property.label} onBlur={(event) => { const values = event.target.value.split(',').map((entry) => Number(entry.trim())); if (!values.every((value) => Number.isFinite(value))) { event.target.value = (property.value as number[]).join(', '); return } if (!commit(values)) event.target.value = (property.value as number[]).join(', ') }} />
  else if (property.type === 'App::PropertyColorList') editor = <ColorListEditor label={property.label} value={property.value as ColorListValue} commit={commit} />
  else if (property.type === 'App::PropertyColor' && property.scope === 'view') editor = <label className="property-color"><input type="color" value={String(property.value)} aria-label={property.label} onChange={(event) => commit(event.target.value)} /><span>{String(property.value).toUpperCase()}</span></label>
  else if (property.type === 'App::PropertyColor') {
    const color = property.value as ColorValue
    const hex = colorValueHex(color)
    editor = <label className="property-color property-color-rgba"><input type="color" value={hex} aria-label={`${property.label} RGB`} onChange={(event) => { const [red, green, blue] = colorHexChannels(event.target.value); commit([red, green, blue, color[3]]) }} /><input className="property-control property-color-alpha" type="number" step={0.01} defaultValue={color[3]} aria-label={`${property.label} alpha`} title="Alpha" onBlur={(event) => { const alpha = Number(event.target.value); if (!Number.isFinite(alpha) || !commit([color[0], color[1], color[2], alpha])) event.target.value = String(color[3]) }} onKeyDown={(event) => { if (event.key === 'Enter') event.currentTarget.blur() }} /><span>{hex.toUpperCase()}</span></label>
  }
  else if (property.type === 'App::PropertyLength' || property.type === 'App::PropertyAngle' || property.type === 'App::PropertyAcceleration' || property.type === 'App::PropertyArea' || property.type === 'App::PropertyForce' || property.type === 'App::PropertyHeatFlux' || property.type === 'App::PropertyPercent' || property.type === 'App::PropertyFloat' || property.type === 'App::PropertyInteger') editor = <label className="property-number"><input className="property-control" type="number" defaultValue={Number(property.value)} min={property.name === 'Occurrences' ? 2 : property.type === 'App::PropertyLength' || property.type === 'App::PropertyAngle' || property.type === 'App::PropertyArea' || property.type === 'App::PropertyPercent' ? 0 : undefined} max={property.name === 'Occurrences' ? 100 : property.type === 'App::PropertyAngle' ? 360 : property.type === 'App::PropertyPercent' ? 100 : undefined} step={property.type === 'App::PropertyLength' || property.type === 'App::PropertyArea' ? 0.1 : 1} aria-label={property.label} onBlur={(event) => { if (!commit(Number(event.target.value))) event.target.value = String(property.value) }} onKeyDown={(event) => { if (event.key === 'Enter') event.currentTarget.blur() }} /><span>{property.unit}</span></label>
  else editor = <input className="property-control" defaultValue={String(property.value ?? '')} aria-label={property.label} onBlur={(event) => { if (!commit(event.target.value)) event.target.value = String(property.value ?? '') }} onKeyDown={(event) => { if (event.key === 'Enter') event.currentTarget.blur() }} />
  return <><div className="property-row"><span className="property-label">{property.label}</span><div className="property-editor">{editor}</div></div>{property.expression && <div className="property-expression"><Code2 size={12} /><span>{property.expression}</span></div>}</>
}

function Viewport({ selectedObject, selectedObjects, setSelectedObject, diagnosticFlash, workbench, facade, documentVersion, recomputeGeneration, showNotice }: { selectedObject: string; selectedObjects: string[]; setSelectedObject: (id: string) => void; diagnosticFlash: { objectId: string; token: number } | null; workbench: Workbench; facade: BitBybitWebCadFacade; documentVersion: number; recomputeGeneration: number; showNotice: (message: string) => void }) {
  const hostRef = useRef<HTMLDivElement>(null)
  const adapterRef = useRef<BitBybitViewportAdapter | null>(null)
  const [orientation, setOrientation] = useState<'axonometric' | 'front'>('axonometric')
  const camSnapshot = useCamSnapshot(facade)
  useEffect(() => {
    const host = hostRef.current
    if (!host) return
    const adapter = facade.viewport.createAdapter()
    adapterRef.current = adapter
    let cancelled = false
    const shapes: ShapeHandle[] = []
    let ownsShapes = false
    try {
      adapter.mount(host)
    } catch (error) {
      showNotice(`WebGL viewport unavailable: ${error instanceof Error ? error.message : 'unknown error'}`)
      adapter.dispose()
      adapterRef.current = null
      return
    }
    const loadGeometry = async () => {
      const capabilities = await facade.geometry.initialize()
      if (capabilities.status !== 'ready') throw new Error(capabilities.reason || 'OCCT geometry runtime unavailable')
      const document = facade.app.document.getActive()
      adapter.setInteractionHandlers?.({
        onObjectClick: (objectId, options) => {
          if (!objectId) return
          if (!options?.additive) { setSelectedObject(objectId); return }
          const current = facade.selection.getObjectIds()
          facade.selection.selectObjects(current.includes(objectId) ? current.filter((candidate) => candidate !== objectId) : [...current, objectId])
        },
        onSubshapeClick: (selection) => {
          const objectId = selection.objectId
          if (!objectId) return
          const object = facade.app.document.getObject(objectId)
          const available = object?.topology?.entries.some((entry) => entry.ref.kind === selection.kind && entry.ref.persistentId === selection.persistentId)
          if (available) facade.selection.selectSubshape({ objectId, kind: selection.kind, persistentId: selection.persistentId })
          else setSelectedObject(objectId)
        },
        onSubshapeHover: (selection) => {
          const objectId = selection?.objectId
          if (!objectId) { facade.selection.preselectSubshape(null); return }
          const object = facade.app.document.getObject(objectId)
          const available = selection && object?.topology?.entries.some((entry) => entry.ref.kind === selection.kind && entry.ref.persistentId === selection.persistentId)
          facade.selection.preselectSubshape(available ? { objectId, kind: selection.kind, persistentId: selection.persistentId } : null)
        },
        onBoxSelect: ({ objectIds, additive }) => {
          const next = additive ? [...facade.selection.getObjectIds(), ...objectIds] : objectIds
          facade.selection.selectObjects([...new Set(next)])
        },
      })
      const bodyFeatureIds = new Set(document.tree.filter((item) => item.type === 'body').flatMap((item) => item.children ?? []))
      const bodyTipIds = new Set(document.objects.filter((object) => object.typeId === 'PartDesign::Body').flatMap((object) => {
        const tip = object.properties.find((property) => property.name === 'Tip')?.value
        return typeof tip === 'string' && tip ? [tip] : []
      }))
      const storedShapes = document.objects.flatMap((object) => {
        const visible = object.properties.find((property) => property.name === 'Visibility')?.value !== false
        if (!visible || (bodyFeatureIds.has(object.id) && !bodyTipIds.has(object.id))) return []
        const shape = facade.geometry.getObjectShape(object.id)
        return shape ? [{ objectId: object.id, shape }] : []
      })
      if (storedShapes.length > 0) {
        const meshes = await Promise.all(storedShapes.map(async ({ objectId, shape }) => ({ objectId, mesh: await facade.geometry.mesh(shape, 0.05) })))
        if (!cancelled) {
          if (adapter.setMeshes) adapter.setMeshes(meshes)
          else adapter.setMesh(meshes[0]?.mesh ?? null)
        }
        return
      }
      ownsShapes = true
      const profile = { outer: [[-1.35, -0.7, 0], [1.35, -0.7, 0], [1.35, 0.7, 0], [-1.35, 0.7, 0]] as [number, number, number][] }
      const pad = await facade.geometry.pad({ profile, length: 1.6, direction: [0, 0, 1], documentId: document.id, documentVersion: document.version })
      shapes.push(pad)
      const pocketProfile = { outer: [[-0.55, -0.32, 0], [0.55, -0.32, 0], [0.55, 0.32, 0], [-0.55, 0.32, 0]] as [number, number, number][] }
      const pocket = await facade.geometry.pocket({ base: pad, profile: pocketProfile, length: 1.8, direction: [0, 0, 1], documentId: document.id, documentVersion: document.version })
      shapes.push(pocket)
      if (cancelled) {
        await Promise.all(shapes.map((entry) => facade.geometry.release(entry)))
        return
      }
      const mesh = await facade.geometry.mesh(pocket, 0.05)
      if (!cancelled) {
        if (adapter.setMeshes) adapter.setMeshes([{ objectId: document.objects.some((object) => object.id === 'pocket') ? 'pocket' : 'preview', mesh }])
        else adapter.setMesh(mesh)
      }
    }
    void loadGeometry().catch((error: unknown) => {
      if (!cancelled) showNotice(`Geometry preview unavailable: ${error instanceof Error ? error.message : String(error)}`)
    })
    return () => {
      cancelled = true
      if (ownsShapes) void Promise.all(shapes.map((entry) => facade.geometry.release(entry)))
      adapter.dispose()
      adapterRef.current = null
    }
  }, [facade, showNotice, documentVersion, recomputeGeneration, setSelectedObject])

  useEffect(() => {
    if (adapterRef.current?.setSelectedObjects) adapterRef.current.setSelectedObjects(selectedObjects)
    else adapterRef.current?.setSelection(selectedObject)
  }, [selectedObject, selectedObjects])
  useEffect(() => {
    if (diagnosticFlash?.objectId === selectedObject) adapterRef.current?.flashSelection?.()
  }, [diagnosticFlash, selectedObject])
  useEffect(() => adapterRef.current?.setToolpath(workbench === 'CAM' ? camSnapshot.operations.filter((operation) => operation.active && operation.path.length > 1).map((operation) => operation.path) : []), [workbench, camSnapshot])
  const applyView = (next: 'axonometric' | 'front') => {
    adapterRef.current?.setView(next)
    setOrientation(next)
  }
  const activeCamOperation = camSnapshot.operations.filter((operation) => operation.active).at(-1)
  return <div className="viewport" data-view-orientation={orientation}><div className="viewport-header"><div className="viewport-title"><span className="eyebrow">{workbench}</span><strong>{workbench === 'CAM' ? `${camSnapshot.label} / ${activeCamOperation?.label || 'No operation'}` : 'Body / Fillet'}</strong></div><div className="viewport-actions"><IconButton icon={ZoomOut} label="Zoom out" onClick={() => adapterRef.current?.zoomBy(1.2)} /><IconButton icon={ZoomIn} label="Zoom in" onClick={() => adapterRef.current?.zoomBy(0.8)} /><IconButton icon={LayoutGrid} label="Front view" active={orientation === 'front'} onClick={() => applyView('front')} /><IconButton icon={Box} label="Axonometric view" active={orientation === 'axonometric'} onClick={() => applyView('axonometric')} /><IconButton icon={Maximize2} label="Fit all" onClick={() => adapterRef.current?.fitAll()} /></div></div><div className="viewport-grid" onClick={() => setSelectedObject('')}><div className="three-viewport-host" ref={hostRef} aria-label="Three.js viewport" /></div><div className="viewport-legend">{workbench === 'CAM' && <span><span className="legend-swatch toolpath" />Toolpath</span>}<span><span className="legend-swatch selected" />Selected</span><span><span className="legend-swatch edge" />Edges</span><span><span className="legend-swatch datum" />Datum</span></div></div>
}

function PageFrame({ page, onNavigate, onOpenWorkspace, showNotice, facade }: { page: Page; onNavigate: (page: Page) => void; onOpenWorkspace: () => void; showNotice: (message: string) => void; facade: BitBybitWebCadFacade }) {
  const pages: Record<Exclude<Page, 'workspace'>, ReactNode> = {
    start: <StartPage onNavigate={onNavigate} onOpenWorkspace={onOpenWorkspace} showNotice={showNotice} />,
    projects: <ProjectsPage onNavigate={onNavigate} onOpenWorkspace={onOpenWorkspace} showNotice={showNotice} facade={facade} />,
    import: <FileFlowPage mode="import" onNavigate={onNavigate} showNotice={showNotice} facade={facade} />,
    export: <FileFlowPage mode="export" onNavigate={onNavigate} showNotice={showNotice} facade={facade} />,
    settings: <SettingsPage onNavigate={onNavigate} showNotice={showNotice} />,
    help: <HelpPage onNavigate={onNavigate} showNotice={showNotice} />,
    diagnostics: <DiagnosticsPage onNavigate={onNavigate} showNotice={showNotice} facade={facade} />,
    sync: <SyncPage onNavigate={onNavigate} showNotice={showNotice} />,
  }
  return <div className="page-shell"><div className="page-content">{pages[page as Exclude<Page, 'workspace'>]}</div></div>
}

function PageHeader({ eyebrow, title, description, actions, onBack }: { eyebrow: string; title: string; description: string; actions?: ReactNode; onBack?: () => void }) {
  return <div className="page-header"><div className="page-heading"><div className="page-eyebrow">{onBack && <button className="back-button" onClick={onBack} title="Back"><ArrowLeft size={15} /></button>}<span>{eyebrow}</span></div><h1>{title}</h1><p>{description}</p></div>{actions && <div className="page-actions">{actions}</div>}</div>
}

function StartPage({ onNavigate, onOpenWorkspace, showNotice }: { onNavigate: (page: Page) => void; onOpenWorkspace: () => void; showNotice: (message: string) => void }) {
  return <div className="start-page">
    <PageHeader eyebrow="Local-first workspace" title="Start a new design." description="A focused CAD workspace for parametric parts, drawings and assemblies." actions={<><button className="button button-outline" onClick={() => onNavigate('projects')}><FolderOpen size={16} />Open project</button><button className="button button-primary" onClick={onOpenWorkspace}><Plus size={16} />New document</button></>} />
    <div className="start-grid"><section className="start-hero panel-surface"><div className="hero-copy"><span className="section-kicker"><Sparkles size={14} />FreeCAD-compatible workspace</span><h2>Model with a clear history.</h2><p>Build parts through ordered features, inspect every dependency and keep your project local by default.</p><div className="hero-actions"><button className="button button-primary" onClick={onOpenWorkspace}><FilePlus2 size={16} />Create blank document</button><button className="text-button" onClick={() => showNotice('Example gallery opened')}>Browse examples <ArrowRight size={14} /></button></div></div><div className="hero-schematic"><div className="schematic-axis axis-x-line" /><div className="schematic-axis axis-y-line" /><div className="schematic-block block-a" /><div className="schematic-block block-b" /><div className="schematic-ring" /><span className="schematic-label label-a">Feature history</span><span className="schematic-label label-b">Parametric part</span></div></section><section className="quick-panel panel-surface"><div className="section-title"><div><span className="section-kicker">Quick access</span><h3>Keep moving</h3></div><MoreHorizontal size={16} /></div><QuickAction icon={FolderOpen} title="Open a project" detail="Browse local files" onClick={() => onNavigate('projects')} /><QuickAction icon={Upload} title="Import a model" detail="STEP, IGES, STL and mesh" onClick={() => onNavigate('import')} /><QuickAction icon={WandSparkles} title="Start from an example" detail="Parametric reference models" onClick={() => showNotice('Example gallery opened')} /></section></div>
    <section className="recent-section"><div className="section-title"><div><span className="section-kicker">Recent projects</span><h3>Your local workspace</h3></div><button className="text-button" onClick={() => onNavigate('projects')}>View all <ArrowRight size={14} /></button></div><div className="recent-grid">{projects.slice(0, 3).map((project, index) => <ProjectCard project={project} key={project.name} index={index} onClick={onOpenWorkspace} />)}</div></section>
    <div className="start-footer"><div><HardDrive size={15} /><span>Local storage capability</span><Badge tone="cyan">Probe on open</Badge></div><div><span>BitBybit CAD Studio</span><span className="footer-separator">·</span><span>FreeCAD UI baseline 1.1.1</span></div></div>
  </div>
}

function QuickAction({ icon: ActionIcon, title, detail, onClick }: { icon: Icon; title: string; detail: string; onClick: () => void }) {
  return <button className="quick-action" onClick={onClick}><span className="quick-icon"><ActionIcon size={17} /></span><span><strong>{title}</strong><small>{detail}</small></span><ArrowRight size={15} /></button>
}

function ProjectCard({ project, index, onClick }: { project: typeof projects[number]; index: number; onClick: () => void }) {
  return <button className="project-card" onClick={onClick}><div className={`project-preview preview-${index}`}><div className="preview-shape" /><div className="preview-grid" /></div><div className="project-card-info"><div className="project-card-title"><strong>{project.name}</strong><MoreHorizontal size={15} /></div><span>{project.path}</span><div className="project-card-meta"><span>{project.modified}</span><span>{project.objects} objects</span><Badge tone={project.state === 'Unsaved' ? 'amber' : project.state === 'Read only' ? 'muted' : 'green'}>{project.state}</Badge></div></div></button>
}

function ProjectsPage({ onNavigate, onOpenWorkspace, showNotice, facade }: { onNavigate: (page: Page) => void; onOpenWorkspace: () => void; showNotice: (message: string) => void; facade: BitBybitWebCadFacade }) {
  const [savedProjects, setSavedProjects] = useState<ProjectSummary[]>([])
  const [loading, setLoading] = useState(true)
  useEffect(() => {
    let cancelled = false
    void facade.project.list().then((items) => { if (!cancelled) setSavedProjects(items) }).catch((error: unknown) => { if (!cancelled) showNotice(`Project list unavailable: ${error instanceof Error ? error.message : String(error)}`) }).finally(() => { if (!cancelled) setLoading(false) })
    return () => { cancelled = true }
  }, [facade, showNotice])
  const openSavedProject = async (documentId: string) => {
    const loaded = await facade.app.document.load(documentId)
    if (!loaded) { showNotice('No saved local snapshot found'); return }
    showNotice(`Loaded ${loaded.label}`)
    onOpenWorkspace()
  }
  return <div className="projects-page"><PageHeader eyebrow="Project manager" title="Your projects." description="Local-first project storage with explicit save and recovery states." actions={<><button className="button button-outline" onClick={() => onNavigate('import')}><Upload size={16} />Import</button><button className="button button-primary" onClick={onOpenWorkspace}><Plus size={16} />New project</button></>} /><div className="manager-toolbar"><div className="large-search"><Search size={16} /><input placeholder="Search projects" /><kbd>/</kbd></div><div className="toolbar-select"><span>Sort by</span><select><option>Last modified</option><option>Name</option><option>Object count</option></select><ChevronDown size={14} /></div><IconButton icon={LayoutGrid} label="Grid view" active /><IconButton icon={ListTree} label="List view" /></div><div className="project-list">{loading ? <div className="project-list-empty">Loading local projects</div> : savedProjects.length === 0 ? <div className="project-list-empty">No saved local projects</div> : savedProjects.map((project, index) => { const state = project.readOnly ? 'Read only' : project.dirty ? 'Unsaved' : 'Saved'; return <button className="project-list-row" key={project.documentId} onClick={() => void openSavedProject(project.documentId)}><div className={`list-thumbnail preview-${index % 4}`}><div className="preview-shape" /><div className="preview-grid" /></div><div className="list-main"><strong>{project.label}</strong><span>Local / {project.documentId}</span></div><span className="list-count">{project.objectCount} objects</span><span className="list-modified">{new Date(project.updatedAt).toLocaleDateString()}</span><Badge tone={state === 'Unsaved' ? 'amber' : state === 'Read only' ? 'muted' : 'green'}>{state}</Badge><span className="row-action" title="Open project"><ArrowRight size={16} /></span></button> })}</div><div className="storage-banner"><div className="storage-icon"><HardDrive size={18} /></div><div><strong>Local storage</strong><span>{savedProjects.length} saved project{savedProjects.length === 1 ? '' : 's'} · browser-managed capacity</span></div><button className="text-button" onClick={() => onNavigate('settings')}>Manage storage <ArrowRight size={14} /></button></div></div>
}

function FileFlowPage({ mode, onNavigate, showNotice, facade }: { mode: 'import' | 'export'; onNavigate: (page: Page) => void; showNotice: (message: string) => void; facade: BitBybitWebCadFacade }) {
  const isImport = mode === 'import'
  const [fcstdReport, setFcstdReport] = useState<FcstdInspection | null>(null)
  const [geometryReport, setGeometryReport] = useState<{ format: string; faces: number; edges: number; vertices: number; volume: number } | null>(null)
  const [selectedFormat, setSelectedFormat] = useState<'STEP' | 'IGES' | 'STL' | 'BREP' | 'GLB / GLTF' | 'Web CAD package'>('STEP')
  const [exporting, setExporting] = useState(false)
  const fileInputRef = useRef<HTMLInputElement>(null)
  const inspectFile = async (file: File) => {
    const lowerName = file.name.toLowerCase()
    const extension = lowerName.split('.').pop()
    const geometryFormat = extension === 'step' || extension === 'stp' ? 'step' : extension === 'iges' || extension === 'igs' ? 'iges' : extension === 'brep' || extension === 'brp' ? 'brep' : null
    setGeometryReport(null)
    setFcstdReport(null)
    if (geometryFormat) {
      try {
        const document = facade.app.document.getActive()
        const shape = await facade.geometry.importShape({ format: geometryFormat, text: new TextDecoder().decode(await file.arrayBuffer()), documentId: document.id, documentVersion: document.version })
        try {
          const [topology, massProperties] = await Promise.all([facade.geometry.topology(shape), facade.geometry.massProperties(shape)])
          setGeometryReport({ format: geometryFormat.toUpperCase(), faces: topology.faces.length, edges: topology.edges.length, vertices: topology.vertices.length, volume: massProperties.volume })
          showNotice(`${geometryFormat.toUpperCase()} geometry preview ready`)
        } finally { await facade.geometry.release(shape) }
      } catch (error) { showNotice(`${geometryFormat.toUpperCase()} rejected: ${error instanceof Error ? error.message : String(error)}`) }
      return
    }
    if (!lowerName.endsWith('.fcstd')) { showNotice('Supported import formats: FCStd, STEP, IGES and BREP'); return }
    try {
      const report = facade.project.fcstd.inspect(new Uint8Array(await file.arrayBuffer()))
      setFcstdReport(report)
      showNotice(`FCStd inspected: ${report.compatibility.level}`)
    } catch (error) { setFcstdReport(null); showNotice(`FCStd rejected: ${error instanceof Error ? error.message : String(error)}`) }
  }
  const exportModel = async () => {
    if (!['STEP', 'IGES', 'STL', 'BREP'].includes(selectedFormat)) { showNotice(`${selectedFormat} export is not implemented yet`); return }
    const objectIds = ['fillet', 'chamfer', 'revolution', 'intersection', 'cut', 'union', 'pocket', 'pad', 'box', 'cylinder', 'sphere', 'cone']
    const shape = objectIds.map((objectId) => facade.geometry.getObjectShape(objectId)).find((candidate): candidate is ShapeHandle => Boolean(candidate))
    if (!shape) { showNotice('Recompute a valid Part or Part Design shape before exporting'); return }
    setExporting(true)
    try {
      const result = selectedFormat === 'STEP'
        ? await facade.geometry.exportStep(shape, `${facade.app.document.getActive().label}.step`)
        : selectedFormat === 'IGES'
          ? await facade.geometry.exportIges(shape, `${facade.app.document.getActive().label}.iges`)
          : selectedFormat === 'STL'
            ? await facade.geometry.exportStl(shape, `${facade.app.document.getActive().label}.stl`)
            : await facade.geometry.exportBrep(shape, `${facade.app.document.getActive().label}.brep`)
      const blob = new Blob([result.text], { type: result.mediaType })
      const url = URL.createObjectURL(blob)
      const anchor = document.createElement('a')
      anchor.href = url
      anchor.download = result.fileName
      anchor.click()
      URL.revokeObjectURL(url)
      showNotice(`${result.format.toUpperCase()} export downloaded`)
    } catch (error) {
      showNotice(`Export failed: ${error instanceof Error ? error.message : String(error)}`)
    } finally { setExporting(false) }
  }
  return <div className="flow-page"><PageHeader eyebrow={isImport ? 'Import model' : 'Export project'} title={isImport ? 'Bring a model into your workspace.' : 'Export a clean deliverable.'} description={isImport ? 'Review format, units and compatibility before the document is created.' : 'Choose an exchange format and keep the native project available for future edits.'} onBack={() => onNavigate('start')} /><div className="flow-layout"><div className="flow-steps"><FlowStep index="01" title="Choose file" active={!isImport} complete={isImport} /><FlowStep index="02" title="Review mapping" active={isImport} complete={false} /><FlowStep index="03" title="Confirm" active={false} complete={false} /></div><section className="flow-card panel-surface"><div className="flow-card-header"><div><span className="section-kicker">Step 01 / 03</span><h2>{isImport ? 'Select a CAD file' : 'Select an export format'}</h2></div><Badge tone="cyan">Local only</Badge></div>{isImport ? <><div className="drop-zone"><div className="drop-icon"><Upload size={20} /></div><strong>Drop a model here</strong><span>or browse from your device</span><input ref={fileInputRef} type="file" accept=".FCStd,.fcstd,.step,.stp,.iges,.igs,.brep,.brp" hidden onChange={(event) => { const file = event.target.files?.[0]; if (file) void inspectFile(file); event.currentTarget.value = '' }} /><button className="button button-outline" onClick={() => fileInputRef.current?.click()}><FolderOpen size={15} />Browse files</button><small>STEP · IGES · BREP · FCStd</small></div><div className="flow-note"><AlertTriangle size={15} /><span>Exchange files are previewed through Bitbybit OCCT and released after inspection. FCStd remains a read-only metadata boundary.</span></div>{fcstdReport && <div className="flow-note"><CheckCircle2 size={15} className="icon-green" /><span>{fcstdReport.label} · {fcstdReport.objects.length} objects · {fcstdReport.compatibility.level}; {fcstdReport.compatibility.warnings.join(' ') || 'No compatibility warnings.'}</span></div>}{geometryReport && <div className="flow-note"><CheckCircle2 size={15} className="icon-green" /><span>{geometryReport.format} preview · {geometryReport.faces} faces · {geometryReport.edges} edges · {geometryReport.vertices} vertices · volume {geometryReport.volume.toFixed(4)}</span></div>}</> : <div className="format-grid">{['STEP', 'IGES', 'STL', 'BREP', 'GLB / GLTF', 'Web CAD package'].map((format, index) => <button key={format} className={`format-card ${selectedFormat === format ? 'is-selected' : ''}`} disabled={format === 'GLB / GLTF' || format === 'Web CAD package'} onClick={() => { setSelectedFormat(format as typeof selectedFormat); showNotice(`${format} selected`) }}><span className="format-icon">{index === 5 ? <FileBox size={18} /> : <FileText size={18} />}</span><strong>{format}</strong><small>{index === 0 ? 'Best for editable solids' : index === 1 ? 'Surface exchange' : index === 2 ? 'Mesh export' : index === 3 ? 'Native shape exchange' : 'Not implemented'}</small>{selectedFormat === format && <Check size={15} className="format-check" />}</button>)}</div>}<div className="flow-footer"><button className="button button-quiet" onClick={() => onNavigate('start')}>Cancel</button><button className="button button-primary" disabled={isImport ? false : exporting} onClick={() => { if (isImport) showNotice(fcstdReport || geometryReport ? 'Review complete; document creation remains explicit' : 'Choose a file to review'); else void exportModel() }}><span>{isImport ? 'Continue' : exporting ? 'Exporting' : `Export ${selectedFormat}`}</span>{exporting ? <RefreshCw size={15} /> : <ArrowRight size={15} />}</button></div></section></div></div>
}

function FlowStep({ index, title, active, complete }: { index: string; title: string; active: boolean; complete: boolean }) {
  return <div className={`flow-step ${active ? 'is-active' : ''} ${complete ? 'is-complete' : ''}`}><span className="flow-index">{complete ? <Check size={13} /> : index}</span><span>{title}</span></div>
}

function SettingsPage({ onNavigate, showNotice }: { onNavigate: (page: Page) => void; showNotice: (message: string) => void }) {
  return <div className="settings-page"><PageHeader eyebrow="Preferences" title="Workspace settings." description="Keep the FreeCAD workflow familiar and tune the browser workspace around it." onBack={() => onNavigate('start')} actions={<button className="button button-primary" onClick={() => showNotice('Preferences saved')}><Save size={16} />Save changes</button>} /><div className="settings-layout"><aside className="settings-nav"><button className="is-active"><SlidersHorizontal size={15} />General</button><button><Rotate3D size={15} />Navigation</button><button><Circle size={15} />Units & precision</button><button><Layers3 size={15} />Display</button><button><Command size={15} />Shortcuts</button><button><HardDrive size={15} />Storage</button><button><UserRound size={15} />Privacy & security</button></aside><section className="settings-content panel-surface"><SettingSection title="Document defaults" detail="New documents inherit these values."><SettingRow label="Unit system" detail="Used for new projects and property editors"><select><option>Metric (mm, kg, s)</option><option>Imperial (in, lb, s)</option></select></SettingRow><SettingRow label="Autosave interval" detail="A local snapshot is created after inactivity"><select><option>Every 5 minutes</option><option>Every 10 minutes</option><option>Manual only</option></select></SettingRow></SettingSection><SettingSection title="Viewport" detail="The default FreeCAD navigation preset is kept intact."><SettingRow label="Navigation preset" detail="Orbit, pan and zoom mapping"><select><option>FreeCAD CAD</option><option>Trackpad</option><option>Blender</option></select></SettingRow><SettingRow label="Display mode" detail="Default rendering for precise edges"><select><option>Flat lines</option><option>Shaded</option><option>Wireframe</option></select></SettingRow><SettingRow label="Show selection edges" detail="Highlight selected sub-shapes"><input type="checkbox" defaultChecked /></SettingRow></SettingSection><SettingSection title="Local storage" detail="No design data leaves this browser unless you export or sync it."><SettingRow label="OPFS workspace" detail="3.4 GB of 10 GB used"><span className="storage-meter"><span /></span></SettingRow><SettingRow label="Diagnostic uploads" detail="Share only when you explicitly attach a report"><span className="setting-value">Off</span></SettingRow></SettingSection></section></div></div>
}

function SettingSection({ title, detail, children }: { title: string; detail: string; children: ReactNode }) {
  return <section className="setting-section"><div className="setting-section-heading"><h2>{title}</h2><p>{detail}</p></div>{children}</section>
}

function SettingRow({ label, detail, children }: { label: string; detail: string; children: ReactNode }) {
  return <div className="setting-row"><div><strong>{label}</strong><span>{detail}</span></div><div className="setting-control">{children}</div></div>
}

function HelpPage({ onNavigate, showNotice }: { onNavigate: (page: Page) => void; showNotice: (message: string) => void }) {
  const helpItems = [{ title: 'Create a feature-based part', detail: 'Body → Sketch → Pad → Pocket → Fillet', icon: Box }, { title: 'Understand the model tree', detail: 'Objects, links, Tip and recompute states', icon: ListTree }, { title: 'Navigate the viewport', detail: 'FreeCAD CAD preset and view controls', icon: Rotate3D }, { title: 'Recover a project', detail: 'Snapshots, compatibility and backups', icon: Archive }]
  return <div className="help-page"><PageHeader eyebrow="Help center" title="Find your next command." description="FreeCAD workflow references, keyboard shortcuts and compatibility notes." onBack={() => onNavigate('start')} /><div className="help-search"><Search size={18} /><input placeholder="Search commands, workbenches and help" autoFocus /><kbd>⌘ K</kbd></div><div className="help-grid">{helpItems.map((item) => <button className="help-card panel-surface" key={item.title} onClick={() => showNotice(`${item.title} opened`)}><span className="help-icon"><item.icon size={18} /></span><span><strong>{item.title}</strong><small>{item.detail}</small></span><ArrowRight size={15} /></button>)}</div><section className="shortcut-panel panel-surface"><div className="section-title"><div><span className="section-kicker">Keyboard</span><h3>Core shortcuts</h3></div><button className="text-button" onClick={() => showNotice('Shortcut editor opened')}>Customize <ArrowRight size={14} /></button></div><div className="shortcut-grid"><Shortcut keyName="⌘ K" label="Command search" /><Shortcut keyName="Space" label="Toggle visibility" /><Shortcut keyName="F" label="Fit all" /><Shortcut keyName="Esc" label="Cancel task or selection" /><Shortcut keyName="⌘ Z" label="Undo" /><Shortcut keyName="⌘ S" label="Save project" /></div></section></div>
}

function Shortcut({ keyName, label }: { keyName: string; label: string }) {
  return <div className="shortcut-row"><kbd>{keyName}</kbd><span>{label}</span></div>
}

function DiagnosticsPage({ onNavigate, showNotice, facade }: { onNavigate: (page: Page) => void; showNotice: (message: string) => void; facade: BitBybitWebCadFacade }) {
  const [recovery, setRecovery] = useState<{ integrity: string; lastSavedVersion: number | null; warnings: string[] } | null>(null)
  const [geometry, setGeometry] = useState(() => facade.geometry.capabilities())
  const document = facade.getState().document
  const persistence = facade.project.capabilities()
  const diagnosticTree = facade.diagnostics.tree()
  const geometryTone = geometry.status === 'ready' ? 'green' : geometry.status === 'failed' || geometry.status === 'unavailable' ? 'amber' : 'cyan'
  const geometryValue = geometry.status === 'ready' ? 'Ready' : geometry.status === 'initializing' ? 'Loading' : geometry.status
  useEffect(() => { void facade.project.recovery(facade.getState().document.id).then(setRecovery).catch((error: unknown) => showNotice(`Recovery report unavailable: ${error instanceof Error ? error.message : String(error)}`)) }, [facade, showNotice])
  useEffect(() => {
    let disposed = false
    void facade.geometry.initialize().then((capabilities) => { if (!disposed) setGeometry(capabilities) }).catch((error: unknown) => { if (!disposed) showNotice(`Geometry capability probe failed: ${error instanceof Error ? error.message : String(error)}`) })
    return () => { disposed = true }
  }, [facade, showNotice])
  return <div className="diagnostics-page">
    <PageHeader eyebrow="System status" title="Runtime checks." description="A concise view of browser capabilities, local storage and the current document runtime." onBack={() => onNavigate('start')} actions={<button className="button button-outline" onClick={() => showNotice('Diagnostic package prepared')}><Download size={16} />Export report</button>} />
    <div className="health-grid"><HealthCard label="WebAssembly" value={geometryValue} detail={geometry.reason || 'BitBybit OCCT package loads on demand'} tone={geometryTone} icon={Code2} /><HealthCard label="Local storage" value={persistence.mode === 'sqlite-opfs' ? 'SQLite + OPFS' : persistence.mode === 'sqlite-memory' ? 'Memory fallback' : 'Unavailable'} detail={`Schema v${persistence.schemaVersion || 'n/a'} · ${persistence.crossTabWriteLock || 'local queue'}`} tone={persistence.mode === 'unavailable' ? 'amber' : 'cyan'} icon={HardDrive} /><HealthCard label="Viewport" value="WebGL2" detail="Three.js 0.185.1" tone="cyan" icon={Rotate3D} /><HealthCard label="Document" value={`${document.recompute?.errors.length || 0} warnings`} detail={`${document.label} · v${document.version}`} tone={document.recompute?.errors.length ? 'amber' : 'green'} icon={AlertTriangle} /></div>
    <section className="diagnostic-table panel-surface"><div className="section-title"><div><span className="section-kicker">Runtime</span><h3>Capability checks</h3></div><span className="last-checked">Last checked just now</span></div><DiagnosticRow name="BitBybit Facade" value="Connected" detail="API v0.1 · single public entry" tone="green" /><DiagnosticRow name="Geometry Worker" value={geometryValue} detail={`${geometry.provider} ${geometry.version} · Box, Boolean, feature and export boundaries`} tone={geometryTone} /><DiagnosticRow name="SQLite WASM" value={persistence.sqliteWasm ? 'Worker ready' : 'Memory fallback'} detail={`Schema v${persistence.schemaVersion || 'n/a'} · ${persistence.reason || 'SQLite worker configured'}`} tone={persistence.sqliteWasm ? 'green' : 'cyan'} /><DiagnosticRow name="OPFS" value={persistence.opfs ? 'Available' : 'Fallback'} detail="Cross-origin isolation and browser OPFS capability" tone={persistence.opfs ? 'green' : 'cyan'} /><DiagnosticRow name="Recovery" value={recovery?.integrity || 'Checking'} detail={recovery ? `Last saved v${recovery.lastSavedVersion ?? 'none'}${recovery.warnings.length ? ` · ${recovery.warnings.length} warning(s)` : ''}` : 'Checking SQLite integrity and saved snapshot'} tone={recovery?.integrity === 'ok' ? 'green' : 'cyan'} /><DiagnosticRow name="FreeCAD baseline" value="1.1.1" detail="Compatibility manifest loaded; unsupported commands remain disabled" tone="cyan" /></section>
    <section className="diagnostic-tree-panel panel-surface"><div className="section-title"><div><span className="section-kicker">Document</span><h3>Diagnostic tree</h3></div><strong className={diagnosticTree.length ? 'icon-amber' : 'icon-green'}>{diagnosticTree.length} root causes</strong></div>{diagnosticTree.length === 0 ? <div className="diagnostic-empty"><CheckCircle2 size={16} />No active document diagnostics</div> : <div className="diagnostic-tree">{diagnosticTree.map((node) => <DocumentDiagnosticNode key={node.diagnostic.id} node={node} facade={facade} showNotice={showNotice} />)}</div>}</section>
  </div>
}

function DocumentDiagnosticNode({ node, facade, showNotice, child = false }: { node: DiagnosticTreeNode; facade: BitBybitWebCadFacade; showNotice: (message: string) => void; child?: boolean }) {
  const diagnostic = node.diagnostic
  const topologyRepair = diagnostic.topologyRepair
  const [topologyCandidate, setTopologyCandidate] = useState(topologyRepair?.candidates[0] ?? '')
  const runRepair = (actionId: 'select-object' | 'recompute-root' | 'suppress-root') => {
    void facade.diagnostics.repair(diagnostic.id, actionId).then((result) => showNotice(result.message))
  }
  const replaceTopologyReference = () => {
    if (!topologyRepair || !topologyCandidate) return
    try {
      facade.app.document.resolveTopologyReference({ ...topologyRepair, candidatePersistentId: topologyCandidate })
      void facade.app.document.recomputeAsync().then((result) => showNotice(result.status === 'completed' ? 'Topology reference replaced' : `Reference replaced; recompute ${result.status}`))
    } catch (error) { showNotice(error instanceof Error ? error.message : String(error)) }
  }
  return <div className={`document-diagnostic ${child ? 'is-child' : ''}`}>
    <div className="document-diagnostic-main"><span className={`diagnostic-severity ${diagnostic.severity}`}><AlertTriangle size={14} /></span><div><strong>{diagnostic.code}</strong><span>{diagnostic.message}</span><small>{diagnostic.objectId || diagnostic.source}{diagnostic.dependencyPath && diagnostic.dependencyPath.length > 1 ? ` · ${diagnostic.dependencyPath.join(' → ')}` : ''}{diagnostic.generation ? ` · generation ${diagnostic.generation}` : ''}</small></div></div>
    {diagnostic.repairActions?.length ? <div className="diagnostic-actions">{diagnostic.repairActions.map((action) => <button key={action.id} className="button button-quiet" disabled={!action.enabled} title={action.reason || action.label} onClick={() => runRepair(action.id)}>{action.id === 'select-object' ? <Search size={13} /> : action.id === 'recompute-root' ? <RefreshCw size={13} /> : <Pause size={13} />}{action.label}</button>)}</div> : null}
    {topologyRepair ? <div className="topology-repair"><select className="property-control" aria-label="Replacement subshape" value={topologyCandidate} onChange={(event) => setTopologyCandidate(event.target.value)}>{topologyRepair.candidates.map((candidate) => <option value={candidate} key={candidate}>{candidate}</option>)}</select><button className="button button-quiet" onClick={replaceTopologyReference} disabled={!topologyCandidate}><Check size={13} />Replace reference</button></div> : null}
    {node.children.length > 0 ? <div className="diagnostic-children">{node.children.map((entry) => <DocumentDiagnosticNode key={entry.diagnostic.id} node={entry} facade={facade} showNotice={showNotice} child />)}</div> : null}
  </div>
}

function HealthCard({ label, value, detail, tone, icon: HealthIcon }: { label: string; value: string; detail: string; tone: 'green' | 'cyan' | 'amber'; icon: Icon }) {
  return <div className="health-card panel-surface"><div className={`health-icon ${tone}`}><HealthIcon size={17} /></div><span className="section-kicker">{label}</span><strong>{value}</strong><small>{detail}</small></div>
}

function DiagnosticRow({ name, value, detail, tone, onClick }: { name: string; value: string; detail: string; tone: 'green' | 'cyan' | 'amber'; onClick?: () => void }) {
  return <button className="diagnostic-row" onClick={onClick}><span className="diagnostic-name"><span className={`status-dot ${tone}`} />{name}</span><span className="diagnostic-detail">{detail}</span><strong className={`diagnostic-value ${tone}`}>{value}</strong><ChevronRight size={15} /></button>
}

function SyncPage({ onNavigate, showNotice }: { onNavigate: (page: Page) => void; showNotice: (message: string) => void }) {
  return <div className="sync-page"><PageHeader eyebrow="Account and sync" title="Stay local by default." description="Cloud features are optional. Your current workspace is stored in this browser." onBack={() => onNavigate('start')} /><div className="sync-grid"><section className="sync-card panel-surface"><div className="sync-card-header"><span className="sync-logo"><UserRound size={18} /></span><div><span className="section-kicker">Not signed in</span><h2>Local workspace</h2></div><Badge tone="green">Active</Badge></div><p>Projects, recovery snapshots and cached geometry remain on this device until you explicitly export or sync them.</p><div className="sync-stats"><div><strong>4</strong><span>projects</span></div><div><strong>3.4 GB</strong><span>storage used</span></div><div><strong>Just now</strong><span>last backup</span></div></div><button className="button button-outline" onClick={() => showNotice('Sign-in flow is ready for the next phase')}><UserRound size={16} />Connect an account</button></section><section className="sync-card panel-surface"><div className="section-title"><div><span className="section-kicker">Optional services</span><h3>Choose when to share</h3></div><Settings2 size={16} /></div><SyncRow title="Project sync" detail="Keep a remote copy and resolve conflicts" disabled /><SyncRow title="Team review links" detail="Share read-only documents" disabled /><SyncRow title="Diagnostic uploads" detail="Attach reports only when requested" disabled /></section></div></div>
}

function SyncRow({ title, detail, disabled }: { title: string; detail: string; disabled?: boolean }) {
  return <div className={`sync-row ${disabled ? 'is-disabled' : ''}`}><div><strong>{title}</strong><span>{detail}</span></div><span className="toggle"><span /></span></div>
}

export default App
