// ─────────────────────────────────────────────
// DEV_Design.jsx  (V7)
// PALETTE_GROUPS, OBJ_ICONS, helpers
// ColDropZone, RowBuilder
// DEVDesign main component
// SDS styles
//
// Loaded after:
//   /v6/DEV_Design.jsx        — DOConfirm, IconBrowser, ThemePanel, ValueListsPanel
//   /v7/DEV_Design_Inspector.jsx
//   /v7/DEV_Design_ListView.jsx
// ─────────────────────────────────────────────
// ─────────────────────────────────────────────
// DEV_Design.jsx  (V7)
//
// V7 changes vs V6:
//   - Layout: pages → rows → cols (nested JSON)
//     No separate row/col/tab DB records
//   - Objects reference col_id (UUID), not row/col#
//   - Tabs removed — pages live in detail.layout[]
//   - Fields: field_id (UUID) + field_code (fN)
//   - API: /v7/dev/apps/:appId/design/:tNum
//   - Save: detail/save + objects/save + listview/save
//
// Components in this file:
//   PALETTE_GROUPS, OBJ_ICONS          — same as V6
//   Inspector                          — V7 field refs
//   ListViewPanel                      — V7 display mode + field refs
//   HomeCanvas                         — V7 table JSON
//   ThemePanel                         — unchanged
//   DEVDesign                          — main component (full rewrite)
//
// Note: DOConfirm, IconBrowser, ColorPicker,
//       ValueListPicker, ValueListsPanel, KBIcon,
//       KBSidebar all come from V6 DEV_Design.jsx
//       which loads before this file.
// ─────────────────────────────────────────────

const PALETTE_GROUPS = [
  { label:'Data Fields', items:[
    { type:'input',    label:'Text Input',  icon:'T',  color:'#2563eb' },
    { type:'number',   label:'Number',      icon:'#',  color:'#0891b2' },
    { type:'date',     label:'Date',        icon:'📅', color:'#059669' },
    { type:'time',     label:'Time',        icon:'🕐', color:'#059669' },
    { type:'select',   label:'Dropdown',    icon:'▾',  color:'#d97706' },
    { type:'checkbox', label:'Check/Radio',    icon:'☑',  color:'#d97706' },
    { type:'file',     label:'File/Gallery',icon:'📎', color:'#dc2626' },
  ]},
  { label:'Display', items:[
    { type:'text',    label:'Text/Label', icon:'ℹ',  color:'#64748b' },
    { type:'rating',  label:'Rating',     icon:'⭐', color:'#f59e0b' },
    { type:'divider', label:'Divider',    icon:'—',  color:'#94a3b8' },
  ]},
  { label:'External', items:[
    { type:'map',      label:'Map',     icon:'🗺', color:'#059669' },
    { type:'portal',   label:'Portal',  icon:'⊞',  color:'#7c3aed' },
    { type:'weather',  label:'Weather', icon:'⛅', color:'#0891b2' },
  ]},
];

const OBJ_ICONS = {
  input:    { icon:'T',  color:'#2563eb' },
  number:   { icon:'#',  color:'#0891b2' },
  date:     { icon:'📅', color:'#059669' },
  time:     { icon:'🕐', color:'#059669' },
  select:   { icon:'▾',  color:'#d97706' },
  checkbox: { icon:'☑',  color:'#d97706' },
  file:     { icon:'📎', color:'#dc2626' },
  text:     { icon:'ℹ',  color:'#64748b' },
  rating:   { icon:'⭐', color:'#f59e0b' },
  divider:  { icon:'—',  color:'#94a3b8' },
  map:      { icon:'🗺', color:'#059669' },
  portal:   { icon:'⊞',  color:'#7c3aed' },
  weather:  { icon:'⛅', color:'#0891b2' },
};

function objIcon(type) { return OBJ_ICONS[type] || { icon:'?', color:'#94a3b8' }; }

function tempId() {
  return 'temp_' + Math.random().toString(36).substr(2, 9);
}

function newPageId()  { return 'page_'  + Math.random().toString(36).substr(2, 9); }
function newRowId()   { return 'row_'   + Math.random().toString(36).substr(2, 9); }
function newColId()   { return 'col_'   + Math.random().toString(36).substr(2, 9); }

// ─────────────────────────────────────────────
// V7 Inspector
// selectedObj: the full object JSON
// fields: schema fields array from table JSON
//         { id, code, name, desc, status }
// ─────────────────────────────────────────────
function ColDropZone({ col, objects, selectedId, placingType, onPlace, onSelectObj,
                       onSelectCol, siblingTotal, dragOver, onDragStart, onColMouseMove,
                       onColMouseLeave }) {
  const colObjs = objects
    .filter(o => o.col_id === col.id && o.status !== 0)
    .sort((a,b) => (a.order||0) - (b.order||0));

  const isPlacing  = !!placingType;
  const isDragOver = dragOver?.colId === col.id;
  const oi         = placingType ? objIcon(placingType) : null;

  // Render a blue drop indicator line at position idx
  function DropLine({ idx }) {
    if (!isDragOver || dragOver.idx !== idx) return null;
    return (
      <div style={{ height:2, background:'#2563eb', borderRadius:2,
                    margin:'2px 0', transition:'none' }} />
    );
  }

  return (
    <div
      onClick={() => isPlacing && onPlace(col.id)}
      onMouseMove={e => !isPlacing && onColMouseMove(e, col.id)}
      onMouseLeave={() => !isPlacing && onColMouseLeave()}
      style={{
        flex: col.width || 1,
        minHeight: 60,
        border: isPlacing
          ? '2px dashed #2563eb'
          : isDragOver ? '1.5px solid #93c5fd' : '1px solid #e2e8f0',
        borderRadius: 6,
        padding: '4px 6px',
        background: isPlacing ? '#f0f7ff' : isDragOver ? '#f0f7ff' : '#fff',
        cursor: isPlacing ? 'crosshair' : 'default',
        position: 'relative',
        transition: 'border-color 0.1s, background 0.1s',
      }}>

      {/* Col header */}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between',
                    marginBottom:4 }}>
        <span style={{ fontSize:9, color:'#cbd5e1', fontFamily:'DM Mono, monospace' }}>
          {col.width}/12
        </span>
        <button
          onClick={e => { e.stopPropagation(); onSelectCol(col, siblingTotal); }}
          style={{ background:'none', border:'none', color:'#94a3b8', cursor:'pointer',
                   fontSize:12, padding:'1px 4px', lineHeight:1 }}
          title="Edit column">✏️</button>
      </div>

      {/* Drop indicator before first object */}
      <DropLine idx={0} />

      {/* Objects */}
      {colObjs.map((obj, i) => {
        const ic         = objIcon(obj.type);
        const isSelected = selectedId === obj.id;
        return (
          <React.Fragment key={obj.id}>
            <div
              data-objid={obj.id}
              onMouseDown={e => { e.stopPropagation(); if (!isPlacing) onDragStart(e, obj); }}
              onClick={e => { e.stopPropagation(); !isPlacing && onSelectObj(obj); }}
              style={{
                display:'flex', alignItems:'center', gap:6,
                padding:'5px 8px', marginBottom:0, borderRadius:5,
                background: isSelected ? '#eff6ff' : '#f8fafc',
                border: isSelected ? '1.5px solid #2563eb' : '1px solid #e2e8f0',
                cursor: isPlacing ? 'crosshair' : 'grab',
                fontSize:12, userSelect:'none',
              }}>
              <span style={{ fontSize:10, color:'#cbd5e1', cursor:'grab', marginRight:2 }}>⠿</span>
              <span style={{ fontSize:12, color: ic.color, fontWeight:700,
                             minWidth:14, textAlign:'center' }}>{ic.icon}</span>
              <span style={{ flex:1, color:'#0f1923', overflow:'hidden',
                             textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                {obj.label || obj.field_code || obj.type}
              </span>
            </div>
            {/* Drop indicator after each object */}
            <DropLine idx={i + 1} />
          </React.Fragment>
        );
      })}

      {/* Placing hint */}
      {isPlacing && (
        <div style={{ padding:'6px', textAlign:'center', fontSize:11,
                      color:'#2563eb', opacity:0.7 }}>
          <span style={{ fontSize:14 }}>{oi?.icon}</span>
          <span style={{ display:'block' }}>click to place</span>
        </div>
      )}

      {/* Empty state */}
      {!isPlacing && colObjs.length === 0 && (
        <div style={{ padding:'8px', textAlign:'center', fontSize:11,
                      color:'#cbd5e1' }}>empty</div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────
// RowBuilder — one row with its cols
// ─────────────────────────────────────────────
function RowBuilder({ row, pageId, objects, selectedId, placingType, onPlace, onSelectObj,
                      onSelectCol, onDeleteRow, onSplitCol,
                      dragOver, onDragStart, onColMouseMove, onColMouseLeave }) {
  return (
    <div style={{ marginBottom:8 }}>
      {/* Row header */}
      <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:4 }}>
        <span style={{ fontSize:10, color:'#94a3b8', fontFamily:'DM Mono, monospace',
                       minWidth:40 }}>row</span>
        <button onClick={() => onSplitCol(pageId, row.id)}
          style={{ fontSize:11, padding:'2px 8px', background:'#fff',
                   border:'1px solid #e2e8f0', borderRadius:4, color:'#374151',
                   cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}
          title="Split widest column">
          + col
        </button>
        <button onClick={() => onDeleteRow(pageId, row.id)}
          style={{ fontSize:11, padding:'2px 8px', background:'#fff',
                   border:'1px solid #fecaca', borderRadius:4, color:'#dc2626',
                   cursor:'pointer', fontFamily:'DM Sans, sans-serif' }}>
          ✕ row
        </button>
        <span style={{ fontSize:10, color:'#e2e8f0' }}>
          {row.cols.map(c => c.width).join(' + ')} = {row.cols.reduce((s,c) => s+(c.width||0), 0)}/12
        </span>
      </div>

      {/* Cols */}
      <div style={{ display:'flex', gap:6 }}>
        {row.cols.map(col => {
          const sibTotal = row.cols
            .filter(c => c.id !== col.id)
            .reduce((s, c) => s + (c.width || 0), 0);
          return (
            <ColDropZone key={col.id}
              col={col}
              objects={objects}
              selectedId={selectedId}
              placingType={placingType}
              siblingTotal={sibTotal}
              onPlace={onPlace}
              onSelectObj={onSelectObj}
              onSelectCol={onSelectCol}
              dragOver={dragOver}
              onDragStart={onDragStart}
              onColMouseMove={onColMouseMove}
              onColMouseLeave={onColMouseLeave}
            />
          );
        })}
      </div>
    </div>
  );
}

const LAYOUT_FONTS = [
  { v:"'Segoe UI', sans-serif",        l:'Segoe UI'      },
  { v:"'DM Sans', sans-serif",         l:'DM Sans'       },
  { v:"'Inter', sans-serif",           l:'Inter'         },
  { v:"'Roboto', sans-serif",          l:'Roboto'        },
  { v:"'Open Sans', sans-serif",       l:'Open Sans'     },
  { v:"'Lato', sans-serif",            l:'Lato'          },
  { v:"'Poppins', sans-serif",         l:'Poppins'       },
  { v:"'Nunito', sans-serif",          l:'Nunito'        },
  { v:"'Raleway', sans-serif",         l:'Raleway'       },
  { v:"'Merriweather', serif",         l:'Merriweather'  },
  { v:"'Georgia', serif",              l:'Georgia'       },
  { v:"'DM Mono', monospace",          l:'DM Mono'       },
];

// ─────────────────────────────────────────────
// TabsInspector — edit tab style, page names, sticky
// ─────────────────────────────────────────────
function TabsInspector({ detail, onUpdate }) {
  const [local,   setLocal]   = React.useState(() => JSON.parse(JSON.stringify(detail)));
  const [dragIdx, setDragIdx] = React.useState(null);
  const [overIdx, setOverIdx] = React.useState(null);
  const dragRef = React.useRef(null);

  React.useEffect(() => {
    setLocal(JSON.parse(JSON.stringify(detail)));
  }, [detail?.id]);

  function emit(updated) { setLocal(updated); onUpdate(updated); }

  function setTop(key, val) {
    emit({ ...local, [key]: val });
  }

  function renamePage(pageId, name) {
    emit({ ...local, layout: local.layout.map(p => p.id === pageId ? { ...p, name } : p) });
  }

  function deletePage(pageId) {
    if (local.layout.length <= 1) return;
    emit({ ...local, layout: local.layout.filter(p => p.id !== pageId)
                               .map((p, i) => ({ ...p, order: i + 1 })) });
  }

  function addPage() {
    const newPage = {
      id:    'page_' + Math.random().toString(36).substr(2, 9),
      name:  `Page ${local.layout.length + 1}`,
      order: local.layout.length + 1,
      rows:  [],
    };
    emit({ ...local, layout: [...local.layout, newPage] });
  }

  // Drag sort
  function onDragStart(idx) { dragRef.current = idx; setDragIdx(idx); }
  function onDragEnter(idx) { setOverIdx(idx); }
  function onDragEnd() {
    if (dragRef.current !== null && overIdx !== null && dragRef.current !== overIdx) {
      const next = [...local.layout];
      const [moved] = next.splice(dragRef.current, 1);
      next.splice(overIdx, 0, moved);
      next.forEach((p, i) => { p.order = i + 1; });
      emit({ ...local, layout: next });
    }
    dragRef.current = null; setDragIdx(null); setOverIdx(null);
  }

  const lbl = txt => <div style={SDS.inspLabel}>{txt}</div>;
  const toggle = (key, opts) => (
    <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6,
                  overflow:'hidden', marginBottom:8 }}>
      {opts.map(o => (
        <button key={String(o.v)} type="button" onClick={() => setTop(key, o.v)}
          style={{ flex:1, padding:'5px 4px', border:'none', fontSize:11, fontWeight:500,
                   cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                   background: local[key]===o.v ? '#0f1923' : '#fff',
                   color:      local[key]===o.v ? '#fff'    : '#64748b' }}>
          {o.l}
        </button>
      ))}
    </div>
  );

  return (
    <div style={SDS.inspPanel}>
      <div style={SDS.inspHeaderRow}>
        <div style={{ display:'flex', alignItems:'center', gap:4 }}>
          <span style={SDS.inspHeader}>Tabs</span>
          {typeof KBIcon !== 'undefined' && <KBIcon kbKey="design.tabs.inspector" />}
        </div>
        <span style={SDS.inspBadge('#7c3aed')}>✏️</span>
      </div>

      {lbl('Tab Style')}
      {toggle('tab_style', [
        { v:'underline', l:'Underline' },
        { v:'tabs',      l:'Tabs'      },
        { v:'pills',     l:'Pills'     },
      ])}

      {lbl('Tabs')}
      <div style={{ border:'1px solid #e2e8f0', borderRadius:7, overflow:'hidden',
                    marginBottom:6 }}>
        {local.layout.map((page, idx) => (
          <div key={page.id} draggable
            onDragStart={() => onDragStart(idx)}
            onDragEnter={() => onDragEnter(idx)}
            onDragEnd={onDragEnd}
            onDragOver={e => e.preventDefault()}
            style={{ display:'flex', alignItems:'center', gap:4, padding:'4px 6px',
                     borderTop: idx > 0 ? '1px solid #f1f5f9' : 'none',
                     background: overIdx===idx && dragIdx!==idx ? '#eff6ff' : '#fff',
                     opacity: dragIdx===idx ? 0.4 : 1,
                     cursor:'grab' }}>
            {/* Grip */}
            <span style={{ color:'#cbd5e1', fontSize:13, flexShrink:0,
                           cursor:'grab', userSelect:'none', width:14,
                           textAlign:'center' }}>⠿</span>
            {/* Name input */}
            <input value={page.name}
              onChange={e => renamePage(page.id, e.target.value)}
              style={{ flex:1, minWidth:0, padding:'4px 6px',
                       border:'1.5px solid #e2e8f0', borderRadius:5,
                       fontSize:12, fontFamily:'DM Sans, sans-serif',
                       outline:'none', color:'#0f1923', background:'#fff' }} />
            {/* Delete */}
            <button onClick={() => deletePage(page.id)}
              disabled={local.layout.length <= 1}
              title="Delete tab"
              style={{ flexShrink:0, width:20, height:20,
                       display:'flex', alignItems:'center', justifyContent:'center',
                       background:'none', border:'none',
                       color: local.layout.length <= 1 ? '#e2e8f0' : '#dc2626',
                       cursor: local.layout.length <= 1 ? 'not-allowed' : 'pointer',
                       fontSize:14, padding:0, lineHeight:1 }}>✕</button>
          </div>
        ))}
      </div>

      <button onClick={addPage}
        style={{ width:'100%', padding:'6px', background:'#fff',
                 border:'1.5px dashed #e2e8f0', borderRadius:6, fontSize:12,
                 color:'#2563eb', cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                 marginBottom:12 }}>
        + Add Tab
      </button>

      <label style={{ display:'flex', alignItems:'flex-start', gap:8, fontSize:12,
                      color:'#374151', cursor:'pointer', lineHeight:1.5 }}>
        <input type="checkbox"
          checked={local.tab_sticky === true || local.tab_sticky === undefined}
          onChange={e => setTop('tab_sticky', e.target.checked)}
          style={{ marginTop:2, width:14, height:14, cursor:'pointer', flexShrink:0 }} />
        Sticky (stays visible while scrolling)
      </label>
    </div>
  );
}

// ─────────────────────────────────────────────
// LayoutInspector — nav colors, font, width, nav_data
// Overrides app theme when set
// ─────────────────────────────────────────────
function LayoutInspector({ detail, onUpdate }) {
  const [local, setLocal] = React.useState(() => JSON.parse(JSON.stringify(detail)));

  React.useEffect(() => {
    setLocal(JSON.parse(JSON.stringify(detail)));
  }, [detail?.id]);

  function set(key, val) {
    const updated = { ...local, [key]: val };
    setLocal(updated);
    onUpdate(updated);
  }

  function clear(key) {
    const updated = { ...local };
    delete updated[key];
    setLocal(updated);
    onUpdate(updated);
  }

  const lbl = txt => <div style={SDS.inspLabel}>{txt}</div>;

  return (
    <div style={SDS.inspPanel}>
      <div style={SDS.inspHeaderRow}>
        <div style={{ display:'flex', alignItems:'center', gap:4 }}>
          <span style={SDS.inspHeader}>Layout</span>
          {typeof KBIcon !== 'undefined' && <KBIcon kbKey="design.layout.inspector" />}
        </div>
        <span style={SDS.inspBadge('#0891b2')}>⚙️</span>
      </div>

      <div style={{ fontSize:11, color:'#94a3b8', marginBottom:4, lineHeight:1.5,
                    background:'#f8fafc', padding:'6px 8px', borderRadius:5,
                    border:'1px solid #f1f5f9' }}>
        Set values override the app theme for this table only. Clear to inherit.
      </div>

      <ColorPicker label="Nav Background"
        value={local.nav_bg || ''}
        onChange={v => set('nav_bg', v)} />
      {local.nav_bg
        ? <button onClick={() => clear('nav_bg')} style={SDS.inspClearBtn}>
            ✕ clear — inherit from theme
          </button>
        : <div style={{ fontSize:10, color:'#94a3b8', marginBottom:8, fontStyle:'italic' }}>
            from theme
          </div>
      }

      <ColorPicker label="Nav Text Color"
        value={local.nav_color || ''}
        onChange={v => set('nav_color', v)} />
      {local.nav_color
        ? <button onClick={() => clear('nav_color')} style={SDS.inspClearBtn}>
            ✕ clear — inherit from theme
          </button>
        : <div style={{ fontSize:10, color:'#94a3b8', marginBottom:8, fontStyle:'italic' }}>
            from theme
          </div>
      }

      {lbl('Nav Data')}
      <input value={local.nav_data || ''}
        onChange={e => set('nav_data', e.target.value)}
        placeholder="{f1} {f2}"
        style={SDS.inspInput} />
      <div style={{ fontSize:10, color:'#94a3b8', marginBottom:8 }}>
        Use {'{f1}'}, {'{f2}'} etc. for field values
      </div>

      {lbl('Layout Font')}
      <select value={local.font || ''}
        onChange={e => set('font', e.target.value)}
        style={{ ...SDS.inspInput, background:'#fff' }}>
        <option value="">— inherit from theme —</option>
        {LAYOUT_FONTS.map(f => (
          <option key={f.v} value={f.v}>{f.l}</option>
        ))}
      </select>

      {lbl('Font Size')}
      <select value={local.font_size || ''}
        onChange={e => set('font_size', Number(e.target.value) || '')}
        style={{ ...SDS.inspInput, background:'#fff' }}>
        <option value="">— inherit from theme —</option>
        {[10,12,14,16,18,20,24].map(n => (
          <option key={n} value={n}>{n}px</option>
        ))}
      </select>

      {lbl('Layout Width')}
      <div style={{ display:'flex', border:'1.5px solid #e2e8f0', borderRadius:6,
                    overflow:'hidden', marginBottom:8 }}>
        {[['fixed','Fixed (1200px)'],['fluid','Full Width']].map(([v,l]) => (
          <button key={v} type="button"
            onClick={() => set('width', v)}
            style={{ flex:1, padding:'5px 4px', border:'none', fontSize:11, fontWeight:500,
                     cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                     background: (local.width||'fixed')===v ? '#0f1923' : '#fff',
                     color:      (local.width||'fixed')===v ? '#fff'    : '#64748b' }}>
            {l}
          </button>
        ))}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────
// Main DEVDesign component (V7)
// ─────────────────────────────────────────────
function DEVDesign({ app, jwt }) {
  // ── Table selection ──
  const [tables,         setTables]         = React.useState([]);
  const [activeTableOid, setActiveTableOid] = React.useState(null);
  const [schema,         setSchema]         = React.useState(null); // current table + fields

  // ── Sub-tab ──
  const [subTab, setSubTab] = React.useState('detail');

  // ── Design data ──
  const [detail,   setDetail]   = React.useState(null);
  const [listview, setListview] = React.useState(null);
  const [objects,  setObjects]  = React.useState([]);
  const [theme,    setTheme]    = React.useState(null);

  // ── Active page ──
  const [activePage, setActivePage] = React.useState(null); // page ID in layout[]

  // ── Inspector panel mode ──
  // 'object' | 'col' | 'tabs' | 'layout'
  const [inspMode, setInspMode] = React.useState('object');

  // ── Selection ──
  const [selectedObj,  setSelectedObj]  = React.useState(null);
  const [placingType,  setPlacingType]  = React.useState(null);

  // ── Drag state ──
  const dragState   = React.useRef(null);   // { objId, fromColId }
  const dragOverRef = React.useRef(null);   // { colId, idx }
  const [dragOver,  setDragOver]  = React.useState(null);

  // ── Dirty tracking ──
  const [dirty,         setDirty]         = React.useState(false);
  const [listviewDirty, setListviewDirty] = React.useState(false);
  const [themeDirty,    setThemeDirty]    = React.useState(false);
  const [saving,        setSaving]        = React.useState(false);
  const [saveMsg,       setSaveMsg]       = React.useState(null);

  // ── Widescreen ──
  const [wide, setWide] = React.useState(false);

  function toggleWide() {
    const el = document.getElementById('do-design-panel');
    if (!el) return;
    if (!wide) {
      el.style.position    = 'fixed';
      el.style.top         = '54px';
      el.style.left        = '0';
      el.style.right       = '0';
      el.style.bottom      = '0';
      el.style.zIndex      = '500';
      el.style.borderRadius = '0';
      el.style.marginTop   = '0';
      el.style.overflowY   = 'auto';
    } else {
      el.style.position = el.style.top = el.style.left =
        el.style.right  = el.style.bottom = el.style.zIndex =
        el.style.borderRadius = el.style.marginTop = el.style.overflowY = '';
    }
    setWide(w => !w);
  }

  // ── Home Page card selection (for inspector) ──
  const [selectedTable, setSelectedTable] = React.useState(null);

  // ── Misc ──
  const [loading,   setLoading]   = React.useState(false);
  const [error,     setError]     = React.useState(null);
  const [confirm,   setConfirm]   = React.useState(null);
  const [renamePage, setRenamePage] = React.useState(null); // { pageId, name }
  const [showIconBrowser, setShowIconBrowser] = React.useState(null); // callback fn

  // ── Drag handlers ──
  function handleDragStart(e, obj) {
    e.preventDefault();
    dragState.current = { objId: obj.id, fromColId: obj.col_id };
    document.body.style.cursor = 'grabbing';

    function onMouseUp() {
      document.body.style.cursor = '';
      document.removeEventListener('mouseup', onMouseUp);
      if (dragState.current && dragOverRef.current) {
        commitDrop(dragState.current, dragOverRef.current);
      }
      dragState.current   = null;
      dragOverRef.current = null;
      setDragOver(null);
    }
    document.addEventListener('mouseup', onMouseUp);
  }

  function handleColMouseMove(e, colId) {
    if (!dragState.current) return;
    const items = Array.from(e.currentTarget.querySelectorAll('[data-objid]'));
    let idx = items.length;
    for (let i = 0; i < items.length; i++) {
      const rect = items[i].getBoundingClientRect();
      if (e.clientY < rect.top + rect.height / 2) { idx = i; break; }
    }
    const next = { colId, idx };
    dragOverRef.current = next;
    setDragOver(prev => {
      if (prev?.colId === colId && prev?.idx === idx) return prev;
      return next;
    });
  }

  function handleColMouseLeave() {
    if (!dragState.current) return;
    dragOverRef.current = null;
    setDragOver(null);
  }

  function commitDrop(ds, dov) {
    const { objId, fromColId } = ds;
    const { colId: toColId, idx: toIdx } = dov;

    setObjects(prev => {
      const obj = prev.find(o => o.id === objId);
      if (!obj) return prev;

      // Build target col list without the dragged object
      const toColObjs = prev
        .filter(o => o.col_id === toColId && o.status !== 0 && o.id !== objId)
        .sort((a, b) => (a.order||0) - (b.order||0));

      // Insert dragged object at target index
      const insertAt = Math.max(0, Math.min(toIdx, toColObjs.length));
      toColObjs.splice(insertAt, 0, { ...obj, col_id: toColId });

      // Build source col list (only needed if moving between cols)
      const fromColObjs = fromColId !== toColId
        ? prev
            .filter(o => o.col_id === fromColId && o.status !== 0 && o.id !== objId)
            .sort((a, b) => (a.order||0) - (b.order||0))
        : null;

      // Apply new order values
      return prev.map(o => {
        const toPos = toColObjs.findIndex(t => t.id === o.id);
        if (toPos !== -1) return { ...o, col_id: toColId, order: toPos + 1 };

        if (fromColObjs) {
          const fromPos = fromColObjs.findIndex(f => f.id === o.id);
          if (fromPos !== -1) return { ...o, order: fromPos + 1 };
        }
        return o;
      });
    });

    setDirty(true);
  }

  // ── Load tables on mount ──
  React.useEffect(() => {
    loadTables();
  }, [app.app_id]);

  // ── Escape cancels placing ──
  React.useEffect(() => {
    function onKey(e) {
      if (e.key === 'Escape') { setPlacingType(null); setSelectedObj(null); }
    }
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  async function loadTables() {
    setLoading(true);
    try {
      const res  = await apiFetch(`${API_BASE}/v7/dev/apps/${app.app_id}/tables`);
      const data = await res.json();
      if (data.status !== 'ok') throw new Error(data.message);
      const rows = data.tables || [];
      setTables(rows);
      if (rows.length) {
        const first = rows[0];
        setActiveTableOid(first._oid);
        await loadDesign(first);
      }
    } catch(err) { setError(err.message); }
    setLoading(false);
  }

  async function loadDesign(tableRow) {
    setSchema(tableRow);
    setDetail(null); setListview(null); setObjects([]); setTheme(null);
    setSelectedObj(null); setPlacingType(null); setDirty(false);
    setListviewDirty(false); setThemeDirty(false);

    const tNum = tableRow.tNum || (tableRow.code || '').replace('t', '');
    try {
      const res  = await apiFetch(`${API_BASE}/v7/dev/apps/${app.app_id}/design/${tNum}`);
      const data = await res.json();
      if (data.status !== 'ok') throw new Error(data.message);

      // Detail layout
      const det = data.detail || makeDefaultDetail(tableRow);
      setDetail(det);
      const firstPage = det.layout?.[0]?.id || null;
      setActivePage(firstPage);

      // Other
      setListview(data.listview || makeDefaultListview(tableRow));
      setObjects(data.objects  || []);
      setTheme(data.theme      || {});
    } catch(err) { setError(err.message); }
  }

  function makeDefaultDetail(t) {
    const pageId = newPageId();
    return {
      schema_version: 1,
      group: 'design', type: 'detail',
      table_id: t._oid || t.id,
      table:    t.code,
      name:     t.name,
      nav_data: '',
      width:    'fixed',
      tab_style:'underline',
      tab_sticky: true,
      layout: [{
        id:    pageId,
        name:  'Info',
        order: 1,
        rows:  [],
      }],
      status: 1,
    };
  }

  function makeDefaultListview(t) {
    const fields = (t.fields || []).filter(f => f.status !== 0);
    return {
      schema_version: 1,
      group:     'design', type: 'listview',
      table_id:  t._oid || t.id,
      table:     t.code,
      name:      t.name,
      display:   'list',
      pagesize:  25,
      sortfield: 'r_auto',
      sortdir:   'asc',
      editable:  false,
      fields:    fields.slice(0, 4).map(f => ({
        field_id: f.id, field_code: f.code, label: f.name, width: 2,
      })),
      status: 1,
    };
  }

  // ── Table switch ──
  async function switchTable(t) {
    if (dirty || listviewDirty || themeDirty) {
      setConfirm({
        msg:    'Unsaved changes',
        detail: 'Switch tables without saving?',
        okLabel:'Switch anyway',
        okDanger: true,
        onOk: () => { setActiveTableOid(t._oid); loadDesign(t); },
      });
      return;
    }
    setActiveTableOid(t._oid);
    await loadDesign(t);
  }

  // ── Page management ──
  function addPage() {
    const det = JSON.parse(JSON.stringify(detail));
    const newPage = {
      id:    newPageId(),
      name:  `Page ${det.layout.length + 1}`,
      order: det.layout.length + 1,
      rows:  [],
    };
    det.layout.push(newPage);
    setDetail(det); setDirty(true);
    setActivePage(newPage.id);
  }

  function deletePage(pageId) {
    const det = JSON.parse(JSON.stringify(detail));
    const page = det.layout.find(p => p.id === pageId);
    if (!page) return;

    // Collect all col IDs from this page
    const colIds = page.rows.flatMap(r => r.cols.map(c => c.id));
    // Soft-delete objects in those cols
    const updatedObjects = objects.map(o =>
      colIds.includes(o.col_id) ? { ...o, status: 0 } : o
    );
    setObjects(updatedObjects);

    det.layout = det.layout.filter(p => p.id !== pageId);
    det.layout.forEach((p, i) => p.order = i + 1);
    setDetail(det); setDirty(true);
    setActivePage(det.layout[0]?.id || null);
    if (selectedObj?.col_id && colIds.includes(selectedObj.col_id)) setSelectedObj(null);
  }

  function renamingPage(pageId) {
    const page = detail.layout.find(p => p.id === pageId);
    setRenamePage({ pageId, name: page?.name || '' });
  }

  function commitRenamePage() {
    if (!renamePage) return;
    const det = JSON.parse(JSON.stringify(detail));
    const page = det.layout.find(p => p.id === renamePage.pageId);
    if (page) page.name = renamePage.name;
    setDetail(det); setDirty(true);
    setRenamePage(null);
  }

  // ── Row management ──
  function addRow(pageId) {
    const det = JSON.parse(JSON.stringify(detail));
    const page = det.layout.find(p => p.id === pageId);
    if (!page) return;
    const rowId = newRowId();
    const colId = newColId();
    page.rows.push({
      id:    rowId,
      order: page.rows.length + 1,
      cols:  [{ id: colId, width: 12, order: 1 }],
    });
    setDetail(det); setDirty(true);
  }

  function deleteRow(pageId, rowId) {
    const det = JSON.parse(JSON.stringify(detail));
    const page = det.layout.find(p => p.id === pageId);
    if (!page) return;
    const row = page.rows.find(r => r.id === rowId);
    const colIds = row?.cols.map(c => c.id) || [];

    // Soft-delete objects in these cols
    const updatedObjects = objects.map(o =>
      colIds.includes(o.col_id) ? { ...o, status: 0 } : o
    );
    setObjects(updatedObjects);

    page.rows = page.rows.filter(r => r.id !== rowId);
    page.rows.forEach((r, i) => r.order = i + 1);
    setDetail(det); setDirty(true);
    if (colIds.includes(selectedObj?.col_id)) setSelectedObj(null);
  }

  // ── Col management ──
  function splitCol(pageId, rowId) {
    const det = JSON.parse(JSON.stringify(detail));
    const page = det.layout.find(p => p.id === pageId);
    const row  = page?.rows.find(r => r.id === rowId);
    if (!row) return;

    // Find widest col with width >= 2
    const widest = [...row.cols].sort((a,b) => (b.width||0) - (a.width||0))
      .find(c => (c.width||0) >= 2);
    if (!widest) return;

    const half1 = Math.floor(widest.width / 2);
    const half2 = widest.width - half1;
    widest.width = half1;
    const newCol = { id: newColId(), width: half2, order: row.cols.length + 1 };
    const idx = row.cols.findIndex(c => c.id === widest.id);
    row.cols.splice(idx + 1, 0, newCol);
    row.cols.forEach((c, i) => c.order = i + 1);
    setDetail(det); setDirty(true);
  }

  function updateColWidth(pageId, rowId, colId, newWidth) {
    const det = JSON.parse(JSON.stringify(detail));
    const page = det.layout.find(p => p.id === pageId);
    const row  = page?.rows.find(r => r.id === rowId);
    const col  = row?.cols.find(c => c.id === colId);
    if (!col) return;
    col.width = newWidth;
    setDetail(det); setDirty(true);
  }

  function deleteCol(pageId, rowId, colId) {
    const det = JSON.parse(JSON.stringify(detail));
    const page = det.layout.find(p => p.id === pageId);
    const row  = page?.rows.find(r => r.id === rowId);
    if (!row) return;

    // Redistribute width to adjacent col
    const col = row.cols.find(c => c.id === colId);
    const remaining = row.cols.filter(c => c.id !== colId);
    if (remaining.length > 0) remaining[0].width += (col?.width || 0);
    row.cols = remaining;
    row.cols.forEach((c, i) => c.order = i + 1);

    const updatedObjects = objects.map(o =>
      o.col_id === colId ? { ...o, status: 0 } : o
    );
    setObjects(updatedObjects);
    setDetail(det); setDirty(true);
    if (selectedObj?.col_id === colId) setSelectedObj(null);
    if (selectedObj?._isCol && selectedObj?.id === colId) setSelectedObj(null);
  }

  // ── Object placement ──
  function placeObject(colId) {
    if (!placingType) return;
    const newObj = {
      id:         tempId(),
      group:      'object',
      type:       placingType,
      col_id:     colId,
      order:      objects.filter(o => o.col_id === colId && o.status !== 0).length + 1,
      label:      '',
      field_id:   '',
      field_code: '',
      status:     1,
    };
    setObjects(prev => [...prev, newObj]);
    setPlacingType(null);
    setSelectedObj(newObj);
    setDirty(true);
  }

  // ── Object inspector callbacks ──
  function handleUpdateObj(updated) {
    setObjects(prev => prev.map(o => o.id === updated.id ? { ...updated } : o));
    setSelectedObj(updated);
    setDirty(true);
  }

  function handleDeleteObj(obj) {
    setObjects(prev => prev.map(o => o.id === obj.id ? { ...o, status: 0 } : o));
    setSelectedObj(null);
    setDirty(true);
  }

  // ── Col inspector callbacks ──
  function selectCol(col, siblingTotal, pageId, rowId) {
    setSelectedObj({ ...col, _isCol: true, _siblingTotal: siblingTotal,
                     _pageId: pageId, _rowId: rowId });
  }

  function handleUpdateCol(updated) {
    if (!updated._isCol) return;
    updateColWidth(updated._pageId, updated._rowId, updated.id, updated.width);
    setSelectedObj(null);
  }

  function handleDeleteCol(updated) {
    if (!updated._isCol) return;
    deleteCol(updated._pageId, updated._rowId, updated.id);
  }

  // ── Save ──
  async function handleSave() {
    setSaving(true); setSaveMsg(null);
    const tNum = schema?.tNum || (schema?.code || '').replace('t', '');

    try {
      // 1. Save detail layout
      if (dirty) {
        const res  = await apiFetch(
          `${API_BASE}/v7/dev/apps/${app.app_id}/design/${tNum}/detail/save`,
          { method:'POST', body: JSON.stringify(detail) }
        );
        const data = await res.json();
        if (data.status !== 'ok') throw new Error('Detail save: ' + data.message);
      }

      // 2. Save objects
      const newObjs        = objects.filter(o =>  String(o.id).startsWith('temp_') && o.status !== 0);
      const existingActive = objects.filter(o => !String(o.id).startsWith('temp_') && o.status !== 0);
      const toDelete       = objects.filter(o => !String(o.id).startsWith('temp_') && o.status === 0);
      const allToSave      = [...existingActive, ...newObjs];

      if (allToSave.length) {
        const res  = await apiFetch(
          `${API_BASE}/v7/dev/apps/${app.app_id}/design/${tNum}/objects/save`,
          { method:'POST', body: JSON.stringify({ objects: allToSave }) }
        );
        const data = await res.json();
        if (data.status !== 'ok') throw new Error('Objects save: ' + data.message);

        // Replace temp IDs with real UUIDs
        if (data.idMap && Object.keys(data.idMap).length) {
          setObjects(prev => prev.map(o => {
            const realId = data.idMap[o.id];
            return realId ? { ...o, id: realId } : o;
          }));
        }
      }

      if (toDelete.length) {
        await apiFetch(
          `${API_BASE}/v7/dev/apps/${app.app_id}/design/${tNum}/objects/delete`,
          { method:'POST', body: JSON.stringify({ ids: toDelete.map(o => o.id) }) }
        );
      }

      // 3. Save listview
      if (listviewDirty && listview) {
        const res  = await apiFetch(
          `${API_BASE}/v7/dev/apps/${app.app_id}/design/${tNum}/listview/save`,
          { method:'POST', body: JSON.stringify(listview) }
        );
        const data = await res.json();
        if (data.status !== 'ok') throw new Error('Listview save: ' + data.message);
      }

      // 4. Save theme
      if (themeDirty && theme) {
        await apiFetch(
          `${API_BASE}/v7/dev/apps/${app.app_id}/design/theme/save`,
          { method:'POST', body: JSON.stringify({ theme }) }
        );
      }

      // 5. Save home config (table visibility/sort)
      if (dirty && subTab === 'home') {
        await apiFetch(
          `${API_BASE}/v7/dev/apps/${app.app_id}/design/home/save`,
          { method:'POST', body: JSON.stringify({ tables }) }
        );
      }

      setDirty(false); setListviewDirty(false); setThemeDirty(false);
      setSaveMsg('Saved ✓');
      setTimeout(() => setSaveMsg(null), 2500);
    } catch(err) {
      setSaveMsg('Error: ' + err.message);
    }
    setSaving(false);
  }

  // ── Derived ──
  const activePageObj = detail?.layout?.find(p => p.id === activePage);
  const isDirty = dirty || listviewDirty || themeDirty;
  const currentTNum = schema?.tNum || (schema?.code || '').replace('t', '');

  if (loading) return <div style={SDS.center}>Loading…</div>;
  if (error)   return <div style={SDS.errorBox}>{error}</div>;
  if (!tables.length) return (
    <div style={SDS.center}>
      No tables yet — create one in the Database tab first.
    </div>
  );

  return (
    <div id="do-design-panel"
      style={{ border:'1px solid #e2e8f0', borderRadius:8, overflow:'hidden',
               marginTop:8, background:'#fff', fontFamily:'DM Sans, sans-serif' }}>
      {confirm && <DOConfirm {...confirm} onClose={() => setConfirm(null)} />}

      {/* ── Toolbar ── */}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between',
                    padding:'8px 14px', borderBottom:'1px solid #e2e8f0',
                    background:'#f8fafc', gap:8, flexWrap:'wrap' }}>

        <div style={{ display:'flex', alignItems:'center', gap:6, flexWrap:'wrap' }}>

          {/* Home Page */}
          <button onClick={() => { setSubTab('home'); setSelectedObj(null); setSelectedTable(null); }}
            style={{ ...SDS.toolBtn, ...(subTab==='home' ? SDS.toolBtnActive : {}) }}>
            🏠 <span>Home Page</span>
          </button>

          {/* Tables dropdown */}
          <select
            value={subTab === 'detail' || subTab === 'listview' ? (activeTableOid || '') : ''}
            onChange={e => {
              if (!e.target.value) return;
              const t = tables.find(t => t._oid === e.target.value);
              if (t) {
                switchTable(t);
                setSubTab(subTab === 'listview' ? 'listview' : 'detail');
              }
            }}
            style={{ padding:'5px 10px',
                     border: (subTab==='detail'||subTab==='listview')
                       ? '2px solid #0f1923' : '1.5px solid #e2e8f0',
                     borderRadius:6, fontSize:13,
                     fontFamily:'DM Sans, sans-serif', background:'#fff',
                     cursor:'pointer', outline:'none', color:'#0f1923' }}>
            <option value="">🗃️ Tables</option>
            {tables.map(t => (
              <option key={t._oid} value={t._oid}>
                {t.icon ? t.icon + ' ' : ''}{t.name} ({t.code})
              </option>
            ))}
          </select>

          {/* List View / Detail View — only when a table is selected */}
          {(subTab === 'detail' || subTab === 'listview') && activeTableOid && (
            <div style={{ display:'flex', border:'1.5px solid #e2e8f0',
                          borderRadius:6, overflow:'hidden' }}>
              {[['listview','List View'],['detail','Detail View']].map(([v,l]) => (
                <button key={v} onClick={() => setSubTab(v)}
                  style={{ padding:'5px 14px', border:'none',
                           borderRight:'1px solid #e2e8f0',
                           fontSize:12, fontWeight: subTab===v ? 700 : 500,
                           cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                           background: subTab===v ? '#f1f5f9' : '#fff',
                           color:'#0f1923' }}>
                  {l}
                </button>
              ))}
            </div>
          )}

          {/* Separator */}
          <div style={{ width:1, height:22, background:'#e2e8f0', margin:'0 2px' }} />

          {/* Theme */}
          <button onClick={() => { setSubTab('theme'); setSelectedObj(null); }}
            style={{ ...SDS.toolBtn, ...(subTab==='theme' ? SDS.toolBtnActive : {}) }}>
            🎨 <span>Theme</span>
          </button>

          {/* Value Lists */}
          <button onClick={() => { setSubTab('valuelists'); setSelectedObj(null); }}
            style={{ ...SDS.toolBtn, ...(subTab==='valuelists' ? SDS.toolBtnActive : {}) }}>
            🗒️ <span>Value Lists</span>
          </button>
        </div>

        {/* Right side: dirty + save + widescreen */}
        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          {saveMsg && (
            <span style={{ fontSize:12,
                           color: saveMsg.startsWith('Error') ? '#dc2626' : '#22c55e',
                           fontWeight:500 }}>
              {saveMsg}
            </span>
          )}
          {isDirty && !saveMsg && (
            <span style={{ fontSize:12, color:'#f59e0b', fontWeight:500 }}>● Unsaved</span>
          )}
          <button onClick={handleSave} disabled={saving || !isDirty}
            style={{ padding:'5px 14px',
                     background: isDirty ? '#0f1923' : '#94a3b8',
                     color:'#fff', border:'none', borderRadius:6,
                     fontSize:12, fontWeight:600, cursor: isDirty ? 'pointer' : 'default',
                     fontFamily:'DM Sans, sans-serif' }}>
            {saving ? 'Saving…' : 'Save'}
          </button>
          {/* Widescreen toggle */}
          <button onClick={toggleWide}
            title={wide ? 'Exit fullscreen' : 'Expand to full width'}
            style={{ padding:'5px 9px', border:'1.5px solid #e2e8f0', borderRadius:6,
                     fontSize:15, background:'#fff', cursor:'pointer',
                     color: wide ? '#2563eb' : '#374151' }}>
            {wide ? '⊠' : '⛶'}
          </button>
        </div>
      </div>

      {/* ══ DETAIL tab ══════════════════════════════════ */}
      {subTab === 'detail' && detail && (
        <div style={SDS.designShell}>

          {/* ── Left: Palette ── */}
          <div style={SDS.palette}>
            <div style={SDS.palTitle}>Objects</div>
            {PALETTE_GROUPS.map(group => (
              <div key={group.label} style={{ marginBottom:14 }}>
                <div style={SDS.palGroup}>{group.label}</div>
                {group.items.map(item => {
                  const isActive = placingType === item.type;
                  return (
                    <div key={item.type}
                      onClick={() => setPlacingType(isActive ? null : item.type)}
                      style={{
                        display:'flex', alignItems:'center', gap:8,
                        padding:'6px 10px', borderRadius:6, marginBottom:3,
                        cursor:'pointer', fontSize:12,
                        background: isActive ? '#eff6ff' : 'transparent',
                        border: isActive ? '1.5px solid #2563eb' : '1.5px solid transparent',
                        color: isActive ? '#2563eb' : '#374151',
                        transition:'all 0.1s',
                      }}>
                      <span style={{ fontSize:13, minWidth:16, textAlign:'center',
                                     color: item.color }}>{item.icon}</span>
                      <span>{item.label}</span>
                      {isActive && <span style={{ marginLeft:'auto', fontSize:10 }}>✓</span>}
                    </div>
                  );
                })}
              </div>
            ))}
            {placingType && (
              <div style={{ marginTop:8, padding:'8px 10px', background:'#fef9c3',
                            borderRadius:6, fontSize:11, color:'#92400e', lineHeight:1.5 }}>
                <strong>Placing: {placingType}</strong><br/>
                Click a column to place.<br/>ESC to cancel.
              </div>
            )}
          </div>

          {/* ── Center: Canvas ── */}
          <div style={SDS.canvas}>

            {/* Page tabs */}
            <div style={{ ...SDS.pageTabs, justifyContent:'space-between' }}>
              <div style={{ display:'flex', alignItems:'center', flex:1, overflowX:'auto' }}>
              {detail.layout.map(page => (
                <div key={page.id} style={{ display:'flex', alignItems:'center' }}>
                  {renamePage?.pageId === page.id ? (
                    <input autoFocus value={renamePage.name}
                      onChange={e => setRenamePage(p => ({ ...p, name: e.target.value }))}
                      onBlur={commitRenamePage}
                      onKeyDown={e => { if(e.key==='Enter') commitRenamePage();
                                        if(e.key==='Escape') setRenamePage(null); }}
                      style={{ padding:'4px 8px', border:'1.5px solid #2563eb', borderRadius:5,
                               fontSize:12, fontFamily:'DM Sans, sans-serif',
                               outline:'none', width:100 }} />
                  ) : (
                    <button
                      onClick={() => { setActivePage(page.id); setInspMode('object'); }}
                      onDoubleClick={() => renamingPage(page.id)}
                      style={{
                        padding:'6px 14px', border:'none',
                        borderBottom: activePage===page.id ? '2px solid #2563eb' : '2px solid transparent',
                        background:'none', fontSize:13, fontWeight:500, cursor:'pointer',
                        color: activePage===page.id ? '#0f1923' : '#64748b',
                        fontFamily:'DM Sans, sans-serif',
                      }}>
                      {page.name}
                    </button>
                  )}
                </div>
              ))}
              <button onClick={addPage}
                style={{ padding:'6px 10px', background:'none', border:'none',
                         color:'#2563eb', cursor:'pointer', fontSize:18,
                         fontFamily:'DM Sans, sans-serif', lineHeight:1 }}
                title="Add page">+</button>
              </div>

              {/* Tabs + Layout inspector icons */}
              <div style={{ display:'flex', alignItems:'center', gap:4,
                            paddingRight:8, flexShrink:0 }}>
                <button
                  title="Edit Tabs"
                  onClick={() => { setInspMode(inspMode==='tabs' ? 'object' : 'tabs'); setSelectedObj(null); }}
                  style={{ padding:'3px 7px', border:'1.5px solid',
                           borderColor: inspMode==='tabs' ? '#2563eb' : '#e2e8f0',
                           borderRadius:5, background: inspMode==='tabs' ? '#eff6ff' : '#fff',
                           cursor:'pointer', fontSize:14, lineHeight:1 }}>
                  ✏️
                </button>
                <button
                  title="Layout Settings"
                  onClick={() => { setInspMode(inspMode==='layout' ? 'object' : 'layout'); setSelectedObj(null); }}
                  style={{ padding:'3px 7px', border:'1.5px solid',
                           borderColor: inspMode==='layout' ? '#2563eb' : '#e2e8f0',
                           borderRadius:5, background: inspMode==='layout' ? '#eff6ff' : '#fff',
                           cursor:'pointer', fontSize:14, lineHeight:1 }}>
                  ⚙️
                </button>
              </div>
            </div>

            {/* Active page rows */}
            <div style={SDS.canvasBody}>
              {!activePageObj ? (
                <div style={SDS.center}>Select or add a page</div>
              ) : (
                <>
                  {activePageObj.rows.length === 0 && (
                    <div style={{ textAlign:'center', padding:'40px 0',
                                  color:'#94a3b8', fontSize:13 }}>
                      No rows yet — add one below
                    </div>
                  )}
                  {activePageObj.rows.map(row => (
                    <RowBuilder key={row.id}
                      row={row}
                      pageId={activePageObj.id}
                      objects={objects}
                      selectedId={selectedObj?.id}
                      placingType={placingType}
                      onPlace={placeObject}
                      onSelectObj={obj => { setSelectedObj(obj); setPlacingType(null); setInspMode('object'); }}
                      onSelectCol={(col, sibTotal) => {
                        selectCol(col, sibTotal, activePageObj.id, row.id);
                        setInspMode('col');
                      }}
                      onDeleteRow={() => setConfirm({
                        msg: 'Delete this row?',
                        detail: 'All columns and objects in this row will be removed.',
                        okDanger: true,
                        onOk: () => deleteRow(activePageObj.id, row.id),
                      })}
                      onSplitCol={() => splitCol(activePageObj.id, row.id)}
                      dragOver={dragOver}
                      onDragStart={handleDragStart}
                      onColMouseMove={handleColMouseMove}
                      onColMouseLeave={handleColMouseLeave}
                    />
                  ))}

                  {/* Add row */}
                  <button onClick={() => addRow(activePageObj.id)}
                    style={SDS.addRowBtn}>
                    + Add Row
                  </button>
                </>
              )}
            </div>
          </div>

          {/* ── Right: Inspector ── */}
          <div style={SDS.inspector}>
            {inspMode === 'tabs' && (
              <TabsInspector
                detail={detail}
                onUpdate={updated => { setDetail(updated); setDirty(true); }}
              />
            )}
            {inspMode === 'layout' && (
              <LayoutInspector
                detail={detail}
                onUpdate={updated => { setDetail(updated); setDirty(true); }}
              />
            )}
            {(inspMode === 'object' || inspMode === 'col') && (
              <Inspector
                selectedObj={selectedObj}
                fields={schema?.fields || []}
                app={app}
                onBrowseIcon={cb => setShowIconBrowser(() => cb)}
                onUpdate={updated => {
                  if (updated._isCol) handleUpdateCol(updated);
                  else handleUpdateObj(updated);
                }}
                onDelete={obj => {
                  if (obj._isCol) handleDeleteCol(obj);
                  else handleDeleteObj(obj);
                }}
              />
            )}
          </div>
        </div>
      )}

      {/* ══ LISTVIEW tab ════════════════════════════════ */}
      {subTab === 'listview' && schema && (
        <div style={{ marginTop:8 }}>
          <ListViewPanel
            key={activeTableOid}
            fields={schema.fields || []}
            cfg={listview}
            schema={schema}
            app={app}
            onChange={updated => {
              setListview(prev => ({ ...prev, ...updated }));
              setListviewDirty(true);
            }}
          />
        </div>
      )}

      {/* ══ HOME tab ════════════════════════════════════ */}
      {subTab === 'home' && (
        <div style={{ display:'flex', minHeight:500 }}>
          {/* Card canvas */}
          <div style={{ flex:1, padding:16, minWidth:0 }}>
            <HomeCanvas
              tables={tables}
              selectedOid={selectedTable?._oid}
              onSelectTable={t => setSelectedTable({ ...t, _isTable:true })}
              onReorder={updated => { setTables(updated); setDirty(true); }}
            />
          </div>
          {/* Inspector panel */}
          <div style={SDS.inspector}>
            <HomeInspector
              table={selectedTable}
              onUpdate={updated => {
                setTables(prev => prev.map(t =>
                  t._oid === updated._oid ? { ...t, ...updated } : t
                ));
                setSelectedTable(updated);
                setDirty(true);
              }}
            />
          </div>
        </div>
      )}

      {/* ══ THEME tab ════════════════════════════════════ */}
      {subTab === 'theme' && (
        <div style={{ marginTop:8 }}>
          {typeof ThemePanel !== 'undefined'
            ? <ThemePanel theme={theme || {}} onChange={t => { setTheme(t); setThemeDirty(true); }} />
            : <div style={{ color:'#94a3b8', textAlign:'center', padding:40 }}>
                Theme panel loading…
              </div>
          }
        </div>
      )}

      {/* ══ VALUE LISTS tab ══════════════════════════════ */}
      {subTab === 'valuelists' && (
        <div style={{ marginTop:8 }}>
          {typeof ValueListsPanel !== 'undefined'
            ? <ValueListsPanel app={app} />
            : <div style={{ color:'#94a3b8', textAlign:'center', padding:40 }}>
                Loading…
              </div>
          }
        </div>
      )}

      {/* ── Icon browser overlay ── */}
      {showIconBrowser && (
        <IconBrowser
          onSelect={val => { showIconBrowser(val); setShowIconBrowser(null); }}
          onClose={() => setShowIconBrowser(null)}
        />
      )}

      {/* ── Rename page modal ── */}
      {renamePage && (
        <div style={SDS.backdrop}
          onClick={() => setRenamePage(null)}>
          <div style={{ ...SDS.modal, maxWidth:360 }}
            onClick={e => e.stopPropagation()}>
            <div style={{ fontSize:15, fontWeight:700, marginBottom:12, color:'#0f1923' }}>
              Rename Page
            </div>
            <input autoFocus value={renamePage.name}
              onChange={e => setRenamePage(p => ({ ...p, name: e.target.value }))}
              onKeyDown={e => { if(e.key==='Enter') commitRenamePage(); }}
              style={{ width:'100%', padding:'8px 12px', border:'1.5px solid #e2e8f0',
                       borderRadius:7, fontSize:13, fontFamily:'DM Sans, sans-serif',
                       outline:'none', marginBottom:14, boxSizing:'border-box' }} />
            <div style={{ display:'flex', gap:8, justifyContent:'flex-end' }}>
              <button onClick={() => setRenamePage(null)}
                style={SDS.cancelBtn}>Cancel</button>
              <button onClick={commitRenamePage}
                style={SDS.submitBtn}>Rename</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────
// Styles — prefix SDS (Design Styles V7)
// ─────────────────────────────────────────────
const SDS = {
  // ── Toolbar buttons ──
  toolBtn:     { padding:'5px 12px', border:'1.5px solid #e2e8f0', borderRadius:6,
                 fontSize:12, fontWeight:500, background:'#fff', color:'#0f1923',
                 cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                 display:'flex', alignItems:'center', gap:5 },
  toolBtnActive: { border:'2px solid #0f1923', fontWeight:600 },

  // ── Layout ──
  topBar:      { display:'flex', alignItems:'center', justifyContent:'space-between',
                 marginBottom:16, gap:12, flexWrap:'wrap' },
  subTabs:     { display:'flex', borderBottom:'2px solid #e2e8f0', gap:0 },
  subTab:      { padding:'8px 14px', background:'none', border:'none',
                 borderBottom:'2px solid transparent', marginBottom:'-2px',
                 fontSize:13, fontWeight:500, color:'#64748b', cursor:'pointer',
                 fontFamily:'DM Sans, sans-serif', transition:'color 0.15s',
                 whiteSpace:'nowrap' },
  subTabActive:{ color:'#0f1923', borderBottomColor:'#0f1923', fontWeight:600 },
  saveBtn:     { padding:'7px 18px', color:'#fff', border:'none', borderRadius:7,
                 fontSize:13, fontWeight:600, cursor:'pointer',
                 fontFamily:'DM Sans, sans-serif', transition:'background 0.15s' },

  // ── Three-panel shell ──
  designShell: { display:'flex', gap:0, minHeight:600, border:'1px solid #e2e8f0',
                 borderRadius:12, overflow:'hidden', background:'#f8fafc' },
  palette:     { width:180, borderRight:'1px solid #e2e8f0', background:'#fff',
                 padding:'14px 10px', flexShrink:0, overflowY:'auto' },
  palTitle:    { fontSize:11, fontWeight:700, color:'#64748b', textTransform:'uppercase',
                 letterSpacing:'0.07em', marginBottom:12 },
  palGroup:    { fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase',
                 letterSpacing:'0.06em', marginBottom:6, marginTop:4 },
  canvas:      { flex:1, minWidth:0, display:'flex', flexDirection:'column' },
  inspector:   { width:230, borderLeft:'1px solid #e2e8f0', background:'#fff',
                 flexShrink:0, overflowY:'auto' },

  // ── Page tabs ──
  pageTabs:    { display:'flex', alignItems:'center', borderBottom:'2px solid #e2e8f0',
                 background:'#fff', padding:'0 12px', gap:0 },
  canvasBody:  { flex:1, padding:'16px', overflowY:'auto' },

  // ── Add row ──
  addRowBtn:   { display:'block', width:'100%', padding:'8px', background:'#fff',
                 border:'2px dashed #e2e8f0', borderRadius:8, fontSize:12,
                 color:'#94a3b8', cursor:'pointer', fontFamily:'DM Sans, sans-serif',
                 marginTop:8, transition:'border-color 0.15s, color 0.15s' },

  // ── Inspector ──
  inspPanel:   { padding:14, overflowY:'auto' },
  inspHeaderRow:{ display:'flex', alignItems:'center', justifyContent:'space-between',
                  marginBottom:12, paddingBottom:8, borderBottom:'1px solid #f1f5f9' },
  inspHeader:  { fontSize:11, fontWeight:700, color:'#64748b', textTransform:'uppercase',
                 letterSpacing:'0.07em' },
  inspBadge:   (color) => ({ fontSize:10, background: color + '22', color,
                             padding:'2px 7px', borderRadius:4, fontWeight:600,
                             textTransform:'uppercase' }),
  inspLabel:   { fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase',
                 letterSpacing:'0.06em', marginBottom:4, marginTop:10 },
  inspInput:   { width:'100%', padding:'6px 8px', border:'1.5px solid #e2e8f0',
                 borderRadius:6, fontSize:12, fontFamily:'DM Sans, sans-serif',
                 color:'#0f1923', outline:'none', boxSizing:'border-box', marginBottom:4 },
  inspApply:   { width:'100%', padding:'7px', background:'#0f1923', color:'#fff',
                 border:'none', borderRadius:6, fontSize:12, fontWeight:600,
                 cursor:'pointer', fontFamily:'DM Sans, sans-serif' },
  inspDelete:  { width:'100%', padding:'7px', background:'#fff', color:'#dc2626',
                 border:'1px solid #fecaca', borderRadius:6, fontSize:12,
                 cursor:'pointer', fontFamily:'DM Sans, sans-serif', marginTop:4 },
  inspClearBtn:{ display:'block', fontSize:10, color:'#94a3b8', background:'none',
                 border:'none', cursor:'pointer', padding:'0 0 8px',
                 fontFamily:'DM Sans, sans-serif', textAlign:'left' },

  // ── ListView ──
  lvCtrlLabel: { fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase',
                 letterSpacing:'0.07em', marginBottom:5 },
  lvSelect:    { padding:'6px 8px', border:'1.5px solid #e2e8f0', borderRadius:6,
                 fontSize:12, fontFamily:'DM Sans, sans-serif', background:'#fff',
                 outline:'none', color:'#0f1923', minWidth:130 },
  lvSection:   { fontSize:10, fontWeight:700, color:'#94a3b8', textTransform:'uppercase',
                 letterSpacing:'0.06em', marginBottom:8, marginTop:4 },
  lvHeader:    { display:'flex', alignItems:'center', background:'#f8fafc',
                 padding:'6px 14px', fontSize:10, fontWeight:700, color:'#94a3b8',
                 letterSpacing:'0.06em', textTransform:'uppercase', gap:8 },

  // ── Misc ──
  center:      { display:'flex', alignItems:'center', justifyContent:'center',
                 padding:'60px 20px', color:'#94a3b8', fontSize:13 },
  errorBox:    { background:'#fef2f2', color:'#dc2626', padding:'12px 16px',
                 borderRadius:8, fontSize:13, border:'1px solid #fecaca' },
  backdrop:    { position:'fixed', inset:0, background:'rgba(0,0,0,0.4)',
                 display:'flex', alignItems:'center', justifyContent:'center', zIndex:200 },
  modal:       { background:'#fff', borderRadius:12, padding:'24px 28px',
                 width:'100%', maxWidth:480, boxShadow:'0 20px 60px rgba(0,0,0,0.2)' },
  cancelBtn:   { padding:'7px 16px', background:'#fff', color:'#374151',
                 border:'1.5px solid #e2e8f0', borderRadius:6, fontSize:12,
                 cursor:'pointer', fontFamily:'DM Sans, sans-serif' },
  submitBtn:   { padding:'7px 16px', background:'#0f1923', color:'#fff',
                 border:'none', borderRadius:6, fontSize:12, fontWeight:600,
                 cursor:'pointer', fontFamily:'DM Sans, sans-serif' },
};
