'use client'

import {
  AlignCenter,
  AlignHorizontalDistributeCenter,
  AlignLeft,
  AlignRight,
  AlignVerticalDistributeCenter,
  ArrowDownToLine,
  Bold,
  ChevronDown,
  Circle,
  Crosshair,
  FlipHorizontal2,
  FlipVertical2,
  ImagePlus,
  Italic,
  Layers3,
  MousePointer2,
  PaintBucket,
  Pipette,
  Square,
  Star,
  Type,
  FolderOpen,
  Save,
  X,
  ZoomIn,
  ZoomOut,
} from 'lucide-react'
import { useCallback, useEffect, useRef, useState } from 'react'
import type { LucideIcon } from 'lucide-react'
import type { Canvas } from 'fabric'
import { FabricCanvasSurface, exportCanvasImage } from './fabric-canvas-surface'
import type { CanvasElement, CanvasTool, ElementUpdate } from './fabric-canvas-surface'

export type PresetId = 'feed' | 'tiktok' | 'reels' | 'shorts' | 'youtube'
type ElementKind = CanvasElement['kind']
type GuideId = 'thirds' | 'phi' | 'horizon' | 'triangles' | 'center' | 'safe'
type ProjectFile = { format: 'canvasroom-project'; version: 1; name: string; presetId: PresetId; backgroundColor: string; checkerboard: boolean; guides: GuideId[]; elements: CanvasElement[] }

function layerLabel(element: CanvasElement) {
  return element.layerName || (element.kind === 'text' ? element.text?.split('\n')[0] || 'Text' : element.kind === 'star' ? 'Star' : element.kind === 'circle' ? 'Ellipse' : element.kind === 'image' ? element.name || 'Image' : 'Rectangle')
}

function boundsForElement(element: CanvasElement) {
  const lines = element.kind === 'text' ? (element.text ?? '').split('\n') : []
  const width = element.kind === 'text' ? Math.max(element.fontSize ?? 48, ...lines.map((line) => line.length * (element.fontSize ?? 48) * 0.7)) : element.width
  const height = element.kind === 'text' ? (element.fontSize ?? 48) * Math.max(lines.length, 1) : element.height
  const radians = element.rotation * Math.PI / 180
  const rotatedWidth = Math.abs(width * Math.cos(radians)) + Math.abs(height * Math.sin(radians))
  const rotatedHeight = Math.abs(width * Math.sin(radians)) + Math.abs(height * Math.cos(radians))
  return { left: element.x + (width - rotatedWidth) / 2, top: element.y + (height - rotatedHeight) / 2, width: rotatedWidth, height: rotatedHeight }
}

function updateSelectionColor(element: CanvasElement, color: string): CanvasElement {
  return { ...element, color }
}

function updateCanvasRect(element: CanvasElement, updates: ElementUpdate): CanvasElement {
  return { ...element, ...updates }
}


function fileAsDataUrl(file: File): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader()
    reader.onload = () => typeof reader.result === 'string' ? resolve(reader.result) : reject(new Error('Could not read image'))
    reader.onerror = () => reject(reader.error ?? new Error('Could not read image'))
    reader.readAsDataURL(file)
  })
}

function paletteFromColor(hex: string) {
  const channels = hex.match(/[a-f0-9]{2}/gi)?.map((part) => Number.parseInt(part, 16) / 255) ?? [0.5, 0.5, 0.5]
  const max = Math.max(...channels), min = Math.min(...channels), delta = max - min
  let hue = 0
  const lightness = (max + min) / 2
  const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1))
  if (delta) hue = max === channels[0] ? ((channels[1] - channels[2]) / delta) % 6 : max === channels[1] ? (channels[2] - channels[0]) / delta + 2 : (channels[0] - channels[1]) / delta + 4
  hue = (hue * 60 + 360) % 360
  return [0, 35, 180, 205, 320].map((offset) => {
    const h = (hue + offset) % 360
    const a = saturation * Math.min(lightness, 1 - lightness)
    const channel = (n: number) => {
      const k = (n + h / 30) % 12
      const value = lightness - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))
      return Math.round(255 * value).toString(16).padStart(2, '0')
    }
    return `#${channel(0)}${channel(8)}${channel(4)}`.toUpperCase()
  })
}

function PaletteGenerator({ onApply }: { onApply: (color: string) => void }) {
  const [seed, setSeed] = useState('#aeb5bc')
  const palette = paletteFromColor(seed)
  return <section className="sidebar-section palette-generator"><div className="panel-heading"><span className="eyebrow">COLOR PALETTE</span></div><div className="palette-generator-controls"><label className="palette-wheel" title="Pick a palette base color"><input aria-label="Palette base color wheel" type="color" value={seed} onChange={(event) => setSeed(event.target.value)} /></label><span>Harmony</span><button type="button" onClick={() => setSeed(`#${Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0')}`)}>Shuffle</button></div><div className="generated-swatches">{palette.map((color, index) => <button key={`${color}-${index}`} type="button" style={{ background: color }} aria-label={`Apply ${color}`} title={`Apply ${color} to selection`} onClick={() => onApply(color)} />)}</div></section>
}

function downloadFile(blob: Blob, filename: string) {
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = filename
  link.click()
  window.setTimeout(() => URL.revokeObjectURL(url), 1000)
}

const PRESETS: { id: PresetId; label: string; width: number; height: number }[] = [
  { id: 'feed', label: 'Instagram feed', width: 1080, height: 1080 },
  { id: 'tiktok', label: 'TikTok', width: 1080, height: 1920 },
  { id: 'reels', label: 'Instagram Reels', width: 1080, height: 1920 },
  { id: 'shorts', label: 'YouTube Shorts', width: 1080, height: 1920 },
  { id: 'youtube', label: 'YouTube', width: 1920, height: 1080 },
]

const GUIDE_OPTIONS: { id: GuideId; label: string; description: string }[] = [
  { id: 'thirds', label: 'Rule of thirds', description: '3 × 3 composition grid' },
  { id: 'phi', label: 'Phi grid', description: 'Golden ratio divisions' },
  { id: 'horizon', label: 'Horizon / level', description: 'Horizon line and level marks' },
  { id: 'triangles', label: 'Golden triangles', description: 'Diagonal composition guides' },
  { id: 'center', label: 'Center crosshair', description: 'Center axes and intersection' },
  { id: 'safe', label: 'Safe areas', description: 'Format-aware title and action zones' },
]

const INITIAL_ELEMENTS: CanvasElement[] = [
  { id: 'kicker', kind: 'text', x: 130, y: 220, width: 0, height: 0, color: '#b8bfc5', opacity: 100, blur: 0, shadowBlur: 0, shadowOpacity: 0, rotation: 0, text: 'COMPOSITION STUDY  /  001', fontFamily: 'Montserrat', fontSize: 20, align: 'left', bold: true },
  { id: 'headline', kind: 'text', x: 118, y: 340, width: 0, height: 0, color: '#f0f2f3', opacity: 100, blur: 0, shadowBlur: 12, shadowOpacity: 55, rotation: 0, text: 'MAKE\nIT MATTER.', fontFamily: 'Montserrat', fontSize: 104, align: 'left', bold: true },
  { id: 'subhead', kind: 'text', x: 130, y: 800, width: 0, height: 0, color: '#b8bfc5', opacity: 100, blur: 0, shadowBlur: 0, shadowOpacity: 0, rotation: 0, text: 'Ideas worth stopping for.', fontFamily: 'Montserrat', fontSize: 28, align: 'left' },
  { id: 'accent-circle', kind: 'circle', x: 785, y: 135, width: 150, height: 150, color: '#858e96', opacity: 100, blur: 0, shadowBlur: 28, shadowOpacity: 38, rotation: 0 },
]

const FONTS = ['Montserrat', 'Poppins', 'Rubik', 'Gabarito', 'Fira Sans Condensed', 'Bebas Neue', 'Impact', 'Akira Expanded', 'Luckiest Guy', 'Didot', 'Bodoni', 'Cormorant Garamond', 'Creative Vintage', 'Marine Sikona', 'Yamatha']
function createElement(kind: ElementKind, index: number): CanvasElement {
  const offset = (index % 6) * 34
  return {
    id: `${kind}-${Date.now()}-${index}`,
    kind,
    x: 280 + offset,
    y: 340 + offset,
    width: kind === 'text' ? 0 : kind === 'star' ? 210 : 220,
    height: kind === 'text' ? 0 : kind === 'star' ? 200 : 220,
    color: kind === 'text' ? '#f0f2f3' : '#858e96',
    opacity: 100,
    blur: 0,
    shadowBlur: 0,
    shadowOpacity: 35,
    rotation: 0,
    ...(kind === 'text' ? { text: 'Your headline', fontFamily: 'Montserrat', fontSize: 72, align: 'left' as const, bold: true, italic: false, uppercase: false } : {}),
  }
}

function IconButton({ icon: Icon, label, onClick, active = false }: { icon: LucideIcon; label: string; onClick: () => void; active?: boolean }) {
  return <button className={`tool-button${active ? ' is-active' : ''}`} type="button" onClick={onClick} aria-label={label} title={label}><Icon aria-hidden="true" /><span>{label}</span></button>
}

function GuideToggle({ label, description, active, onClick }: { label: string; description: string; active: boolean; onClick: () => void }) {
  return (
    <button type="button" role="switch" aria-checked={active} className={`guide-toggle${active ? ' is-on' : ''}`} onClick={onClick} title={description}>
      <span className="guide-toggle-label">{label}</span><span className="switch-track" aria-hidden="true"><span /></span>
    </button>
  )
}

function ToolPanel({
  elements, selectedIds, guides, primaryColor, secondaryColor, tool, onAdd, onSelect, onToggleGuide, onApplyColor, onPrimaryColor, onSecondaryColor, onToolChange, onRenameLayer,
}: {
  elements: CanvasElement[]
  selectedIds: Set<string>
  guides: Set<GuideId>
  primaryColor: string
  secondaryColor: string
  tool: CanvasTool
  onAdd: (kind: ElementKind) => void
  onSelect: (id: string, additive?: boolean) => void
  onToggleGuide: (id: GuideId) => void
  onApplyColor: (color: string) => void
  onPrimaryColor: (color: string) => void
  onSecondaryColor: (color: string) => void
  onToolChange: (tool: CanvasTool) => void
  onRenameLayer: (id: string, name: string) => void
}) {
  const [editingId, setEditingId] = useState<string | null>(null)
  const [draft, setDraft] = useState('')
  const commitRename = (id: string) => {
    onRenameLayer(id, draft.trim())
    setEditingId(null)
  }
  return (
    <aside className="tool-panel" aria-label="Editor tools and layers">
      <section className="sidebar-section">
        <div className="panel-heading"><span className="eyebrow">ADD ELEMENT</span></div>
        <div className="tool-list">
          <IconButton icon={Type} label="Text" onClick={() => onAdd('text')} active={elements.some((element) => selectedIds.has(element.id) && element.kind === 'text')} />
          <IconButton icon={Square} label="Rectangle" onClick={() => onAdd('square')} />
          <IconButton icon={Circle} label="Ellipse" onClick={() => onAdd('circle')} />
          <IconButton icon={Star} label="Star" onClick={() => onAdd('star')} />
        </div>
      </section>
      <section className="active-colors" aria-label="Active colors and canvas tools">
        <label className="active-color-swatch"><span>Primary</span><i style={{ backgroundColor: primaryColor }} /><input type="color" aria-label="Primary color" value={primaryColor} onChange={(event) => onPrimaryColor(event.target.value.toUpperCase())} /></label>
        <label className="active-color-swatch"><span>Secondary</span><i style={{ backgroundColor: secondaryColor }} /><input type="color" aria-label="Secondary color" value={secondaryColor} onChange={(event) => onSecondaryColor(event.target.value.toUpperCase())} /></label>
        <button type="button" className={`mini-tool${tool === 'eyedropper' ? ' is-active' : ''}`} aria-label="Eyedropper" aria-pressed={tool === 'eyedropper'} title="Pick a color from the canvas" onClick={() => onToolChange(tool === 'eyedropper' ? 'select' : 'eyedropper')}><Pipette aria-hidden="true" /></button>
        <button type="button" className={`mini-tool${tool === 'bucket' ? ' is-active' : ''}`} aria-label="Paint bucket" aria-pressed={tool === 'bucket'} title="Apply the primary color to the selected layer" onClick={() => { if (tool === 'bucket') { onToolChange('select'); return } onApplyColor(primaryColor); onToolChange('bucket') }}><PaintBucket aria-hidden="true" /></button>
      </section>
      <section className="sidebar-section layers-section">
        <div className="panel-heading section-heading"><span className="eyebrow">LAYERS</span><span className="count-badge">{elements.length}</span></div>
        <p className="layers-hint">Shift-click or drag to select · Delete removes</p>
        <ul className="layer-list" aria-label="Layers">
          {[...elements].reverse().map((element) => {
            const id = element.id
            return <li key={id}>
              <div className={`layer-row${selectedIds.has(id) ? ' is-selected' : ''}`}>
                <button type="button" aria-label={`Select ${layerLabel(element)} layer`} aria-pressed={selectedIds.has(id)} className="layer-select-button" onClick={(event) => onSelect(id, event.metaKey || event.ctrlKey || event.shiftKey)}>
                  <span className="layer-kind-icon">{element.kind === 'text' ? <Type /> : element.kind === 'circle' ? <Circle /> : element.kind === 'star' ? <Star /> : element.kind === 'image' ? <ImagePlus /> : <Square />}</span>
                </button>
                {editingId === id ? <input className="layer-name-input" aria-label={`Rename ${layerLabel(element)} layer`} value={draft} autoFocus onChange={(event) => setDraft(event.target.value)} onBlur={() => commitRename(id)} onKeyDown={(event) => {
                  if (event.key === 'Enter' && (event.nativeEvent.isComposing || event.keyCode === 229)) return
                  if (event.key === 'Enter') { event.preventDefault(); commitRename(id) }
                  if (event.key === 'Escape') { event.preventDefault(); setEditingId(null) }
                }} /> : <button type="button" className="layer-name-button" aria-label={`Rename layer ${layerLabel(element)}`} onClick={() => { setDraft(layerLabel(element)); setEditingId(id) }}>{layerLabel(element)}</button>}
                {element.groupId && <span className="layer-group-mark" title="Grouped layer">G</span>}
                <button type="button" className="layer-index-button" aria-label={`Select layer ${elements.indexOf(element) + 1}`} onClick={(event) => onSelect(id, event.metaKey || event.ctrlKey || event.shiftKey)}>{String(elements.indexOf(element) + 1).padStart(2, '0')}</button>
              </div>
            </li>
          })}
          <li><button type="button" aria-pressed={selectedIds.has('background')} className={`layer-row background-layer${selectedIds.has('background') ? ' is-selected' : ''}`} onClick={() => onSelect('background')}>
            <span className="layer-kind-icon background-chip" /><span className="layer-name">Background</span><span className="layer-index">BG</span>
          </button></li>
        </ul>
      </section>
      <section className="sidebar-section guides-section">
        <div className="panel-heading"><span className="eyebrow">COMPOSITION GUIDES</span><span className="panel-heading-note">Overlays · excluded from export</span></div>
        <div className="guide-list">
          {GUIDE_OPTIONS.map((guide) => <GuideToggle key={guide.id} label={guide.label} description={guide.description} active={guides.has(guide.id)} onClick={() => onToggleGuide(guide.id)} />)}
        </div>
      </section>
      <PaletteGenerator onApply={onApplyColor} />
      <div className="sidebar-footer"><Crosshair aria-hidden="true" /><span>Guides are non-destructive</span></div>
    </aside>
  )
}

function ColorControl({ color, label, onChange }: { color: string; label: string; onChange: (value: string) => void }) {
  const [draft, setDraft] = useState(color)
  useEffect(() => setDraft(color), [color])
  const handleTextChange = (value: string) => {
    const normalized = value.startsWith('#') ? value : `#${value}`
    setDraft(normalized)
    if (/^#[0-9a-fA-F]{6}$/.test(normalized)) onChange(normalized.toUpperCase())
  }
  return (
    <div className="color-control-row">
      {/* The preview browser adds an automation attribute to color labels before hydration. */}
      <label className="color-input-wrap" aria-label={`Choose ${label}`} suppressHydrationWarning><input type="color" value={color} onChange={(event) => onChange(event.target.value.toUpperCase())} /></label>
      <input className="hex-input" aria-label={`${label} hex color`} value={draft} maxLength={7} spellCheck={false} onChange={(event) => handleTextChange(event.target.value)} onBlur={() => setDraft(color)} />
    </div>
  )
}

function StackControls({ onAction }: { onAction: (action: 'front' | 'forward' | 'backward' | 'back') => void }) {
  return (
    <section className="property-section">
      <div className="section-title"><h3>Layer order</h3><Layers3 aria-hidden="true" /></div>
      <div className="stack-grid">
        <button type="button" onClick={() => onAction('front')}>Bring to front</button>
        <button type="button" onClick={() => onAction('forward')}>Bring forward</button>
        <button type="button" onClick={() => onAction('backward')}>Send backward</button>
        <button type="button" onClick={() => onAction('back')}>Send to back</button>
      </div>
    </section>
  )
}

function PropertyPanel({
  selected, backgroundSelected, backgroundColor, onUpdate, onUpdateBackground, onDelete, onStack,
}: {
  selected: CanvasElement | null
  backgroundSelected: boolean
  backgroundColor: string
  onUpdate: (updates: ElementUpdate) => void
  onUpdateBackground: (color: string) => void
  onDelete: () => void
  onStack: (action: 'front' | 'forward' | 'backward' | 'back') => void
}) {
  if (!selected && !backgroundSelected) {
    return <aside className="properties-panel" aria-label="Properties"><div className="properties-title-row"><span className="eyebrow">INSPECTOR</span></div><div className="property-empty-state"><div className="empty-icon"><MousePointer2 aria-hidden="true" /></div><strong>No selection</strong><span>Select a layer or the canvas background to inspect its settings.</span></div></aside>
  }
  if (backgroundSelected) {
    return (
      <aside className="properties-panel" aria-label="Background properties">
        <div className="properties-title-row"><div><span className="eyebrow">INSPECTOR</span><h2>Canvas background</h2></div></div>
        <section className="property-section"><div className="section-title"><h3>Fill</h3></div><ColorControl color={backgroundColor} label="background" onChange={onUpdateBackground} /></section>
        <div className="inspector-note">Canvas color updates instantly. Uploaded images cover the canvas color.</div>
      </aside>
    )
  }
  if (!selected) return null
  const isText = selected.kind === 'text'
  const updateNumber = (key: keyof CanvasElement, value: string) => onUpdate({ [key]: Number(value) })
  const title = isText ? 'Text layer' : selected.kind === 'image' ? selected.name || 'Image layer' : selected.kind === 'star' ? 'Star layer' : selected.kind === 'circle' ? 'Ellipse layer' : 'Rectangle layer'
  return (
    <aside className="properties-panel" aria-label="Properties">
      <div className="properties-title-row"><div><span className="eyebrow">INSPECTOR</span><h2>{title}</h2></div><button className="delete-layer" type="button" onClick={onDelete} aria-label="Delete selected layer" title="Delete layer">×</button></div>
      {isText && <section className="property-section">
        <label className="control-label" htmlFor="layer-copy">Text</label>
        <textarea id="layer-copy" className="text-content-input" rows={3} value={selected.text ?? ''} onChange={(event) => onUpdate({ text: event.target.value })} />
        <div className="control-label-row"><label className="control-label" htmlFor="font-family">Typeface</label></div>
        <select id="font-family" className="dark-select" value={selected.fontFamily} onChange={(event) => onUpdate({ fontFamily: event.target.value })}>{FONTS.map((font) => <option value={font} key={font}>{font}</option>)}</select>
        <div className="control-label-row size-label-row"><label className="control-label" htmlFor="font-size">Size</label><span className="value-label">{selected.fontSize}px</span></div>
        <input id="font-size" className="range-control" type="range" min="12" max="320" value={selected.fontSize} onChange={(event) => updateNumber('fontSize', event.target.value)} />
        <div className="format-row">
          <div className="format-group" aria-label="Text formatting"><button className={`format-button${selected.bold ? ' selected' : ''}`} type="button" aria-label="Bold" aria-pressed={Boolean(selected.bold)} onClick={() => onUpdate({ bold: !selected.bold })}><Bold /></button><button className={`format-button${selected.italic ? ' selected' : ''}`} type="button" aria-label="Italic" aria-pressed={Boolean(selected.italic)} onClick={() => onUpdate({ italic: !selected.italic })}><Italic /></button><button className={`format-button${selected.uppercase ? ' selected' : ''}`} type="button" aria-label="Uppercase" aria-pressed={Boolean(selected.uppercase)} onClick={() => onUpdate({ uppercase: !selected.uppercase })}><span className="uppercase-mark">AA</span></button></div>
          <div className="format-group align-group" aria-label="Text alignment">{([['left', AlignLeft], ['center', AlignCenter], ['right', AlignRight]] as const).map(([align, Icon]) => <button className={`format-button${selected.align === align ? ' selected' : ''}`} type="button" aria-label={`Align ${align}`} aria-pressed={selected.align === align} key={align} onClick={() => onUpdate({ align })}><Icon /></button>)}</div>
        </div>
      </section>}
      {isText && <section className="property-section highlight-section">
          <div className="section-title"><h3>Highlights</h3><label className="highlight-toggle"><input type="checkbox" checked={Boolean(selected.highlight)} onChange={(event) => onUpdate({ highlight: event.target.checked })} /><span>Background</span></label></div>
          <div className="highlight-color-row"><label htmlFor="highlight-color">Highlight</label><input id="highlight-color" type="color" value={selected.highlightColor ?? '#f0f2f3'} onChange={(event) => onUpdate({ highlightColor: event.target.value })} /></div>
          <div className="highlight-color-row"><label htmlFor="outline-color">Outline</label><input id="outline-color" type="color" value={selected.outlineColor ?? '#111111'} onChange={(event) => onUpdate({ outlineColor: event.target.value })} /></div>
          <div className="control-label-row"><label className="control-label" htmlFor="outline-width">Outline width</label><span className="value-label">{selected.outlineWidth ?? 0}px</span></div>
          <input id="outline-width" className="range-control" type="range" min="0" max="24" value={selected.outlineWidth ?? 0} onChange={(event) => updateNumber('outlineWidth', event.target.value)} />
      </section>}
      <section className="property-section color-section"><div className="section-title"><h3>{isText ? 'Text color' : 'Fill color'}</h3></div><ColorControl color={selected.color} label="element" onChange={(color) => onUpdate({ color })} /></section>
      <section className="property-section">
        <div className="control-label-row"><label className="control-label" htmlFor="element-opacity">Opacity</label><span className="value-label">{selected.opacity}%</span></div>
        <input id="element-opacity" className="range-control" type="range" min="0" max="100" value={selected.opacity} onChange={(event) => updateNumber('opacity', event.target.value)} />
      </section>
      <section className="property-section">
        <div className="control-label-row"><label className="control-label" htmlFor="element-blur">Blur</label><span className="value-label">{selected.blur}px</span></div>
        <input id="element-blur" className="range-control" type="range" min="0" max="50" value={selected.blur} onChange={(event) => updateNumber('blur', event.target.value)} />
      </section>
      <section className="property-section shadow-section">
        <div className="section-title"><h3>Drop shadow</h3><span className="shadow-indicator">{selected.shadowBlur > 0 && selected.shadowOpacity > 0 ? 'ON' : 'OFF'}</span></div>
        <div className="control-label-row"><label className="control-label" htmlFor="shadow-blur">Softness</label><span className="value-label">{selected.shadowBlur}px</span></div>
        <input id="shadow-blur" className="range-control" type="range" min="0" max="100" value={selected.shadowBlur} onChange={(event) => updateNumber('shadowBlur', event.target.value)} />
        <div className="control-label-row shadow-opacity-label"><label className="control-label" htmlFor="shadow-opacity">Opacity</label><span className="value-label">{selected.shadowOpacity}%</span></div>
        <input id="shadow-opacity" className="range-control" type="range" min="0" max="100" value={selected.shadowOpacity} onChange={(event) => updateNumber('shadowOpacity', event.target.value)} />
      </section>
      <section className="property-section"><div className="control-label-row"><label className="control-label" htmlFor="element-rotation">Rotation</label><span className="value-label">{Math.round(selected.rotation)}°</span></div><input id="element-rotation" className="range-control" type="range" min="-180" max="180" value={selected.rotation} onChange={(event) => updateNumber('rotation', event.target.value)} /><div className="flip-controls"><button type="button" className={`format-button${selected.flipX ? ' selected' : ''}`} aria-pressed={Boolean(selected.flipX)} onClick={() => onUpdate({ flipX: !selected.flipX })}><FlipHorizontal2 aria-hidden="true" />Flip horizontal</button><button type="button" className={`format-button${selected.flipY ? ' selected' : ''}`} aria-pressed={Boolean(selected.flipY)} onClick={() => onUpdate({ flipY: !selected.flipY })}><FlipVertical2 aria-hidden="true" />Flip vertical</button></div>{selected.kind === 'image' && <p className="inspector-note image-tint-note">PNG tint follows the layer fill color.</p>}</section>
      <StackControls onAction={onStack} />
    </aside>
  )
}

export default function SocialImageEditor() {
  const [presetId, setPresetId] = useState<PresetId>('feed')
  const [elements, setElements] = useState<CanvasElement[]>(INITIAL_ELEMENTS)
  const [selectedId, setSelectedId] = useState<string | null>('headline')
  const [selectedIds, setSelectedIds] = useState<Set<string>>(() => new Set(['headline']))
  const [backgroundColor, setBackgroundColor] = useState('#22272b')
  const [checkerboard, setCheckerboard] = useState(true)
  const [guides, setGuides] = useState<Set<GuideId>>(() => new Set())
  const [exportFormat, setExportFormat] = useState<'png' | 'jpg'>('png')
  const [exporting, setExporting] = useState(false)
  const [exportError, setExportError] = useState(false)
  const [projectName, setProjectName] = useState('Untitled composition')
  const [dialog, setDialog] = useState<'project' | 'export' | null>(null)
  const [dialogMessage, setDialogMessage] = useState('')
  const [plannerSaving, setPlannerSaving] = useState(false)
  const [primaryColor, setPrimaryColor] = useState('#aeb5bc')
  const [secondaryColor, setSecondaryColor] = useState('#22272b')
  const [tool, setTool] = useState<CanvasTool>('select')
  const [zoom, setZoom] = useState(1)
  const canvasRef = useRef<Canvas | null>(null)
  const uploadRef = useRef<HTMLInputElement>(null)
  const projectRef = useRef<HTMLInputElement>(null)
  const preset = PRESETS.find((item) => item.id === presetId) ?? PRESETS[0]
  const selected = elements.find((element) => element.id === selectedId) ?? null
  const selectedElements = elements.filter((element) => selectedIds.has(element.id))
  const backgroundSelected = selectedIds.has('background')
  const changeZoom = useCallback((value: number) => setZoom(value), [])
  const applyPaintColor = useCallback((color = primaryColor, targetId?: string) => {
    setPrimaryColor(color)
    setElements((current) => {
      const target = targetId ? current.find((element) => element.id === targetId) : undefined
      const targetIds = target?.groupId ? new Set(current.filter((element) => element.groupId === target.groupId).map((element) => element.id)) : target ? new Set([target.id]) : selectedIds
      return current.map((element) => targetIds.has(element.id) ? updateSelectionColor(element, color) : element)
    })
  }, [primaryColor, selectedIds])
  const applyFabricUpdates = useCallback((updates: Array<{ id: string; updates: ElementUpdate }>) => {
    const byId = new Map(updates.map(({ id, updates: value }) => [id, value]))
    setElements((current) => current.map((element) => byId.has(element.id) ? updateCanvasRect(element, byId.get(element.id) ?? {}) : element))
  }, [])
  const handleCanvasSelection = useCallback((ids: Set<string>, preferredId: string | null) => {
    setSelectedIds(ids)
    setSelectedId(preferredId)
  }, [])
  const selectCanvasBackground = useCallback(() => {
    setSelectedId('background')
    setSelectedIds(new Set(['background']))
  }, [])
  const pickCanvasColor = useCallback((color: string) => {
    setPrimaryColor(color)
    setTool('select')
  }, [])
  const selectLayer = (id: string, additive = false) => {
    const target = elements.find((item) => item.id === id)
    const groupIds = target?.groupId ? elements.filter((item) => item.groupId === target.groupId).map((item) => item.id) : [id]
    if (additive && id !== 'background') {
      const next = new Set(selectedIds)
      next.delete('background')
      const allAlreadySelected = groupIds.every((groupId) => next.has(groupId))
      groupIds.forEach((groupId) => allAlreadySelected ? next.delete(groupId) : next.add(groupId))
      setSelectedIds(next)
      setSelectedId(next.has(id) ? id : Array.from(next).at(-1) ?? null)
      return
    }
    setSelectedIds(new Set(groupIds))
    setSelectedId(id)
  }
  const clearCanvas = () => {
    setElements([])
    setSelectedId(null)
    setSelectedIds(new Set())
    setGuides(new Set())
    setBackgroundColor('#ffffff')
    setCheckerboard(true)
    setProjectName('Untitled composition')
  }
  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      const target = event.target as HTMLElement | null
      const editing = target?.matches('input, textarea, select, [contenteditable="true"]')
      if (event.isComposing) return
      if (event.key === 'Escape') {
        event.preventDefault()
        if (dialog) { setDialog(null); return }
        clearCanvas()
        return
      }
      if (!editing && (event.key === '+' || event.key === '=' || event.key === '-')) {
        event.preventDefault()
        setZoom((current) => Math.max(0.5, Math.min(2.5, Number((current * (event.key === '-' ? 0.9 : 1.1)).toFixed(2)))))
        return
      }
      if (editing || (event.key !== 'Delete' && event.key !== 'Backspace')) return
      if (selectedIds.size === 0 || (selectedIds.size === 1 && selectedIds.has('background'))) return
      event.preventDefault()
      setElements((current) => current.filter((element) => !selectedIds.has(element.id)))
      setSelectedIds(new Set())
      setSelectedId(null)
    }
    window.addEventListener('keydown', handleKeyDown)
    return () => window.removeEventListener('keydown', handleKeyDown)
  }, [selectedIds, dialog])

  const updateElement = useCallback((id: string, updates: ElementUpdate) => {
    setElements((current) => current.map((element) => element.id === id ? { ...element, ...updates } : element))
  }, [])
  const applyPaletteColor = (color: string) => applyPaintColor(color)
  const renameLayer = (id: string, name: string) => setElements((current) => current.map((element) => element.id === id ? { ...element, layerName: name || undefined } : element))
  const updateBackgroundColor = (color: string) => { setBackgroundColor(color); setCheckerboard(false) }
  const addElement = (kind: ElementKind) => {
    const element = createElement(kind, elements.length)
    setElements((current) => [...current, element])
    setSelectedId(element.id)
    setSelectedIds(new Set([element.id]))
  }
  const toggleGuide = (id: GuideId) => setGuides((current) => {
    const next = new Set(current)
    if (next.has(id)) next.delete(id)
    else next.add(id)
    return next
  })
  const changeStack = (action: 'front' | 'forward' | 'backward' | 'back') => {
    if (!selected) return
    setElements((current) => {
      const moving = current.filter((element) => selectedIds.has(element.id))
      if (!moving.length) return current
      const rest = current.filter((element) => !selectedIds.has(element.id))
      const from = current.findIndex((element) => selectedIds.has(element.id))
      const target = action === 'front' ? rest.length : action === 'back' ? 0 : action === 'forward' ? Math.min(from + 1, rest.length) : Math.max(from - 1, 0)
      rest.splice(target, 0, ...moving)
      return rest
    })
  }
  const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(event.target.files ?? []).filter((file) => ['image/png', 'image/jpeg'].includes(file.type))
    event.target.value = ''
    if (!files.length) return
    try {
      const created = await Promise.all(files.map(async (file, index) => {
        const src = await fileAsDataUrl(file)
        const dimensions = await new Promise<{ width: number; height: number }>((resolve, reject) => {
          const image = new Image()
          image.onload = () => resolve({ width: image.naturalWidth, height: image.naturalHeight })
          image.onerror = () => reject(new Error('Invalid image file'))
          image.src = src
        })
        const width = Math.min(preset.width * 0.68, dimensions.width)
        const height = width * dimensions.height / dimensions.width
        const maxHeight = preset.height * 0.68
        const scale = Math.min(1, maxHeight / Math.max(height, 1))
        const finalWidth = width * scale
        const finalHeight = height * scale
        return {
          id: `image-${Date.now()}-${index}-${Math.random().toString(36).slice(2, 7)}`,
          kind: 'image' as const, x: (preset.width - finalWidth) / 2, y: (preset.height - finalHeight) / 2,
          width: finalWidth, height: finalHeight, color: '#ffffff', opacity: 100, blur: 0, shadowBlur: 0, shadowOpacity: 0,
          rotation: 0, src, name: file.name,
        }
      }))
      setElements((current) => [...current, ...created])
      setSelectedIds(new Set(created.map((element) => element.id)))
      setSelectedId(created.at(-1)?.id ?? null)
    } catch {
      setExportError(true)
      window.setTimeout(() => setExportError(false), 3200)
    }
  }
  const saveProject = () => {
    const project: ProjectFile = { format: 'canvasroom-project', version: 1, name: projectName.trim() || 'Untitled composition', presetId, backgroundColor, checkerboard, guides: [...guides], elements }
    downloadFile(new Blob([JSON.stringify(project, null, 2)], { type: 'application/json' }), `${project.name.replace(/[^a-z0-9-_ ]/gi, '').trim().replace(/\s+/g, '-') || 'composition'}.canvasroom.json`)
    setDialogMessage('Editable project downloaded. Image layers are embedded and can be reopened from this editor.')
  }
  const handleOpenProject = async (event: React.ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0]
    event.target.value = ''
    if (!file) return
    try {
      const project = JSON.parse(await file.text()) as ProjectFile
      const validPreset = PRESETS.some(({ id }) => id === project.presetId)
      const validKinds = new Set(['text', 'square', 'circle', 'star', 'image'])
      const validElements = Array.isArray(project.elements) && project.elements.every((element) => element && typeof element.id === 'string' && validKinds.has(element.kind) && Number.isFinite(element.x) && Number.isFinite(element.y) && Number.isFinite(element.rotation) && Number.isFinite(element.width) && Number.isFinite(element.height) && (element.kind !== 'image' || typeof element.src === 'string' && /^data:image\/(png|jpeg);base64,/i.test(element.src)))
      if (project.format !== 'canvasroom-project' || project.version !== 1 || !validPreset || !validElements || typeof project.backgroundColor !== 'string') throw new Error('Invalid project file')
      setPresetId(project.presetId)
      setProjectName(typeof project.name === 'string' ? project.name : 'Untitled composition')
      setBackgroundColor(project.backgroundColor)
      setCheckerboard(Boolean(project.checkerboard))
      setGuides(new Set((project.guides ?? []).filter((guide): guide is GuideId => GUIDE_OPTIONS.some((option) => option.id === guide))))
      setElements(project.elements)
      setSelectedIds(new Set())
      setSelectedId(null)
      setDialogMessage('Project opened successfully.')
    } catch {
      setDialogMessage('This file is not a valid Canvasroom project.')
    }
  }
  const groupSelection = () => {
    if (selectedElements.length < 2) return
    const groupId = `group-${Date.now()}`
    setElements((current) => current.map((element) => selectedIds.has(element.id) ? { ...element, groupId } : element))
  }
  const ungroupSelection = () => {
    const groups = new Set(selectedElements.map((element) => element.groupId).filter(Boolean))
    setElements((current) => current.map((element) => selectedIds.has(element.id) || (element.groupId && groups.has(element.groupId)) ? { ...element, groupId: undefined } : element))
  }
  const alignSelection = (alignment: 'left' | 'center-x' | 'right' | 'top' | 'center-y' | 'bottom') => {
    if (selectedElements.length < 2) return
    const bounds = selectedElements.map((element) => ({ element, rect: boundsForElement(element) }))
    const left = Math.min(...bounds.map(({ rect }) => rect.left))
    const top = Math.min(...bounds.map(({ rect }) => rect.top))
    const right = Math.max(...bounds.map(({ rect }) => rect.left + rect.width))
    const bottom = Math.max(...bounds.map(({ rect }) => rect.top + rect.height))
    setElements((current) => current.map((element) => {
      const item = bounds.find(({ element: candidate }) => candidate.id === element.id)
      if (!item) return element
      if (alignment === 'left') return { ...element, x: element.x + left - item.rect.left }
      if (alignment === 'center-x') return { ...element, x: element.x + (left + right - item.rect.width) / 2 - item.rect.left }
      if (alignment === 'right') return { ...element, x: element.x + right - item.rect.width - item.rect.left }
      if (alignment === 'top') return { ...element, y: element.y + top - item.rect.top }
      if (alignment === 'center-y') return { ...element, y: element.y + (top + bottom - item.rect.height) / 2 - item.rect.top }
      return { ...element, y: element.y + bottom - item.rect.height - item.rect.top }
    }))
  }
  const distributeSelection = (axis: 'horizontal' | 'vertical') => {
    if (selectedElements.length < 3) return
    const horizontal = axis === 'horizontal'
    const ordered = selectedElements.map((element) => ({ element, rect: boundsForElement(element) })).sort((a, b) => horizontal ? a.rect.left - b.rect.left : a.rect.top - b.rect.top)
    const first = ordered[0].rect
    const last = ordered[ordered.length - 1].rect
    const start = horizontal ? first.left : first.top
    const end = horizontal ? last.left + last.width : last.top + last.height
    const totalSize = ordered.reduce((sum, item) => sum + (horizontal ? item.rect.width : item.rect.height), 0)
    const gap = (end - start - totalSize) / (ordered.length - 1)
    let cursor = start
    const positions = new Map<string, number>()
    ordered.forEach(({ element, rect }) => {
      const position = horizontal ? rect.left : rect.top
      positions.set(element.id, horizontal ? element.x + cursor - position : element.y + cursor - position)
      cursor += (horizontal ? rect.width : rect.height) + gap
    })
    setElements((current) => current.map((element) => {
      const value = positions.get(element.id)
      if (value === undefined) return element
      return horizontal ? { ...element, x: value } : { ...element, y: value }
    }))
  }

  const renderCanvas = async () => {
    const canvas = canvasRef.current
    if (!canvas) throw new Error('Canvas is still loading')
    await document.fonts.ready
    await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
    const marginX = Math.round(preset.width * 0.18)
    const marginY = Math.round(preset.height * 0.18)
    const exportBackground = checkerboard ? (exportFormat === 'jpg' ? '#ffffff' : 'transparent') : backgroundColor
    const previousBackground = canvas.backgroundColor
    const previousTransform = [...canvas.viewportTransform] as [number, number, number, number, number, number]
    try {
      canvas.setViewportTransform([1, 0, 0, 1, 0, 0])
      canvas.backgroundColor = exportBackground
      canvas.requestRenderAll()
      await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))
      return exportCanvasImage(canvas, { left: marginX, top: marginY, width: preset.width, height: preset.height, format: exportFormat === 'jpg' ? 'jpeg' : 'png', quality: 0.94 })
    } finally {
      canvas.backgroundColor = previousBackground
      canvas.setViewportTransform(previousTransform)
      canvas.requestRenderAll()
    }
  }
  const exportCanvas = async () => {
    if (exporting) return
    setExporting(true)
    try {
      const output = await renderCanvas()
      const response = await fetch(output)
      downloadFile(await response.blob(), `composition-${preset.id}.${exportFormat}`)
      setDialog(null)
      setDialogMessage('Image downloaded.')
    } catch {
      setExportError(true)
      window.setTimeout(() => setExportError(false), 3200)
    } finally {
      setExporting(false)
    }
  }
  const saveToPlanner = async () => {
    if (plannerSaving) return
    setPlannerSaving(true)
    setDialogMessage('Rendering the image and preparing the Planner request…')
    try {
      const image = await fetch(await renderCanvas()).then((response) => response.blob())
      const payload = new FormData()
      payload.append('image', image, `composition-${preset.id}.${exportFormat}`)
      payload.append('project', JSON.stringify({ name: projectName, presetId, elements, backgroundColor, checkerboard }))
      const response = await fetch('/api/planner/save', { method: 'POST', body: payload })
      const result = await response.json() as { message?: string }
      setDialogMessage(result.message ?? (response.ok ? 'Saved to Planner.' : 'Planner could not save this image.'))
    } catch {
      setDialogMessage('The Planner save request could not be completed. The image and editable project are ready to send when a Planner endpoint is connected.')
    } finally {
      setPlannerSaving(false)
    }
  }

  return (
    <main className="editor-app">
      <header className="topbar">
        <div className="workspace-title"><span className="workspace-indicator" /><div><strong>Image Editor</strong><span>COMPOSITION WORKSPACE</span></div></div>
        <div className="document-status"><span className="status-square" /><input className="project-name-input" aria-label="Project name" value={projectName} onChange={(event) => setProjectName(event.target.value)} /><span className="saved-label">Editable project</span></div>
        <div className="topbar-actions">
          <button className="project-action" type="button" onClick={() => { setDialog('project'); setDialogMessage('') }}><Save aria-hidden="true" />Save</button>
          <button className="project-action" type="button" onClick={() => projectRef.current?.click()}><FolderOpen aria-hidden="true" />Open</button>
          <label className="size-caption" htmlFor="canvas-preset">FORMAT</label>
          <div className="preset-select-wrap"><select id="canvas-preset" aria-label="Canvas format" className="preset-select" value={presetId} onChange={(event) => setPresetId(event.target.value as PresetId)}>{PRESETS.map(({ id, label, width, height }) => <option key={id} value={id}>{label} · {width} × {height}</option>)}</select><ChevronDown aria-hidden="true" /></div>
          <div className="export-control"><div className="format-switch" role="group" aria-label="Export format"><button className={exportFormat === 'png' ? 'selected' : ''} type="button" onClick={() => setExportFormat('png')} aria-pressed={exportFormat === 'png'}>PNG</button><button className={exportFormat === 'jpg' ? 'selected' : ''} type="button" onClick={() => setExportFormat('jpg')} aria-pressed={exportFormat === 'jpg'}>JPG</button></div><button className="export-button" type="button" onClick={() => { setDialog('export'); setDialogMessage('') }} disabled={exporting}><ArrowDownToLine aria-hidden="true" />{exporting ? 'Rendering…' : 'Export'}</button></div>
        </div>
      </header>
      <div className="editor-workspace">
        <ToolPanel elements={elements} selectedIds={selectedIds} guides={guides} primaryColor={primaryColor} secondaryColor={secondaryColor} tool={tool} onAdd={addElement} onSelect={selectLayer} onToggleGuide={toggleGuide} onApplyColor={applyPaletteColor} onPrimaryColor={setPrimaryColor} onSecondaryColor={setSecondaryColor} onToolChange={setTool} onRenameLayer={renameLayer} />
        <section className="canvas-workspace" aria-label="Composition canvas workspace">
          <div className="canvas-toolbar">
            <div className="canvas-tool-active"><MousePointer2 aria-hidden="true" /><span>{tool === 'eyedropper' ? 'Eyedropper' : tool === 'bucket' ? 'Paint bucket' : 'Selection'}</span><kbd>{tool === 'eyedropper' ? 'I' : tool === 'bucket' ? 'G' : 'V'}</kbd></div>
            {selectedElements.length > 1 && <div className="selection-actions" aria-label="Selection actions">
              <button type="button" onClick={groupSelection} disabled={selectedElements.length < 2}>Group</button>
              <button type="button" onClick={ungroupSelection} disabled={!selectedElements.some((element) => element.groupId)}>Ungroup</button>
              <span className="align-action-label">Align</span>
              {(['left', 'center-x', 'right', 'top', 'center-y', 'bottom'] as const).map((alignment) => <button key={alignment} type="button" onClick={() => alignSelection(alignment)} aria-label={`Align ${alignment}`} title={`Align ${alignment}`}>{alignment === 'left' ? 'L' : alignment === 'center-x' ? '↔' : alignment === 'right' ? 'R' : alignment === 'top' ? 'T' : alignment === 'center-y' ? '↕' : 'B'}</button>)}
              <button type="button" onClick={() => distributeSelection('horizontal')} disabled={selectedElements.length < 3} aria-label="Distribute horizontally" title="Distribute horizontally"><AlignHorizontalDistributeCenter aria-hidden="true" /></button>
              <button type="button" onClick={() => distributeSelection('vertical')} disabled={selectedElements.length < 3} aria-label="Distribute vertically" title="Distribute vertically"><AlignVerticalDistributeCenter aria-hidden="true" /></button>
            </div>}
            <span className="canvas-dimensions">{preset.width} × {preset.height} px</span>
            <div className="zoom-controls" aria-label="Canvas zoom controls"><button type="button" aria-label="Zoom out" title="Zoom out (-)" onClick={() => setZoom((current) => Math.max(0.5, Number((current * 0.9).toFixed(2))))}><ZoomOut aria-hidden="true" /></button><span>{Math.round(zoom * 100)}%</span><button type="button" aria-label="Zoom in" title="Zoom in (+)" onClick={() => setZoom((current) => Math.min(2.5, Number((current * 1.1).toFixed(2))))}><ZoomIn aria-hidden="true" /></button></div>
            <button className="upload-canvas-button" type="button" onClick={() => uploadRef.current?.click()}><ImagePlus aria-hidden="true" /><span>Place images</span></button>
          </div>
          <div className="canvas-stage">
            <FabricCanvasSurface key={presetId} elements={elements} preset={preset} backgroundColor={backgroundColor} checkerboard={checkerboard} guides={guides} selectedIds={selectedIds} tool={tool} zoom={zoom} canvasRef={canvasRef} onSelectionChange={handleCanvasSelection} onObjectUpdates={applyFabricUpdates} onBackgroundSelect={selectCanvasBackground} onPickColor={pickCanvasColor} onPaint={(targetId) => applyPaintColor(primaryColor, targetId)} onZoomChange={changeZoom} />
          </div>
          <div className="canvas-statusbar"><div><span className="status-indicator" />{guides.size ? `${guides.size} guide${guides.size === 1 ? '' : 's'} active` : 'Ready'}<span className="status-separator">/</span>{elements.length} layers<span className="status-separator">/</span>Shift-drag to marquee select</div><span>{Math.round(zoom * 100)}%</span></div>
        </section>
        <PropertyPanel selected={selected} backgroundSelected={backgroundSelected} backgroundColor={backgroundColor} onUpdate={(updates) => selected && updateElement(selected.id, updates)} onUpdateBackground={updateBackgroundColor} onDelete={() => { setElements((current) => current.filter((element) => !selectedIds.has(element.id))); setSelectedId(null); setSelectedIds(new Set()) }} onStack={changeStack} />
      </div>
      <input ref={uploadRef} className="visually-hidden" type="file" accept="image/png,image/jpeg" multiple onChange={handleUpload} aria-label="Place image layers" />
      <input ref={projectRef} className="visually-hidden" type="file" accept=".json,.canvasroom.json,application/json" onChange={(event) => void handleOpenProject(event)} aria-label="Open editable project file" />
      {dialog && <div className="editor-dialog-backdrop" onMouseDown={(event) => { if (event.target === event.currentTarget) setDialog(null) }}>
        <section className="editor-dialog" role="dialog" aria-modal="true" aria-labelledby="editor-dialog-title">
          <header className="editor-dialog-header"><div><span className="eyebrow">{dialog === 'project' ? 'PROJECT FILE' : 'IMAGE EXPORT'}</span><h2 id="editor-dialog-title">{dialog === 'project' ? 'Save editable project' : 'Export composition'}</h2></div><button type="button" className="dialog-close" aria-label="Close dialog" onClick={() => setDialog(null)}><X aria-hidden="true" /></button></header>
          {dialog === 'project' ? <div className="dialog-body"><label className="control-label" htmlFor="project-file-name">Project name</label><input id="project-file-name" className="project-file-name" value={projectName} onChange={(event) => setProjectName(event.target.value)} /><p>Downloads a JSON project file with every layer and image embedded. Open it later to keep editing.</p><div className="dialog-actions"><button type="button" className="dialog-secondary" onClick={() => projectRef.current?.click()}><FolderOpen aria-hidden="true" />Open project</button><button type="button" className="dialog-primary" onClick={saveProject}><Save aria-hidden="true" />Download project</button></div></div> : <div className="dialog-body"><p>Choose how to use this {exportFormat.toUpperCase()} composition.</p><div className="export-choice-list"><button type="button" className="export-choice" onClick={() => void exportCanvas()} disabled={exporting}><ArrowDownToLine aria-hidden="true" /><span><strong>Download to computer</strong><small>Save a rendered {preset.width} × {preset.height} image</small></span></button><button type="button" className="export-choice" onClick={() => void saveToPlanner()} disabled={plannerSaving}><Layers3 aria-hidden="true" /><span><strong>{plannerSaving ? 'Preparing Planner save…' : 'Save on server for Planner'}</strong><small>Sends the rendered image and editable project payload</small></span></button></div></div>}
          {dialogMessage && <p className="dialog-status" role="status">{dialogMessage}</p>}
        </section>
      </div>}
      {exportError && <div className="export-error" role="status">Could not read or export that file. Try a PNG or JPG image.</div>}
    </main>
  )
}

