/* global React */
// ─────────────────────────────────────────────────────────────────
// Chrome — header, secondary toolbar, sidebar, right panel,
// floating Contacts panel, bottom action bar.
// All visual; behaviour wired up in viewer.jsx via props.
// ─────────────────────────────────────────────────────────────────

const { useState, useEffect, useRef } = React;

// ─── Icons (inline SVGs, Lucide-style 1.75 stroke) ───
const I = {
  arrowDown: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" width="12" height="12"><line x1="12" y1="5" x2="12" y2="19" /><polyline points="6 13 12 19 18 13" /></svg>,
  arrowLeft: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" width="12" height="12"><line x1="19" y1="12" x2="5" y2="12" /><polyline points="11 6 5 12 11 18" /></svg>,
  arrowRight: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" width="12" height="12"><line x1="5" y1="12" x2="19" y2="12" /><polyline points="13 6 19 12 13 18" /></svg>,
  menu: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><line x1="4" y1="6" x2="20" y2="6" /><line x1="4" y1="12" x2="20" y2="12" /><line x1="4" y1="18" x2="20" y2="18" /></svg>,
  back: <svg viewBox="0 0 24 24" fill="none" width="18" height="18"><path d="M11.4697 4.46967C11.7626 4.17678 12.2374 4.17678 12.5303 4.46967C12.8232 4.76256 12.8232 5.23732 12.5303 5.53022L6.06055 11.9999L12.5303 18.4697C12.8232 18.7626 12.8232 19.2373 12.5303 19.5302C12.2374 19.8231 11.7626 19.8231 11.4697 19.5302L4.46973 12.5302C4.17683 12.2373 4.17683 11.7626 4.46973 11.4697L11.4697 4.46967Z" fill="currentColor" /><path d="M19 11.2499C19.4142 11.2499 19.75 11.5857 19.75 11.9999C19.75 12.4142 19.4142 12.7499 19 12.7499H5C4.58579 12.7499 4.25 12.4142 4.25 11.9999C4.25 11.5857 4.58579 11.2499 5 11.2499H19Z" fill="currentColor" /></svg>,
  undo: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M3 7v6h6" /><path d="M21 17a9 9 0 0 0-15-6.7L3 13" /></svg>,
  redo: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M21 7v6h-6" /><path d="M3 17a9 9 0 0 1 15-6.7L21 13" /></svg>,
  bell: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" /><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" /></svg>,
  gear: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" /></svg>,
  chevDown: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="14" height="14"><polyline points="6 9 12 15 18 9" /></svg>,
  chevUp: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><polyline points="6 15 12 9 18 15" /></svg>,
  expand: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M8 3H5a2 2 0 0 0-2 2v3" /><path d="M21 8V5a2 2 0 0 0-2-2h-3" /><path d="M3 16v3a2 2 0 0 0 2 2h3" /><path d="M16 21h3a2 2 0 0 0 2-2v-3" /></svg>,
  collapse: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M8 3v3a2 2 0 0 1-2 2H3" /><path d="M21 8h-3a2 2 0 0 1-2-2V3" /><path d="M3 16h3a2 2 0 0 1 2 2v3" /><path d="M16 21v-3a2 2 0 0 1 2-2h3" /></svg>,
  chevRight: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="14" height="14"><polyline points="9 18 15 12 9 6" /></svg>,
  chevLeft: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="14" height="14"><polyline points="15 18 9 12 15 6" /></svg>,
  x: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>,
  xBold: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>,
  check: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><polyline points="5 12 10 17 19 7" /></svg>,
  eye: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z" /><circle cx="12" cy="12" r="3" /></svg>,
  zoomIn: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" /><line x1="11" y1="8" x2="11" y2="14" /><line x1="8" y1="11" x2="14" y2="11" /></svg>,
  zoomOut: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" /><line x1="8" y1="11" x2="14" y2="11" /></svg>,
  eyeOff: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M9.88 9.88a3 3 0 1 0 4.24 4.24" /><path d="M10.73 5.08A10.4 10.4 0 0 1 12 5c6.5 0 10 7 10 7a13.2 13.2 0 0 1-1.67 2.68" /><path d="M6.61 6.61A13.5 13.5 0 0 0 2 12s3.5 7 10 7a9.7 9.7 0 0 0 5.39-1.61" /><line x1="2" y1="2" x2="22" y2="22" /></svg>,
  caretUp: <svg viewBox="0 0 24 24" fill="currentColor" width="10" height="10"><path d="M7 14l5-5 5 5z" /></svg>,
  caretDown: <svg viewBox="0 0 24 24" fill="currentColor" width="10" height="10"><path d="M7 10l5 5 5-5z" /></svg>,
  imagePh: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><rect x="3" y="3" width="18" height="18" rx="2" ry="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" /></svg>,
  fit: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" width="22" height="22"><path d="M4 8V4h4M16 4h4v4M20 16v4h-4M8 20H4v-4" /></svg>,
  // Left sidebar tool icons
  checklist: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><rect x="3" y="3" width="18" height="18" rx="2" /><polyline points="9 11 12 14 17 8" /><line x1="3" y1="16" x2="6" y2="16" /></svg>,
  importTooth: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><path d="M12 3v8" /><polyline points="8 7 12 3 16 7" /><path d="M5 12h2v2H5z" fill="currentColor" /><path d="M17 12h2v2h-2z" fill="currentColor" /><path d="M12 13v8" /><polyline points="8 17 12 21 16 17" /></svg>,
  scan: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><path d="M3 7V5a2 2 0 0 1 2-2h2" /><path d="M17 3h2a2 2 0 0 1 2 2v2" /><path d="M21 17v2a2 2 0 0 1-2 2h-2" /><path d="M7 21H5a2 2 0 0 1-2-2v-2" /><line x1="7" y1="12" x2="17" y2="12" /></svg>,
  move: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><polyline points="5 9 2 12 5 15" /><polyline points="9 5 12 2 15 5" /><polyline points="15 19 12 22 9 19" /><polyline points="19 9 22 12 19 15" /><line x1="2" y1="12" x2="22" y2="12" /><line x1="12" y1="2" x2="12" y2="22" /></svg>,
  sculpt: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><path d="M14.5 4l5.5 5.5L8 21H3v-5z" /></svg>,
  wrench: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><path d="M14.7 6.3a4 4 0 0 0-5.4 5.4L3 18l3 3 6.3-6.3a4 4 0 0 0 5.4-5.4L15 12l-3-3 2.7-2.7z" /></svg>,
  tooth: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><path d="M12 2c-3 0-5 2-7 2C2 4 2 8 3 11s2 4 2 7l1 4c.4 1 1.8 1 2 0l1-5c.3-1 .8-2 3-2s2.7 1 3 2l1 5c.2 1 1.6 1 2 0l1-4c0-3 1-4 2-7s1-7-2-7c-2 0-4-2-7-2z" /></svg>,
  ruler: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><path d="M21 3L3 21l-1-1L20 2z" /><path d="M9 9l1 1M12 6l1.5 1.5M15 3l1.5 1.5M6 12l1.5 1.5M3 15l1.5 1.5M9 15l3 3M6 18l3 3" /></svg>,
  flag: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="20" height="20"><path d="M4 22V4l9 3-3 4 9 3-15 8z" /></svg>,
  caretL: <svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M15 18l-6-6 6-6v12z" /></svg>,
  caretR: <svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14"><path d="M9 18l6-6-6-6v12z" /></svg>,
  caretFirst: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><polyline points="11 17 6 12 11 7" /><line x1="18" y1="17" x2="18" y2="7" /></svg>,
  chat: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" /></svg>,
  pencilSm: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="15" height="15"><path d="M15.8 3.1 3.8 15.1a2 2 0 0 0-.5.9L2.3 19.1l3.1-.9c.35-.1.67-.3.93-.55L18.9 5.6a2.65 2.65 0 0 0-3.1-2.5z" /><path d="M14.5 4.5l3 3" /></svg>,
  trash: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="15" height="15"><path d="M4 7h16" /><path d="M10 11v6M14 11v6" /><path d="M6 7l1 13h10l1-13" /><path d="M9 7V4h6v3" /></svg>,
  download: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="16" height="16"><path d="M12 3v12" /><polyline points="7 11 12 16 17 11" /><path d="M5 20h14" /></svg>,
  history: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" width="18" height="18"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path d="M3 3v5h5" /><path d="M12 7v5l4 2" /></svg>,
  checkSolid: <svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.4697 5.46967C19.7626 5.17678 20.2373 5.17678 20.5302 5.46967C20.8231 5.76256 20.8231 6.23732 20.5302 6.53022L9.53022 17.5302C9.23732 17.8231 8.76256 17.8231 8.46967 17.5302L3.46967 12.5302C3.17678 12.2373 3.17678 11.7626 3.46967 11.4697C3.76256 11.1768 4.23732 11.1768 4.53022 11.4697L8.99994 15.9394L19.4697 5.46967Z" /></svg>,
  cornerUpLeft: <svg viewBox="0 0 24 24" fill="currentColor"><path d="M19.25 19.9999V12.9999C19.25 12.138 18.9073 11.3116 18.2979 10.7021C17.6884 10.0926 16.862 9.74994 16 9.74994H4C3.58579 9.74994 3.25 9.41416 3.25 8.99994C3.25 8.58573 3.58579 8.24994 4 8.24994H16C17.2598 8.24994 18.4676 8.75075 19.3584 9.64154C20.2492 10.5323 20.75 11.7402 20.75 12.9999V19.9999C20.75 20.4142 20.4142 20.7499 20 20.7499C19.5858 20.7499 19.25 20.4142 19.25 19.9999Z" /><path d="M8.46973 3.46967C8.76262 3.17678 9.23738 3.17678 9.53027 3.46967C9.82317 3.76256 9.82317 4.23732 9.53027 4.53022L5.06055 8.99994L9.53027 13.4697C9.82317 13.7626 9.82317 14.2373 9.53027 14.5302C9.23738 14.8231 8.76262 14.8231 8.46973 14.5302L3.46973 9.53022C3.17683 9.23732 3.17683 8.76256 3.46973 8.46967L8.46973 3.46967Z" /></svg>
};

// ─── Brand mark ───
function BrandMark() {
  return (
    <div className="brand-mark" aria-label="Glidewell">
      <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 3.2C7.2 3.2 3.3 7.1 3.3 12s3.9 8.8 8.7 8.8c3 0 5.6-1.5 7.2-3.8v-5.4h-6.6V14h3.7c-.6 1.7-2.2 3-4.3 3-2.7 0-4.9-2.2-4.9-5s2.2-5 4.9-5c1.3 0 2.5.5 3.4 1.4l2.7-2.7c-1.6-1.5-3.7-2.5-6.1-2.5z" /></svg>
    </div>);

}

function CanvasActions({ onZoomIn, onZoomOut, isZen, onToggleZen }) {
  return (
    <div className="canvas-actions">
      <button className="icon-btn" aria-label="Zoom out" onClick={onZoomOut}>
        <img className="icon-img" src={(window.__resources && window.__resources.zoomOut) || "uploads/zoom-out.svg"} alt="" width="20" height="20" />
        <span className="vpt-tip"><span className="tip-label">Zoom Out</span></span>
      </button>
      <button className="icon-btn" aria-label="Zoom in" onClick={onZoomIn}>
        <img className="icon-img" src={(window.__resources && window.__resources.zoomIn) || "uploads/zoom-in.svg"} alt="" width="20" height="20" />
        <span className="vpt-tip"><span className="tip-label">Zoom In</span></span>
      </button>
      <button className="icon-btn canvas-actions-last" aria-label={isZen ? "Exit Full Screen" : "Full Screen"} onClick={onToggleZen}>
        {isZen ?
        <img className="icon-img" src={(window.__resources && window.__resources.minimize) || "uploads/minimize.svg"} alt="" width="20" height="20" /> :
        I.expand}
        <span className="vpt-tip tip-align-right">
          <span className="tip-label">{isZen ? "Exit Full Screen" : "Full Screen"}</span>
          {isZen && <span className="tip-key">ESC</span>}
        </span>
      </button>
    </div>);
}

// ─── Top header ───
function Header({ onResetPrototype, menuOpen, onToggleMenu, onHelp }) {
  const [settingsOpen, setSettingsOpen] = useState(false);
  const menuRef = useRef(null);
  useEffect(() => {
    if (!settingsOpen) return;
    const onDoc = (e) => { if (menuRef.current && !menuRef.current.contains(e.target)) setSettingsOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setSettingsOpen(false); };
    window.addEventListener("mousedown", onDoc);
    window.addEventListener("keydown", onKey);
    return () => { window.removeEventListener("mousedown", onDoc); window.removeEventListener("keydown", onKey); };
  }, [settingsOpen]);
  return (
    <header className="header">
      <div className="brand">
        <img src={(window.__resources && window.__resources.logomark) || "assets/glidewell-logomark.svg"} alt="Glidewell" style={{ height: 28, display: 'block' }} />
        <span className="brand-divider" />
        <span className="brand-product">Design Studio</span>
      </div>
      <div className="header-right">
        <button className="icon-btn header-menu-btn glass-l2" aria-label={menuOpen ? "Close menu" : "Open menu"} aria-expanded={!!menuOpen} onClick={() => onToggleMenu && onToggleMenu()}>
          {menuOpen ? I.x : I.menu}
        </button>
        <button className="icon-btn header-icon-btn" aria-label="Help & resources" onClick={onHelp}>
          <img className="icon-img" src={(window.__resources && window.__resources.circleHelp) || "uploads/circle-help.svg"} alt="" width="20" height="20" />
          <span className="vpt-tip"><span className="tip-label">Help & Resources</span></span>
        </button>
        <button className="icon-btn header-icon-btn" aria-label="Notifications">
          {I.bell}
          <span className="vpt-tip"><span className="tip-label">Notifications</span></span>
        </button>
        <div className="settings-menu-wrap" ref={menuRef}>
          <button
            className={"icon-btn header-icon-btn" + (settingsOpen ? " active" : "")}
            aria-label="Settings" aria-haspopup="menu" aria-expanded={settingsOpen}
            onClick={() => setSettingsOpen((o) => !o)}>
            {I.gear}
            {!settingsOpen && <span className="vpt-tip tip-align-right"><span className="tip-label">Settings</span></span>}
          </button>
          {settingsOpen &&
          <div className="settings-menu" role="menu">
            <button
              className="settings-menu-item"
              role="menuitem"
              onClick={() => { setSettingsOpen(false); onResetPrototype && onResetPrototype(); }}>
              {I.history} Reset Prototype
            </button>
          </div>
          }
        </div>
      </div>
    </header>);

}

// ─── Left sidebar ───
function Sidebar({ tool, setTool }) {
  const tools = [
  { id: "checklist", icon: I.checklist, label: "Workflow" },
  { id: "import", icon: I.importTooth, label: "Import/Export", dot: true },
  { id: "scan", icon: I.scan, label: "Scan" },
  { id: "move", icon: I.move, label: "Move" },
  { id: "sculpt", icon: I.sculpt, label: "Sculpt" },
  { id: "wrench", icon: I.wrench, label: "Adjust" },
  { id: "tooth", icon: I.tooth, label: "Tooth Library" },
  { id: "ruler", icon: I.ruler, label: "Measure" },
  { id: "flag", icon: I.flag, label: "Annotate" }];

  return (
    <aside className="sidebar">
      {tools.map((t) =>
      <button key={t.id}
      className={"icon-btn " + (tool === t.id ? "active" : "")}
      title={t.label}
      onClick={() => setTool(t.id)}>
          {t.icon}
          {t.dot && <span className="dot" />}
        </button>
      )}
    </aside>);

}

// ─── Bottom floating viewport toolbar — single compact row ───
function VptButton({ icon: Icon, tip, shortcut, boxed, active, toolActive, hideOnMobile, onClick }) {
  return (
    <button type="button" className={"vpt-btn" + (boxed ? " boxed glass-l2" : "") + (active ? " active" : "") + (toolActive ? " tool-active" : "") + (hideOnMobile ? " vpt-mobile-hide" : "")} onClick={onClick}>
      <Icon />
      <span className="vpt-tip">
        <span className="tip-label">{tip}</span>
        {shortcut && <span className="tip-key">{shortcut}</span>}
      </span>
    </button>);

}
function ViewportToolbar({ view, onView, onFit, onJawView, dark, annotating, onAnnotate, locked }) {
  const [tool, setTool] = useState(null);
  const [fitFlash, setFitFlash] = useState(false);
  // Contacts / Cross Section aren't wired up yet — selecting either shows a
  // callout above the toolbar instead of silently doing nothing.
  const unavailable = tool === "contacts" ? "Contacts" : tool === "cross-section" ? "Cross Section" : null;
  const flashFit = () => {
    setFitFlash(true);
    setTimeout(() => setFitFlash(false), 300);
  };
  // The annotate tool lives in App state, so selecting either side has to
  // clear the other — only one viewport tool is ever active.
  const toggleTool = (name) => {
    if (annotating && onAnnotate) onAnnotate();
    setTool((t) => t === name ? null : name);
  };
  const toggleAnnotate = () => { setTool(null); onAnnotate && onAnnotate(); };

  useEffect(() => {
    const onKey = (e) => {
      if (!e.isTrusted || e.repeat) return;
      if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA" || e.target.isContentEditable) return;
      if (annotating) return;
      const k = e.key.toUpperCase();
      if (k === "A") { if (!locked) toggleAnnotate(); return; }
      if (k === "F") { onFit(); flashFit(); }else
      if (k === "X") onJawView("maxilla");else
      if (k === "D") onJawView("mandible");else
      if (k === "C") toggleTool("contacts");else
      if (k === "S") toggleTool("cross-section");
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onFit, onJawView, onAnnotate, annotating, locked]);

  return (
    <div className={"vpt-wrap" + (dark ? " dark" : "")}>
      <div className={"viewport-toolbar-v2 " + (dark ? "dark" : "") + (locked ? " locked" : "")}>
        <div className="vpt-group">
          <VptButton icon={IconContacts} tip="Contacts" shortcut="C" hideOnMobile toolActive={tool === "contacts"} onClick={() => toggleTool("contacts")} />
          <VptButton icon={IconCrossSection} tip="Cross Section" shortcut="S" hideOnMobile toolActive={tool === "cross-section"} onClick={() => toggleTool("cross-section")} />
          {!locked &&
          <VptButton icon={IconPencil} tip="Annotate" shortcut="A" toolActive={annotating} onClick={(e) => { e.currentTarget.blur(); toggleAnnotate(); }} />
          }
        </div>
        <div className="vpt-divider" />
        <div className="vpt-group">
          <VptButton icon={IconFocus} tip="Fit To Screen" shortcut="F" boxed active={fitFlash} onClick={() => { onFit(); flashFit(); }} />
          <VptButton icon={IconLeftLateral} tip="Left Lateral View" shortcut={I.arrowLeft} boxed active={view === "M"} onClick={() => onView("M")} />
          <VptButton icon={IconLaugh} tip="Frontal View" shortcut={I.arrowDown} boxed active={view === "L"} onClick={() => onView("L")} />
          <VptButton icon={IconRightLateral} tip="Right Lateral View" shortcut={I.arrowRight} boxed active={view === "D"} onClick={() => onView("D")} />
          <VptButton icon={IconUpperOcclusal2} tip="Maxilla View" shortcut="X" boxed active={view === "MAXV"} onClick={() => onJawView("maxilla")} />
          <VptButton icon={IconLowerOcclusal2} tip="Mandible View" shortcut="D" boxed active={view === "MANV"} onClick={() => onJawView("mandible")} />
        </div>
      </div>
      {unavailable &&
        <p className="vpt-unavailable">{unavailable} is not available at this time</p>
      }
    </div>);

}

// ─── Lightbox — fixed-size black viewer with zoom + prev/next ───
function Lightbox({ photos, index, onClose, onNav, onEdit, onDelete }) {
  const [confirmDelete, setConfirmDelete] = useState(false);
  const [zoom, setZoom] = useState(1);
  const [pan, setPan] = useState({ x: 0, y: 0 });
  const [dragging, setDragging] = useState(false);
  const photo = photos[index];
  const stageRef = useRef(null);
  const imgRef = useRef(null);
  const dragRef = useRef(null);

  // Reset zoom + pan whenever the photo changes.
  useEffect(() => {setZoom(1);setPan({ x: 0, y: 0 });}, [index]);

  // Clamp a pan offset so the image can't be dragged entirely out of view.
  // Pan units are unscaled CSS px (translate is applied after scale), so the
  // max offset is half of however much the scaled image overflows the stage.
  const clampPan = (p, z) => {
    const stage = stageRef.current,img = imgRef.current;
    if (!stage || !img) return p;
    const maxX = Math.max(0, (img.offsetWidth * z - stage.clientWidth) / 2);
    const maxY = Math.max(0, (img.offsetHeight * z - stage.clientHeight) / 2);
    return {
      x: Math.min(maxX, Math.max(-maxX, p.x)),
      y: Math.min(maxY, Math.max(-maxY, p.y))
    };
  };

  // Whenever zoom changes, re-clamp the pan (recenter fully at 1x).
  useEffect(() => {
    setPan((p) => zoom <= 1 ? { x: 0, y: 0 } : clampPan(p, zoom));
  }, [zoom]);

  // Keyboard: Esc closes, arrows navigate, +/- zoom.
  useEffect(() => {
    const onKey = (e) => {
      if (e.key === "Escape") onClose();else
      if (e.key === "ArrowLeft") onNav(-1);else
      if (e.key === "ArrowRight") onNav(1);else
      if (e.key === "+" || e.key === "=") setZoom((z) => Math.min(3, +(z + 0.5).toFixed(2)));else
      if (e.key === "-" || e.key === "_") setZoom((z) => Math.max(1, +(z - 0.5).toFixed(2)));
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose, onNav]);

  // Click-and-drag panning — only active while zoomed in.
  const onPointerDown = (e) => {
    if (zoom <= 1) return;
    e.preventDefault();
    dragRef.current = { startX: e.clientX, startY: e.clientY, panX: pan.x, panY: pan.y };
    setDragging(true);
    e.currentTarget.setPointerCapture(e.pointerId);
  };
  const onPointerMove = (e) => {
    if (!dragRef.current) return;
    const dx = e.clientX - dragRef.current.startX;
    const dy = e.clientY - dragRef.current.startY;
    setPan(clampPan({ x: dragRef.current.panX + dx, y: dragRef.current.panY + dy }, zoom));
  };
  const endDrag = (e) => {
    if (!dragRef.current) return;
    dragRef.current = null;
    setDragging(false);
    if (e.currentTarget.hasPointerCapture && e.currentTarget.hasPointerCapture(e.pointerId)) {
      e.currentTarget.releasePointerCapture(e.pointerId);
    }
  };

  return ReactDOM.createPortal(
    <div className="lightbox-overlay" onClick={onClose}>
      <div className="lightbox" onClick={(e) => e.stopPropagation()}>
        <div
          ref={stageRef}
          className={"lb-stage" + (zoom > 1 ? " zoomable" : "") + (dragging ? " dragging" : "")}
          onPointerDown={onPointerDown}
          onPointerMove={onPointerMove}
          onPointerUp={endDrag}
          onPointerCancel={endDrag}
          onPointerLeave={endDrag}>
          <img
            ref={imgRef}
            src={photo.src}
            alt={photo.alt}
            draggable={false}
            style={{ transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})` }} />
          <span className="lb-filename">{photo.name}</span>
        </div>
        <button className="lb-close" onClick={onClose} aria-label="Close">{I.x}</button>
        {photos.length > 1 &&
        <button className="lb-arrow lb-prev" onClick={() => onNav(-1)} aria-label="Previous photo">{I.chevLeft}</button>
        }
        {photos.length > 1 &&
        <button className="lb-arrow lb-next" onClick={() => onNav(1)} aria-label="Next photo">{I.chevRight}</button>
        }
        {confirmDelete &&
          <div className="modal-overlay lb-confirm" onClick={(e) => { e.stopPropagation(); setConfirmDelete(false); }}>
            <div className="modal" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
              <div className="modal-head">
                <div className="modal-headtext">
                  <h2 className="modal-title">Delete this annotation?</h2>
                </div>
                <button className="icon-btn modal-x" onClick={() => setConfirmDelete(false)} aria-label="Close">{I.x}</button>
              </div>
              <div className="modal-body">
                <ul className="attach-list">
                  <li className="attach-item">
                    <img className="attach-thumb" src={photo.src} alt="" />
                    <span className="attach-name mono-id">{photo.name}</span>
                  </li>
                </ul>
                <p className="modal-note">This annotation will be removed from the case and can't be recovered.</p>
              </div>
              <div className="modal-foot">
                <button className="btn btn-lg ann-btn-destructive" onClick={() => { setConfirmDelete(false); onDelete(index); }}>Delete</button>
                <button className="btn btn-neutral btn-lg" onClick={() => setConfirmDelete(false)}>Cancel</button>
              </div>
            </div>
          </div>
        }
        <div className="lb-zoom">
          {onDelete &&
          <button className="lb-edit" onClick={() => setConfirmDelete(true)}>
            {I.trash} Delete
          </button>
          }
          {onEdit &&
          <button className="lb-edit" onClick={() => onEdit(index)}>
            {I.pencilSm} Edit
          </button>
          }
          <button onClick={() => setZoom((z) => Math.max(1, +(z - 0.5).toFixed(2)))} disabled={zoom <= 1} aria-label="Zoom out">{I.zoomOut}</button>
          <button onClick={() => setZoom((z) => Math.min(3, +(z + 0.5).toFixed(2)))} disabled={zoom >= 3} aria-label="Zoom in">{I.zoomIn}</button>
        </div>
      </div>
    </div>,
    document.body);

}

// ─── Right panel ───
// One visibility-controlled item: a label + full-width slider, with an eye
// toggle. A full eye means the item is shown (click to hide); an eye with a
// line through it means hidden (click to show). While hidden, the label and
// slider are disabled. Defined at module scope (not inside RightPanel) so the
// <input> persists across re-renders — otherwise dragging the slider breaks
// because the node is recreated on every value change.
function VisItem({ jaw, setJaw, itemKey, label, tooltip, children }) {
  const item = jaw[itemKey];
  const setShown = (shown) => setJaw({ ...jaw, [itemKey]: { ...item, shown } });
  const setOpacity = (opacity) => setJaw({ ...jaw, [itemKey]: { ...item, opacity } });
  const slider = (
    <input
      className="slider slider-filled vis-slider"
      type="range" min="0" max="100"
      value={item.opacity}
      disabled={!item.shown}
      style={{ "--fill": item.opacity + "%" }}
      onChange={(e) => setOpacity(+e.target.value)} />);

  return (
    <div className={"vis-card glass-l2 " + (item.shown ? "" : "is-hidden")}>
      <div className="vis-card-head">
        <span className="vis-label">{label}</span>
      </div>
      <div className="vis-card-body">
        {tooltip ?
        <div className="vis-slider-wrap">
          {slider}
          <span className="vpt-tip"><span className="tip-label">{tooltip}</span></span>
        </div> :
        slider}
        <button
          className="vis-eye"
          onClick={() => setShown(!item.shown)}
          aria-pressed={item.shown}
          title={item.shown ? "Hide" : "Show"}
          aria-label={item.shown ? "Hide" : "Show"}>
          {item.shown ? I.eye : I.eyeOff}
        </button>
      </div>
      {children &&
      <div className="vis-card-extra">{children}</div>
      }
    </div>);

}

// The 3 visibility sliders, shared between the desktop panel and the mobile
// floating tool panel so there's one source of truth for the jaw/setJaw wiring.
function VisibilitySliders({ jaw, setJaw }) {
  return (
    <React.Fragment>
      <VisItem jaw={jaw} setJaw={setJaw} itemKey="maxArch" label="Maxilla" />
      <VisItem jaw={jaw} setJaw={setJaw} itemKey="manArch" label="Mandible" />
      <VisItem
      jaw={jaw} setJaw={setJaw}
      itemKey="manCrown"
      label={<span><span className="mono-id">#8</span> Crown (BruxZir Esthetic, A1)</span>}
      tooltip="This may not work as intended at this time" />
    </React.Fragment>);

}

// ─── Mobile: top-level menu (4 rows, or 3 without any annotations yet) ───
function MobileMenuList({ onOpenVisibility, onOpenColor, onOpenPhotos, onOpenAnnotations, hasAnnotations }) {
  const rows = [
  { key: "visibility", label: "Visibility Controls", onClick: onOpenVisibility },
  { key: "color", label: "Color Options", onClick: onOpenColor },
  { key: "photos", label: "Design Photos", onClick: onOpenPhotos }];

  if (hasAnnotations) rows.push({ key: "annotations", label: "Annotations", onClick: onOpenAnnotations });
  return (
    <div className="mobile-menu-list">
      {rows.map((r) =>
      <button key={r.key} type="button" className="mobile-menu-row" onClick={r.onClick}>
        <span>{r.label}</span>
        {I.chevRight}
      </button>
      )}
    </div>);

}

// ─── Mobile: drill-down sub-page shell shared by Design Photos / Annotations ───
function MobileSubpage({ title, photos, onBack, onSelect }) {
  return (
    <div className="mobile-subpage">
      <div className="mobile-subpage-head">
        <button type="button" className="icon-btn" aria-label="Back" onClick={onBack}>{I.back}</button>
        <span className="mobile-subpage-title">{title}</span>
      </div>
      <div className="mobile-subpage-body">
        <div className="smile-grid">
          {photos.map((p, i) =>
          <button key={i} type="button" className="smile-thumb" title={p.alt} onClick={() => onSelect(i)}>
            <img src={p.src} alt={p.alt} />
          </button>
          )}
        </div>
      </div>
    </div>);

}

// ─── Mobile: floating canvas-overlay panel for Visibility Controls / Color
// Options. Rendered by App (not RightPanel) since it must sit over the
// canvas while the header and bottom toolbar are hidden. ───
function MobileToolPanel({ variant, jaw, setJaw, colorOptions, modelColor, setModelColor, onDone, dark }) {
  return (
    <div className={"mobile-tool-wrap" + (dark ? " dark" : "")}>
      <div className={"mobile-tool-panel" + (dark ? " dark" : "")}>
        {variant === "visibility" ?
        <VisibilitySliders jaw={jaw} setJaw={setJaw} /> :
        <div className="mobile-color-list">
          {colorOptions.map((opt) =>
          <button key={opt.key}
          type="button"
          className={"mobile-color-row" + (modelColor === opt.key ? " active" : "")}
          aria-pressed={modelColor === opt.key}
          onClick={() => setModelColor(opt.key)}>
              <span className="mobile-color-swatch"><img src={opt.icon} alt="" draggable="false" /></span>
              <span className="mobile-color-label">{opt.label}</span>
            </button>
          )}
        </div>}
      </div>
      <button type="button" className="mobile-tool-done" onClick={onDone}>Done</button>
    </div>);

}

function RightPanel({
  view, onView, onReset,
  colorOptions, modelColor, setModelColor,
  jaw, setJaw,
  annotations = [],
  onEditAnnotation, onCreateAnnotation, onDeleteAnnotation,
  reviewStatus, onReviewComplete,
  onResetPrototype, menuOpen,
  isMobile, onOpenTool,
  onUndo, onRedo, canUndo, canRedo, onHelp
}) {
  const [mobileSettingsOpen, setMobileSettingsOpen] = useState(false);
  // Mobile drill-down: null = top-level menu list, otherwise which sub-page
  // is showing. Reset whenever the menu closes so reopening it always lands
  // back on the top-level list rather than wherever it was left.
  const [mobileSection, setMobileSection] = useState(null);
  useEffect(() => {
    if (!menuOpen) setMobileSection(null);
  }, [menuOpen]);
  // Captured patient photos for the smile-design workflow.
  const smilePhotos = [
  { src: (window.__resources && window.__resources.smile1) || "assets/smile-1.jpg", alt: "Patient portrait", name: "hamilton_smile_retracted.jpg" },
  { src: (window.__resources && window.__resources.smile2) || "assets/smile-2.jpg", alt: "Retracted smile", name: "hamilton_smile.jpg" }];

  const [lbIndex, setLbIndex] = useState(null);
  const [annIndex, setAnnIndex] = useState(null);
  // The Design Review card floats over the scroll area, so reserve exactly its
  // height (plus the 8px inset and a 20px gap) at the end of the scroll. It
  // only renders on the top-level list (not the mobile sub-pages), so this
  // re-runs whenever that visibility changes to (re)attach to the right node.
  const innerRef = useRef(null);
  const reviewRef = useRef(null);
  const showReview = !isMobile || (menuOpen && !mobileSection);
  useEffect(() => {
    const el = reviewRef.current, inner = innerRef.current;
    if (!inner) return;
    if (!el || typeof ResizeObserver === "undefined") { inner.style.paddingBottom = ""; return; }
    const ro = new ResizeObserver(() => {
      inner.style.paddingBottom = (el.offsetHeight + 28) + "px";
    });
    ro.observe(el);
    return () => ro.disconnect();
  }, [showReview]);
  // Reopening the mobile menu should always land on top, not wherever the
  // user last scrolled to.
  useEffect(() => {
    if (menuOpen && innerRef.current) innerRef.current.scrollTop = 0;
  }, [menuOpen]);
  const annPhotos = annotations.map((src, i) => ({ src, alt: "Annotation " + (i + 1), name: "1234567890_annotation_" + (i + 1) + ".png" }));


  return (
    <aside className="right-panel">
      {!mobileSection &&
      <div className="panel-mobile-actions">
        <button className="icon-btn" aria-label="Help & resources" onClick={onHelp}>
          <img className="icon-img" src={(window.__resources && window.__resources.circleHelp) || "uploads/circle-help.svg"} alt="" width="20" height="20" />
        </button>
        <button className="icon-btn" aria-label="Notifications">{I.bell}</button>
        <div className="settings-menu-wrap panel-settings-wrap">
          <button
            className={"icon-btn" + (mobileSettingsOpen ? " active" : "")}
            aria-label="Settings" aria-haspopup="menu" aria-expanded={mobileSettingsOpen}
            onClick={() => setMobileSettingsOpen((o) => !o)}>{I.gear}</button>
          {mobileSettingsOpen &&
          <div className="settings-menu" role="menu">
            <button
              className="settings-menu-item"
              role="menuitem"
              onClick={() => { setMobileSettingsOpen(false); onResetPrototype && onResetPrototype(); }}>
              {I.history} Reset Prototype
            </button>
          </div>
          }
        </div>
      </div>
      }
      <div className="right-panel-inner" ref={innerRef}>
        {!isMobile ?
        <div className="panel-section">
            <div className="subsection-label">Visibility Controls</div>
            <VisibilitySliders jaw={jaw} setJaw={setJaw} />

            <hr className="panel-rule" />

            <div className="subsection-label" style={{ marginTop: 6 }}>Color Options</div>
            <div className="color-swatches">
              {colorOptions.map((opt) =>
            <button key={opt.key}
            type="button"
            className={"swatch " + (modelColor === opt.key ? "active" : "")}
            aria-label={opt.label}
            aria-pressed={modelColor === opt.key}
            onClick={() => setModelColor(opt.key)}>
                  <img src={opt.icon} alt="" draggable="false" />
                  <span className="swatch-tip"><span className="tip-label">{opt.label}</span></span>
                </button>
            )}
            </div>

            <hr className="panel-rule" />
            <div className="subsection-label" style={{ marginTop: 6 }}>Design Photos</div>
            <div className="smile-scroll">
              <div className="smile-grid">
                {smilePhotos.map((p, i) =>
              <button key={i} type="button" className="smile-thumb" title={p.alt} onClick={() => setLbIndex(i)}>
                    <img src={p.src} alt={p.alt} />
                  </button>
              )}
              </div>
            </div>

            {annPhotos.length > 0 &&
            <React.Fragment>
              <hr className="panel-rule" />
              <div className="subsection-label" style={{ marginTop: 6 }}>Annotations</div>
              <div className="smile-scroll smile-scroll-auto">
                <div className="smile-grid">
                  {annPhotos.map((p, i) =>
                  <button key={i} type="button" className="smile-thumb" title={p.alt} onClick={() => setAnnIndex(i)}>
                      <img src={p.src} alt={p.alt} />
                    </button>
                  )}
                </div>
              </div>
            </React.Fragment>
            }
          </div> :
        mobileSection === "photos" ?
        <MobileSubpage
          title="Design Photos"
          photos={smilePhotos}
          onBack={() => setMobileSection(null)}
          onSelect={(i) => setLbIndex(i)} /> :
        mobileSection === "annotations" ?
        <MobileSubpage
          title="Annotations"
          photos={annPhotos}
          onBack={() => setMobileSection(null)}
          onSelect={(i) => setAnnIndex(i)} /> :
        <MobileMenuList
          onOpenVisibility={() => onOpenTool && onOpenTool("visibility")}
          onOpenColor={() => onOpenTool && onOpenTool("color")}
          onOpenPhotos={() => setMobileSection("photos")}
          onOpenAnnotations={() => setMobileSection("annotations")}
          hasAnnotations={annPhotos.length > 0} />
        }
        </div>
          {showReview &&
          (reviewStatus ?
          <div className="review-section-fixed glass-l2 review-section-status" ref={reviewRef}>
            <ReviewStatusCard mode={reviewStatus.mode} date={reviewStatus.date} />
          </div> :
          <div className="review-section-fixed glass-l2" ref={reviewRef}>
            <p className="review-fixed-text">Review this design and let us know how to proceed.</p>
            <ReviewDesign annotations={annotations} onCreateAnnotation={onCreateAnnotation} onReviewComplete={onReviewComplete} />
          </div>)
          }
        {lbIndex !== null &&
      <Lightbox
        photos={smilePhotos}
        index={lbIndex}
        onClose={() => setLbIndex(null)}
        onNav={(d) => setLbIndex((i) => (i + d + smilePhotos.length) % smilePhotos.length)} />
      }
      {annIndex !== null &&
      <Lightbox
        photos={annPhotos}
        index={annIndex}
        onClose={() => setAnnIndex(null)}
        onNav={(d) => setAnnIndex((i) => (i + d + annPhotos.length) % annPhotos.length)}
        onEdit={reviewStatus ? undefined : (i) => { setAnnIndex(null); onEditAnnotation && onEditAnnotation(i); }}
        onDelete={reviewStatus ? undefined : (i) => { setAnnIndex(null); onDeleteAnnotation && onDeleteAnnotation(i); }} />
      }
    </aside>);

}

// ─── Review status card ─── shown fixed to the bottom of the right panel
// once the doctor has submitted a decision, in place of the review actions.
function ReviewStatusCard({ mode, date }) {
  const isReject = mode === "reject";
  const dateStr = date ? date.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" }) : "";
  return (
    <div className="review-status-card glass-l2">
      <div className={"review-status-icon " + (isReject ? "reject" : "approve")}>
        {isReject ? I.cornerUpLeft : I.checkSolid}
      </div>
      <span className="review-status-text">
        {isReject ? "Feedback sent on " + dateStr + "." : "Design approved on " + dateStr + "."}
      </span>
    </div>);

}

// ─── Review modal (reject / approve confirmation) ───
function ReviewModal({ mode, patientName, designs, annotations = [], onCreateAnnotation, onClose, onSubmit }) {
  const [feedback, setFeedback] = useState("");
  const isReject = mode === "reject";
  // Saved annotations ride along with a change request; either a note or at
  // least one attachment is enough to send.
  const allAnnotations = annotations.map((src, i) => ({ src, name: "1234567890_annotation_" + (i + 1) + ".png" }));
  // Change requests carry the annotations by default; an approval only does if
  // the doctor attaches them deliberately.
  const [attached, setAttached] = useState(isReject ? allAnnotations : []);
  const hasAttachments = attached.length > 0;
  const remaining = allAnnotations.filter((a) => !attached.some((x) => x.name === a.name));
  const canSend = isReject ? feedback.trim().length > 0 || hasAttachments : true;

  useEffect(() => {
    const onKey = (e) => {if (e.key === "Escape") onClose();};
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);

  const desc = isReject ?
  "Our design team will review your feedback and apply changes." :
  "Approve to start fabrication. We'll send an update when your case ships.";

  return ReactDOM.createPortal(
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal modal-lg" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div className="modal-headtext">
            <h2 className="modal-title">{isReject ? "Request Changes" : "Approve Design"}</h2>
            <p className="modal-sub modal-sub-desc">{desc}</p>
          </div>
          <button className="icon-btn modal-x" onClick={onClose} aria-label="Close">{I.x}</button>
        </div>
        <div className="modal-body">
          {(isReject || allAnnotations.length > 0) &&
          <div className="attach-block attach-block-top">
            <div className="attach-label">Attached annotations</div>
            {hasAttachments &&
            <ul className="attach-list">
              {attached.map((a) =>
              <li className="attach-item" key={a.name}>
                <img className="attach-thumb" src={a.src} alt="" />
                <span className="attach-name mono-id">{a.name}</span>
                <button
                  className="attach-x"
                  aria-label={"Remove " + a.name}
                  onClick={() => setAttached((list) => list.filter((x) => x.name !== a.name))}>
                  {I.x}
                </button>
              </li>
              )}
            </ul>
            }
            {(remaining.length > 0 || (isReject && allAnnotations.length === 0)) &&
            <div className={"attach-empty" + (hasAttachments ? " attach-empty-inline" : "")}>
              {!hasAttachments &&
              <p className="attach-empty-title">Nothing attached yet</p>
              }
              {isReject && allAnnotations.length === 0 ?
              <React.Fragment>
                <p className="attach-empty-text">Marking up a screenshot is the fastest way to show us exactly what to change.</p>
                <button className="support-cta-btn attach-empty-btn" onClick={() => { onClose(); onCreateAnnotation && onCreateAnnotation(); }}>
                  Create an annotation
                </button>
              </React.Fragment> :
              <React.Fragment>
                <p className="attach-empty-text">Attach your annotations to include them in this submission.</p>
                <button className="attach-link" onClick={() => setAttached(allAnnotations)}>
                  Attach {remaining.length} annotation{remaining.length === 1 ? "" : "s"}
                </button>
              </React.Fragment>
              }
            </div>
            }
          </div>
          }
          <label className="field-label" htmlFor="review-feedback">
            {isReject && hasAttachments ? "Other feedback" : "Feedback"}{" "}
            {isReject && !hasAttachments ?
            <span className="req" aria-label="required">*</span> :
            <span className="opt">(optional)</span>}
          </label>
          <textarea
            id="review-feedback"
            className="field-textarea"
            placeholder={!isReject || hasAttachments ?
            "Anything else you want to tell us" :
            "Describe what needs to change"}
            value={feedback}
            onChange={(e) => setFeedback(e.target.value)}
            rows={1} />
        </div>
        <div className="modal-foot">
          <button
            className={"btn btn-lg " + (isReject ? "btn-danger" : "btn-approve")}
            disabled={!canSend}
            onClick={() => onSubmit && onSubmit(mode, feedback, attached)}>
            {isReject ? "Send Request" : "Send Approval"}
          </button>
        </div>
      </div>
    </div>,
    document.querySelector(".app") || document.body);

}

// ─── Full-screen confirmation page (after a review is submitted) ───
// Branded takeover shown once Approval / Rejection is sent. Offers a
// primary route back to My Account, plus a small text-only Reset at the
// very bottom that returns the whole prototype to its starting point —
// handy when running the demo repeatedly.
function ConfirmationScreen({ mode, patientName, caseId, designs, feedback, attachments = [], onClose }) {
  const isReject = mode === "reject";

  useEffect(() => {
    const onKey = (e) => {if (e.key === "Escape") onClose();};
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);

  const title = isReject ? "Feedback Sent" : "Design Approved";
  const message = isReject ?
  "Your feedback was sent to our design team. The estimated ship date of your case will be updated soon." :
  "This case will now move into fabrication. We'll notify you as soon as your case ships.";

  const shipDate = (() => {
    const d = new Date();
    let added = 0;
    while (added < 2) {
      d.setDate(d.getDate() + 1);
      const day = d.getDay();
      if (day !== 0 && day !== 6) added++;
    }
    return d.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" });
  })();

  const badge = isReject ? <span className="confirm-badge-icon">{I.cornerUpLeft}</span> : <span className="confirm-badge-icon">{I.checkSolid}</span>;


  return ReactDOM.createPortal(
    <div className="confirm-screen">
      <div className="confirm-top">
        <img src={(window.__resources && window.__resources.logomark) || "assets/glidewell-logomark.svg"} alt="Glidewell" style={{ height: 28, display: "block" }} />
        <span className="brand-divider" />
        <span className="brand-product">Design Studio</span>
      </div>

      <div className="confirm-main">
        <div className="confirm-card">
          <div className={"confirm-badge " + (isReject ? "reject" : "approve")}>
            {badge}
          </div>
          <h1 className="confirm-title">{title}</h1>
          <p className="confirm-text">{message}</p>
          <p className="confirm-ship">
            Est. Ship Date: {isReject ? <span className="confirm-ship-tbd">TBD (reviewing feedback)</span> : shipDate}
          </p>

          <div className="confirm-meta">
            <div className="confirm-meta-row">
              <span className="confirm-meta-label">Patient</span>
              <span className="confirm-meta-val">{patientName}</span>
            </div>
            <div className="confirm-meta-row">
              <span className="confirm-meta-label">Case #</span>
              <span className="confirm-meta-val mono-id">{caseId}</span>
            </div>
            {designs.map((d, i) => {
              const sp = d.indexOf(" ");
              const id = sp === -1 ? d : d.slice(0, sp);
              const rest = sp === -1 ? "" : d.slice(sp);
              return (
                <div className="confirm-meta-row" key={i}>
                  <span className="confirm-meta-label">{i === 0 ? "Products" : ""}</span>
                  <span className="confirm-meta-val">
                    <span className="mono-id">{id}</span>{rest}
                  </span>
                </div>);

            })}
            {feedback && feedback.trim() &&
            <div className="confirm-meta-row">
              <span className="confirm-meta-label">Feedback</span>
              <span className="confirm-meta-val">{feedback}</span>
            </div>
            }
            {attachments.length > 0 &&
            <div className="confirm-meta-row">
              <span className="confirm-meta-label">Annotations</span>
              <span className="confirm-meta-val">
                <ul className="attach-list">
                  {attachments.map((a) =>
                  <li className="attach-item" key={a.name}>
                    <img className="attach-thumb" src={a.src} alt="" />
                    <span className="attach-name mono-id">{a.name}</span>
                    <button className="attach-x" aria-label={"Download " + a.name}>{I.download}</button>
                  </li>
                  )}
                </ul>
              </span>
            </div>
            }
          </div>

          <button className="btn btn-approve btn-lg confirm-cta" onClick={onClose}>
            Return to Viewer
          </button>
        </div>
      </div>

      <div className="confirm-bottom">
        <button
          className="confirm-reset"
          onClick={() => window.location.reload()}
          title="Reset the prototype to its starting point">
          Reset
        </button>
      </div>
    </div>,
    document.getElementById("root") || document.body);

}

// ─── Top action bar (review actions) ───
function BottomBar({ designCount = 1 }) {
  const unit = designCount === 1 ? "design" : "designs";
  const [modal, setModal] = useState(null);
  const [confirm, setConfirm] = useState(null);
  const [feedback, setFeedback] = useState("");
  const designs = ["#8 Crown (BruxZir Esthetic, A1)"];
  return (
    <div className="bottom-bar">
      <div className="desc">
        <span className="pt-name">Alexander Hamilton</span>
        <small>
          Review your patient's <strong>{designCount} {unit}</strong> and provide your feedback
        </small>
      </div>
      <button className="btn btn-reject btn-lg" onClick={() => setModal("reject")}>{I.xBold} Reject</button>
      <button className="btn btn-approve btn-lg" onClick={() => setModal("approve")}>{I.check} Approve</button>
      {modal &&
      <ReviewModal
        mode={modal}
        patientName="Products in case"
        designs={designs}
        onClose={() => setModal(null)}
        onSubmit={(m, f) => {setModal(null);setConfirm(m);setFeedback(f);}} />
      }
      {confirm &&
      <ConfirmationScreen
        mode={confirm}
        patientName="Alexander Hamilton"
        caseId="1234567890"
        designs={designs}
        feedback={feedback}
        onClose={() => setConfirm(null)} />
      }
    </div>);

}

// ─── Review Design actions (Reject / Approve) ───
function ReviewDesign({ designCount = 1, annotations = [], onCreateAnnotation, onReviewComplete }) {
  const [modal, setModal] = useState(null);
  const [confirm, setConfirm] = useState(null);
  const [feedback, setFeedback] = useState("");
  const [sentAttachments, setSentAttachments] = useState([]);
  const [submittedAt, setSubmittedAt] = useState(null);
  const designs = ["#8 Crown (BruxZir Esthetic, A1)"];
  return (
    <div className="review-actions">
      <button className="btn btn-approve btn-lg" onClick={() => setModal("approve")}>Approve Design</button>
      <button className="btn btn-reject btn-lg" onClick={() => setModal("reject")}>Request Changes</button>
      {modal &&
      <ReviewModal
        mode={modal}
        patientName="Products in case"
        designs={designs}
        annotations={annotations}
        onCreateAnnotation={onCreateAnnotation}
        onClose={() => setModal(null)}
        onSubmit={(m, f, at) => {setModal(null);setConfirm(m);setFeedback(f);setSentAttachments(at || []);setSubmittedAt(new Date());}} />
      }
      {confirm &&
      <ConfirmationScreen
        mode={confirm}
        patientName="Alexander Hamilton"
        caseId="1234567890"
        designs={designs}
        feedback={feedback}
        attachments={sentAttachments}
        onClose={() => { onReviewComplete && onReviewComplete(confirm, submittedAt); setConfirm(null); }} />
      }
    </div>);

}

const HELP_ROWS_DESKTOP = [
  { label: "Rotate", value: "Hold Left Click" },
  { label: "Pan", value: "Hold Right Click", sub: "or Hold Middle Click" },
  { label: "Zoom In/Out", value: "Scroll" },
];
const HELP_SHORTCUTS_DESKTOP = [
  { label: "Fit To Screen", value: "F" },
  { label: "Left Lateral View", value: "\u2190" },
  { label: "Frontal View", value: "\u2193" },
  { label: "Right Lateral View", value: "\u2192" },
  { label: "Maxilla View", value: "X" },
  { label: "Mandible View", value: "D" },
];
const HELP_ROWS_MOBILE = [
  { label: "Rotate", value: "1-Finger Drag" },
  { label: "Pan", value: "2-Finger Drag" },
  { label: "Zoom In/Out", value: "Pinch" },
  { label: "Twist Rotate", value: "2-Finger Twist" },
];
function HelpRows({ rows }) {
  return rows.map((r) =>
    <div className="help-row" key={r.label}>
      <span className="help-row-label">{r.label}</span>
      <span className="help-row-value">{r.value}{r.sub && <><br />{r.sub}</>}</span>
    </div>);

}
function HelpResourcesModal({ onClose, isMobile }) {
  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal help-modal" role="dialog" aria-modal="true" aria-label="Help & Resources" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div className="modal-headtext">
            <h2 className="modal-title">Help & Resources</h2>
          </div>
          <button className="icon-btn modal-x" onClick={onClose} aria-label="Close">{I.x}</button>
        </div>
        <div className="modal-body help-modal-body">
          <div className="subsection-label">Controls</div>
          <div className="help-rows"><HelpRows rows={isMobile ? HELP_ROWS_MOBILE : HELP_ROWS_DESKTOP} /></div>
          {!isMobile &&
          <>
            <div className="help-divider"></div>
            <div className="subsection-label">Keyboard Shortcuts</div>
            <div className="help-rows"><HelpRows rows={HELP_SHORTCUTS_DESKTOP} /></div>
          </>}
        </div>
      </div>
    </div>);

}

// ─── Toast — small pill notification, e.g. "annotation saved" ───
function Toast({ message }) {
  return (
    <div className="toast" role="status">
      <span className="toast-text">{message}</span>
    </div>);

}

// ── export to window ──
Object.assign(window, {
  Header, Sidebar, RightPanel, CanvasActions,
  BottomBar, ReviewDesign, ViewportToolbar,
  HelpResourcesModal, Toast,
  Icons: I
});