// fc-inspector.jsx — Inspector panel + SettingsModal

const { useState, useRef } = React;

// ── Inspector ──────────────────────────────────────────────────────────────
function Inspector({ node, chart, project, allCharts, nodeTypes, statuses, onUpdateNode, onUpdateChart,
  onAddChild, onDeleteNode, onAddNodeType, onDeleteNodeType,
  onAddStatus, onDeleteStatus,
  onOpenSubChart, onCreateSubChart, onOpenExpanded, onAddImage,
  onLinkNodes, onUnlinkNode, onJumpToLinkedNode, readOnly }) {

  const [showTypeForm,   setShowTypeForm]   = useState(false);
  const [newType,        setNewType]        = useState({ name: '', color: '#60a5fa' });
  const [showStatusForm, setShowStatusForm] = useState(false);
  const [newStatus,      setNewStatus]      = useState({ label: '', color: '#60a5fa' });
  const [assigneeInput,  setAssigneeInput]  = useState('');
  const [tagInput,       setTagInput]       = useState('');
  const [showLinkPicker, setShowLinkPicker] = useState(false);
  const imgRef = useRef(null);

  function upNode(changes) { if (node) onUpdateNode(node.id, changes); }

  function addAssignee(e) {
    if (e.key !== 'Enter') return;
    const val = assigneeInput.trim();
    if (!val || !node) return;
    const cur = node.assignees || [];
    if (!cur.includes(val)) onUpdateNode(node.id, { assignees: [...cur, val] });
    setAssigneeInput('');
  }

  function addTag(e) {
    if (e.key !== 'Enter' && e.key !== ',') return;
    e.preventDefault();
    const val = tagInput.trim().replace(/^#/, '').toLowerCase();
    if (!val || !node) return;
    const cur = node.tags || [];
    if (!cur.includes(val)) onUpdateNode(node.id, { tags: [...cur, val] });
    setTagInput('');
  }

  function removeTag(tag) {
    if (!node) return;
    onUpdateNode(node.id, { tags: (node.tags || []).filter(t => t !== tag) });
  }

  function submitNewType() {
    if (!newType.name.trim()) return;
    onAddNodeType({ id: uid('type'), name: newType.name.trim(), color: newType.color });
    setNewType({ name: '', color: '#60a5fa' });
    setShowTypeForm(false);
  }

  function submitNewStatus() {
    if (!newStatus.label.trim()) return;
    onAddStatus({ id: uid('status'), label: newStatus.label.trim(), color: newStatus.color });
    setNewStatus({ label: '', color: '#60a5fa' });
    setShowStatusForm(false);
  }

  async function handleImgFiles(fileList) {
    const files = [...(fileList || [])];
    for (const file of files) {
      if (!file.type.startsWith('image/')) continue;
      try {
        const dataUrl = (typeof readAndCompressImageFile === 'function')
          ? await readAndCompressImageFile(file)
          : await new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => resolve(reader.result);
            reader.onerror = () => reject(new Error('Read failed'));
            reader.readAsDataURL(file);
          });
        onAddImage && onAddImage(node.id, dataUrl);
      } catch (err) {
        if (err?.message === 'too_large') alert(`"${file.name}" too large (max 4 MB)`);
      }
    }
  }

  const childCount  = node && chart ? chart.edges.filter(e => e.from === node.id).length : 0;
  const parentCount = node && chart ? chart.edges.filter(e => e.to   === node.id).length : 0;

  return (
    <div style={{ width: 262, borderLeft: '1px solid var(--border)', display: 'flex', flexDirection: 'column', overflow: 'hidden', flexShrink: 0, background: 'var(--surface)' }}>
      <div style={{ padding: '13px 14px 9px', borderBottom: '1px solid var(--border)' }}>
        <span style={{ fontWeight: 700, fontSize: 10, color: 'var(--text2)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Inspector</span>
      </div>

      <div style={{ flex: 1, overflowY: 'auto', padding: 14, display: 'flex', flexDirection: 'column', gap: 18 }}>

        {/* ── Chart section ── */}
        {chart && (
          <ISection label="Chart">
            <IField label="Title">
              <FcInput value={chart.title} disabled={readOnly} onChange={e => onUpdateChart('title', e.target.value)} />
            </IField>
            <IField label="Description">
              <FcTextarea value={chart.description || ''} disabled={readOnly} rows={2} onChange={e => onUpdateChart('description', e.target.value)} />
            </IField>
            <IField label="Direction">
              <FcSelect value={chart.orientation} disabled={readOnly} onChange={e => onUpdateChart('orientation', e.target.value)}>
                <option value="vertical">Top → Bottom</option>
                <option value="horizontal">Left → Right</option>
              </FcSelect>
            </IField>
          </ISection>
        )}

        {/* ── Node section ── */}
        <ISection label="Node">
          {node ? (
            <>
              {/* Connection summary */}
              <div style={{ display: 'flex', gap: 6 }}>
                <div style={{ flex: 1, background: 'var(--bg)', border: '1px solid var(--border)', borderRadius: 8, padding: '6px 10px', textAlign: 'center' }}>
                  <div style={{ fontSize: 16, fontWeight: 800, color: 'var(--text)' }}>{parentCount}</div>
                  <div style={{ fontSize: 9, color: 'var(--text2)', textTransform: 'uppercase', letterSpacing: '0.06em', marginTop: 1 }}>parents</div>
                </div>
                <div style={{ flex: 1, background: 'var(--bg)', border: '1px solid var(--border)', borderRadius: 8, padding: '6px 10px', textAlign: 'center' }}>
                  <div style={{ fontSize: 16, fontWeight: 800, color: 'var(--text)' }}>{childCount}</div>
                  <div style={{ fontSize: 9, color: 'var(--text2)', textTransform: 'uppercase', letterSpacing: '0.06em', marginTop: 1 }}>children</div>
                </div>
                <div style={{ flex: 1, background: 'var(--bg)', border: '1px solid var(--border)', borderRadius: 8, padding: '6px 10px', textAlign: 'center' }}>
                  <div style={{ fontSize: 16, fontWeight: 800, color: 'var(--text)' }}>{(node.images || []).length}</div>
                  <div style={{ fontSize: 9, color: 'var(--text2)', textTransform: 'uppercase', letterSpacing: '0.06em', marginTop: 1 }}>images</div>
                </div>
              </div>

              <IField label="Label">
                <FcInput value={node.label} disabled={readOnly} onChange={e => upNode({ label: e.target.value })} />
              </IField>
              <IField label="Notes">
                <FcTextarea value={node.notes || ''} disabled={readOnly} rows={3} placeholder="Describe this node…" onChange={e => upNode({ notes: e.target.value })} />
              </IField>
              <IField label="Type">
                <FcSelect value={node.typeId} disabled={readOnly} onChange={e => upNode({ typeId: e.target.value })}>
                  {nodeTypes.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
                </FcSelect>
              </IField>
              <IField label="Status">
                <FcSelect value={node.status || statuses[0]?.id || 'todo'} disabled={readOnly} onChange={e => upNode({ status: e.target.value })}>
                  {statuses.map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
                </FcSelect>
              </IField>

              {/* Tags */}
              <IField label="Tags">
                {!readOnly && (
                  <FcInput
                    value={tagInput}
                    onChange={e => setTagInput(e.target.value)}
                    onKeyDown={addTag}
                    placeholder="tag name, press Enter"
                  />
                )}
                {(node.tags || []).length > 0 && (
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 }}>
                    {(node.tags || []).map((tag, i) => {
                      const tc = (nodeTypes.find(t => t.id === node.typeId) || nodeTypes[0])?.color || '#94a3b8';
                      return (
                        <span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 10, fontWeight: 600, background: tc + '18', color: tc, padding: '2px 8px', borderRadius: 99 }}>
                          #{tag}
                          {!readOnly && (
                            <button onClick={() => removeTag(tag)} style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', color: tc, fontSize: 13, lineHeight: 1, opacity: 0.7 }}>×</button>
                          )}
                        </span>
                      );
                    })}
                  </div>
                )}
              </IField>

              {/* Assignees */}
              <IField label="Assignees">
                {!readOnly && (
                  <FcInput
                    value={assigneeInput}
                    onChange={e => setAssigneeInput(e.target.value)}
                    onKeyDown={addAssignee}
                    placeholder="Name, press Enter"
                  />
                )}
                {(node.assignees || []).length > 0 && (
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 4 }}>
                    {(node.assignees || []).map((a, i) => (
                      <span key={i} style={{ display: 'inline-flex', alignItems: 'center', gap: 3, fontSize: 11, background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text2)', padding: '2px 8px', borderRadius: 99 }}>
                        {a}
                        {!readOnly && (
                          <button onClick={() => upNode({ assignees: (node.assignees || []).filter(x => x !== a) })}
                            style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', color: 'var(--text2)', fontSize: 13, lineHeight: 1 }}>×</button>
                        )}
                      </span>
                    ))}
                  </div>
                )}
              </IField>

              {/* Images quick upload */}
              {!readOnly && (
                <IField label="Images">
                  <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
                    <FcBtn onClick={() => imgRef.current?.click()} style={{ flex: 1, textAlign: 'center' }}>
                      + Add image
                    </FcBtn>
                    {(node.images || []).length > 0 && (
                      <span style={{ fontSize: 10, color: 'var(--text2)' }}>{node.images.length} file{node.images.length !== 1 ? 's' : ''}</span>
                    )}
                    <input type="file" accept="image/*" multiple ref={imgRef} style={{ display: 'none' }}
                      onChange={e => { handleImgFiles(e.target.files); e.target.value = ''; }} />
                  </div>
                  {(node.images || []).length > 0 && (
                    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 4, marginTop: 4 }}>
                      {node.images.slice(0, 6).map((src, i) => (
                        <div key={i} style={{ paddingBottom: '75%', position: 'relative', borderRadius: 5, overflow: 'hidden', background: 'var(--bg2)', border: '1px solid var(--border)' }}>
                          <img src={src} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover' }} alt="" />
                        </div>
                      ))}
                    </div>
                  )}
                </IField>
              )}

              {/* Linked Nodes (cross-chart content sync) */}
              <IField label="Linked Nodes">
                <LinkedNodesPanel
                  node={node}
                  project={project}
                  allCharts={allCharts}
                  readOnly={readOnly}
                  showPicker={showLinkPicker}
                  onShowPicker={setShowLinkPicker}
                  onLink={onLinkNodes}
                  onUnlink={onUnlinkNode}
                  onJump={onJumpToLinkedNode}
                  currentChartId={chart?.id}
                />
              </IField>

              {/* Sub-chart */}
              <IField label="Sub-chart">
                {node.subChartId ? (
                  <div style={{ display: 'flex', gap: 6 }}>
                    <FcBtn primary onClick={() => onOpenSubChart(node.subChartId, node.label)} style={{ flex: 1 }}>Open →</FcBtn>
                    {!readOnly && <FcBtn danger onClick={() => upNode({ subChartId: null })} title="Unlink sub-chart">Unlink</FcBtn>}
                  </div>
                ) : (
                  !readOnly && <FcBtn onClick={() => onCreateSubChart(node.id, node.label)} style={{ width: '100%', textAlign: 'center' }}>+ Create sub-chart</FcBtn>
                )}
              </IField>

              {/* Actions */}
              {!readOnly && (
                <div style={{ display: 'flex', gap: 6 }}>
                  <FcBtn primary onClick={() => onAddChild(node.id)} style={{ flex: 1 }}>+ Child</FcBtn>
                  <FcBtn danger onClick={() => onDeleteNode(node.id)}>Delete</FcBtn>
                </div>
              )}
              <FcBtn onClick={() => onOpenExpanded && onOpenExpanded(node.id)} style={{ width: '100%', textAlign: 'center' }}>
                ⛶ Open expanded view
              </FcBtn>
            </>
          ) : (
            <p style={{ margin: 0, fontSize: 12, color: 'var(--text2)', lineHeight: 1.65 }}>
              Click a node to inspect it.<br />Right-click the canvas to add a node.
            </p>
          )}
        </ISection>

        {/* ── Node Types section ── */}
        <ISection label="Node Types">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 6 }}>
            {nodeTypes.map(t => (
              <TypeRow key={t.id} type={t} readOnly={readOnly} onDelete={() => onDeleteNodeType(t.id)} />
            ))}
          </div>
          {!readOnly && (
            <>
              <FcBtn onClick={() => setShowTypeForm(v => !v)} style={{ width: '100%', textAlign: 'center' }}>
                {showTypeForm ? 'Cancel' : '+ Add node type'}
              </FcBtn>
              {showTypeForm && (
                <div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
                  <FcInput
                    value={newType.name} autoFocus
                    onChange={e => setNewType(p => ({ ...p, name: e.target.value }))}
                    onKeyDown={e => { if (e.key === 'Enter') submitNewType(); if (e.key === 'Escape') setShowTypeForm(false); }}
                    placeholder="Type name"
                  />
                  <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                    <input type="color" value={newType.color}
                      onChange={e => setNewType(p => ({ ...p, color: e.target.value }))}
                      style={{ width: 36, height: 28, border: '1px solid var(--border)', borderRadius: 6, cursor: 'pointer', background: 'none', padding: 2 }}
                    />
                    <FcBtn primary disabled={!newType.name.trim()} style={{ flex: 1 }} onClick={submitNewType}>Create</FcBtn>
                  </div>
                </div>
              )}
            </>
          )}
        </ISection>

        {/* ── Statuses section ── */}
        <ISection label="Statuses">
          <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 6 }}>
            {statuses.map(s => (
              <StatusRow key={s.id} status={s} readOnly={readOnly} canDelete={statuses.length > 1} onDelete={() => onDeleteStatus(s.id)} />
            ))}
          </div>
          {!readOnly && (
            <>
              <FcBtn onClick={() => setShowStatusForm(v => !v)} style={{ width: '100%', textAlign: 'center' }}>
                {showStatusForm ? 'Cancel' : '+ Add status'}
              </FcBtn>
              {showStatusForm && (
                <div style={{ marginTop: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
                  <FcInput
                    value={newStatus.label} autoFocus
                    onChange={e => setNewStatus(p => ({ ...p, label: e.target.value }))}
                    onKeyDown={e => { if (e.key === 'Enter') submitNewStatus(); if (e.key === 'Escape') setShowStatusForm(false); }}
                    placeholder="Status label"
                  />
                  <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
                    <input type="color" value={newStatus.color}
                      onChange={e => setNewStatus(p => ({ ...p, color: e.target.value }))}
                      style={{ width: 36, height: 28, border: '1px solid var(--border)', borderRadius: 6, cursor: 'pointer', background: 'none', padding: 2 }}
                    />
                    <FcBtn primary disabled={!newStatus.label.trim()} style={{ flex: 1 }} onClick={submitNewStatus}>Create</FcBtn>
                  </div>
                </div>
              )}
            </>
          )}
        </ISection>

      </div>
    </div>
  );
}

// ── TypeRow ────────────────────────────────────────────────────────────────
function TypeRow({ type, readOnly, onDelete }) {
  const [hov, setHov] = useState(false);
  return (
    <div
      onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)}
      style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 6px', borderRadius: 6, background: hov ? 'var(--bg2)' : 'transparent', transition: 'background 0.1s' }}
    >
      <span style={{ width: 10, height: 10, borderRadius: 99, background: type.color, flexShrink: 0 }} />
      <span style={{ fontSize: 11, color: 'var(--text)', flex: 1 }}>{type.name}</span>
      {!readOnly && hov && (
        <button onClick={onDelete} title="Delete type"
          style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#f87171', fontSize: 14, padding: '0 2px', lineHeight: 1, borderRadius: 4 }}>×</button>
      )}
    </div>
  );
}

// ── StatusRow ──────────────────────────────────────────────────────────────
function StatusRow({ status, readOnly, canDelete, onDelete }) {
  const [hov, setHov] = useState(false);
  return (
    <div
      onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)}
      style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 6px', borderRadius: 6, background: hov ? 'var(--bg2)' : 'transparent', transition: 'background 0.1s' }}
    >
      <span style={{ width: 10, height: 10, borderRadius: 99, background: status.color, flexShrink: 0 }} />
      <span style={{ fontSize: 11, color: 'var(--text)', flex: 1 }}>{status.label}</span>
      {!readOnly && hov && canDelete && (
        <button onClick={onDelete} title="Delete status"
          style={{ background: 'none', border: 'none', cursor: 'pointer', color: '#f87171', fontSize: 14, padding: '0 2px', lineHeight: 1, borderRadius: 4 }}>×</button>
      )}
    </div>
  );
}

// ── SettingsModal ──────────────────────────────────────────────────────────
function SettingsModal({ project, readOnly, onClose, onUpdateName, onExport, onImport, onCopyShare, onAIExport, layoutSpacing, onLayoutSpacingChange, defaultSpacing, myUser, onUpdateUser }) {
  const [nameVal,  setNameVal]  = useState(project?.name || '');
  // Local mirror so the input stays responsive even while we debounce the
  // broadcast. The actual broadcast happens onBlur / on color change.
  const [myName,   setMyName]   = useState(myUser?.name  || '');
  const [myColor,  setMyColor]  = useState(myUser?.color || '#3b82f6');
  const fileRef = useRef(null);

  const updateSpacing = (orient, key, value) => {
    if (!onLayoutSpacingChange) return;
    onLayoutSpacingChange({
      ...layoutSpacing,
      [orient]: { ...layoutSpacing[orient], [key]: value },
    });
  };
  const resetSpacing = () => {
    if (onLayoutSpacingChange && defaultSpacing) onLayoutSpacingChange(defaultSpacing);
  };

  return (
    <div
      style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.55)', display: 'grid', placeItems: 'center', zIndex: 100, backdropFilter: 'blur(6px)' }}
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}
    >
      <div style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 18, padding: 28, width: 460, maxWidth: '92vw', maxHeight: '90vh', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 20, boxShadow: '0 24px 64px rgba(0,0,0,0.4)' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <strong style={{ fontSize: 15, color: 'var(--text)' }}>Project Settings</strong>
          <FcBtn onClick={onClose} style={{ padding: '4px 8px' }}>✕</FcBtn>
        </div>

        {!readOnly && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
            <FcLabel>Project Name</FcLabel>
            <div style={{ display: 'flex', gap: 8 }}>
              <FcInput value={nameVal} onChange={e => setNameVal(e.target.value)}
                onKeyDown={e => { if (e.key === 'Enter') { onUpdateName(nameVal); onClose(); } }}
                style={{ flex: 1 }}
              />
              <FcBtn primary onClick={() => { onUpdateName(nameVal); onClose(); }}>Save</FcBtn>
            </div>
          </div>
        )}

        {/* ── Your Display Name (per-browser identity, broadcast to teammates) ── */}
        {myUser && onUpdateUser && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
            <FcLabel>Your Display Name</FcLabel>
            <p style={{ margin: 0, fontSize: 11, color: 'var(--text2)', lineHeight: 1.6 }}>
              How teammates see you in presence pills. Stored only in this browser — not in the project.
            </p>
            <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
              <input
                type="color" value={myColor}
                onChange={e => { setMyColor(e.target.value); onUpdateUser({ color: e.target.value }); }}
                style={{ width: 36, height: 30, border: '1px solid var(--border)', borderRadius: 6, cursor: 'pointer', background: 'none', padding: 2 }}
                title="Pick your colour"
              />
              <FcInput
                value={myName}
                onChange={e => setMyName(e.target.value)}
                onBlur={() => { if (myName.trim()) onUpdateUser({ name: myName.trim() }); }}
                onKeyDown={e => { if (e.key === 'Enter') { e.currentTarget.blur(); } }}
                placeholder="Your name"
                style={{ flex: 1 }}
              />
              <div title="Preview" style={{
                width: 28, height: 28, borderRadius: '50%',
                background: myColor, color: '#fff', display: 'grid', placeItems: 'center',
                fontSize: 10, fontWeight: 800, flexShrink: 0, border: '2px solid var(--surface)',
                boxShadow: '0 0 0 1px ' + myColor,
              }}>
                {(myName || myUser.name || '??').split(/\s+/).map(s => s[0]).join('').slice(0, 2).toUpperCase()}
              </div>
            </div>
          </div>
        )}

        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          <FcLabel>Data &amp; Sharing</FcLabel>
          <p style={{ margin: 0, fontSize: 11, color: 'var(--text2)', lineHeight: 1.6 }}>
            Export for AI generates a structured design doc you can paste into ChatGPT or Claude.
          </p>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
            <FcBtn primary onClick={() => { onAIExport(); onClose(); }} style={{ fontWeight: 700 }}>✦ Export for AI</FcBtn>
            <FcBtn onClick={onCopyShare}>Copy share link</FcBtn>
            {!readOnly && <FcBtn onClick={onExport}>Export JSON</FcBtn>}
            {!readOnly && (
              <>
                <FcBtn onClick={() => fileRef.current?.click()}>Import JSON</FcBtn>
                <input type="file" accept="application/json" ref={fileRef} style={{ display: 'none' }} onChange={onImport} />
              </>
            )}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 4 }}>
          </div>
        </div>

        {/* Layout spacing controls — applied next time you Auto-arrange */}
        {layoutSpacing && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
              <FcLabel>Layout Spacing</FcLabel>
              <button
                onClick={resetSpacing}
                style={{ background: 'transparent', border: 'none', color: 'var(--text2)', fontSize: 10, cursor: 'pointer', textDecoration: 'underline', padding: 0 }}
              >Reset to defaults</button>
            </div>
            <p style={{ margin: 0, fontSize: 11, color: 'var(--text2)', lineHeight: 1.6 }}>
              Tune gaps used by Tree, Hierarchical, and Compact layouts. Sibling = space between sibling subtrees. Depth = space between parent and child levels.
            </p>
            {[
              { orient: 'vertical',   label: 'Vertical (Top → Bottom)' },
              { orient: 'horizontal', label: 'Horizontal (Left → Right)' },
            ].map(({ orient, label }) => (
              <div key={orient} style={{ border: '1px solid var(--border)', borderRadius: 10, padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8, background: 'var(--bg)' }}>
                <div style={{ fontSize: 11, fontWeight: 600, color: 'var(--text)' }}>{label}</div>
                {[
                  { key: 'sib',   name: 'Sibling gap', min: 0,  max: 400, step: 5 },
                  { key: 'depth', name: 'Depth gap',   min: 40, max: 500, step: 5 },
                ].map(({ key, name, min, max, step }) => {
                  const value = layoutSpacing[orient][key];
                  return (
                    <div key={key} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                      <span style={{ fontSize: 11, color: 'var(--text2)', minWidth: 80 }}>{name}</span>
                      <input
                        type="range" min={min} max={max} step={step} value={value}
                        onChange={e => updateSpacing(orient, key, Number(e.target.value))}
                        style={{ flex: 1 }}
                      />
                      <input
                        type="number" min={min} max={max} step={step} value={value}
                        onChange={e => updateSpacing(orient, key, Number(e.target.value))}
                        style={{ width: 56, fontSize: 11, padding: '3px 6px', background: 'var(--surface)', color: 'var(--text)', border: '1px solid var(--border)', borderRadius: 6 }}
                      />
                      <span style={{ fontSize: 10, color: 'var(--text2)', minWidth: 18 }}>px</span>
                    </div>
                  );
                })}
              </div>
            ))}
          </div>
        )}

        {/* Keyboard shortcuts reference */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 7 }}>
          <FcLabel>Keyboard Shortcuts</FcLabel>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px 16px', fontSize: 11, color: 'var(--text2)' }}>
            {[
              ['N', 'Add child node'],
              ['C', 'Connect mode'],
              ['F', 'Fit to view'],
              ['A', 'Auto-arrange'],
              ['⌘Z / ⌘Y', 'Undo / Redo'],
              ['⌘D', 'Duplicate node'],
              ['⌘C / ⌘V', 'Copy / Paste'],
              ['Enter', 'Expand node'],
              ['⌘F', 'Search nodes'],
              ['Del / ⌫', 'Delete node/edge'],
              ['Arrows', 'Move node (±8px)'],
              ['Shift+Arrows', 'Move node (±40px)'],
            ].map(([k, v]) => (
              <div key={k} style={{ display: 'flex', justifyContent: 'space-between', gap: 8, padding: '3px 0', borderBottom: '1px solid var(--border)' }}>
                <span style={{ fontFamily: 'ui-monospace, monospace', fontSize: 10, background: 'var(--bg)', padding: '1px 5px', borderRadius: 4, color: 'var(--text)', border: '1px solid var(--border)', whiteSpace: 'nowrap' }}>{k}</span>
                <span style={{ textAlign: 'right' }}>{v}</span>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

// ── LinkedNodesPanel ──────────────────────────────────────────────────────
// Shows the list of nodes linked to this one (sharing linkGroupId), plus a
// searchable picker for linking to a new node. The CURRENTLY-VIEWED node is
// treated as MASTER: when picking a target, the target's content is
// overwritten with this node's content (matches the user's spec).
function LinkedNodesPanel({ node, project, allCharts, readOnly, showPicker, onShowPicker,
  onLink, onUnlink, onJump, currentChartId }) {

  const [query, setQuery] = useState('');

  // Sibling list (same linkGroupId, excluding self)
  const siblings = (typeof findLinkedNodes === 'function' && node?.linkGroupId)
    ? findLinkedNodes(project, node.linkGroupId, node.id)
    : [];

  // Build pickable target list: every node in the project EXCEPT this one
  // and any already in the same link group. Filtered by query (label/notes).
  const pickable = !node ? [] : (allCharts || []).flatMap(c =>
    (c.nodes || []).filter(n => {
      if (c.id === currentChartId && n.id === node.id) return false;
      if (node.linkGroupId && n.linkGroupId === node.linkGroupId) return false;
      if (!query.trim()) return true;
      const q = query.toLowerCase();
      return (n.label || '').toLowerCase().includes(q)
          || (n.notes || '').toLowerCase().includes(q)
          || (c.title || '').toLowerCase().includes(q);
    }).map(n => ({ chart: c, node: n }))
  ).slice(0, 40); // cap to keep the menu manageable

  if (!node) return null;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
      {/* Existing siblings */}
      {siblings.length > 0 ? (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
          {siblings.map(({ chartId, chartTitle, node: ln }) => (
            <button key={ln.id}
              onClick={() => onJump && onJump(chartId, ln.id)}
              title="Jump to this linked node"
              style={{
                display: 'flex', alignItems: 'center', gap: 6, textAlign: 'left',
                background: 'var(--bg)', border: '1px solid var(--border)',
                borderRadius: 7, padding: '5px 8px', cursor: 'pointer',
                color: 'var(--text)', fontFamily: 'inherit', fontSize: 11,
              }}
              onMouseEnter={e => e.currentTarget.style.borderColor = 'var(--accent)'}
              onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--border)'}
            >
              <span style={{ fontSize: 12, color: 'var(--accent)' }}>🔗</span>
              <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                <strong>{ln.label || 'Untitled'}</strong>
                <span style={{ color: 'var(--text2)', marginLeft: 6, fontWeight: 400 }}>· {chartTitle}</span>
              </span>
              {!readOnly && (
                <button
                  onClick={e => { e.stopPropagation(); onUnlink && onUnlink(ln.id); }}
                  title="Unlink this node"
                  style={{ background: 'none', border: 'none', cursor: 'pointer',
                           color: 'var(--text2)', fontSize: 13, padding: '0 2px', lineHeight: 1 }}>×</button>
              )}
            </button>
          ))}
        </div>
      ) : (
        <div style={{ fontSize: 11, color: 'var(--text2)', fontStyle: 'italic' }}>
          Not linked to other nodes.
        </div>
      )}

      {/* Link button / picker */}
      {!readOnly && !showPicker && (
        <FcBtn onClick={() => onShowPicker(true)} style={{ width: '100%', textAlign: 'center' }}>
          🔗 Link to existing node…
        </FcBtn>
      )}
      {!readOnly && showPicker && (
        <div style={{ background: 'var(--bg2)', border: '1px solid var(--border)', borderRadius: 8, padding: 6, display: 'flex', flexDirection: 'column', gap: 6 }}>
          <FcInput
            value={query} autoFocus
            onChange={e => setQuery(e.target.value)}
            placeholder="Search nodes by label, notes, or chart…"
          />
          <p style={{ margin: 0, fontSize: 10, color: 'var(--text2)', lineHeight: 1.5 }}>
            ⚠ This node is master — the target's content will be overwritten.
          </p>
          <div style={{ maxHeight: 220, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 3 }}>
            {pickable.length === 0 ? (
              <div style={{ padding: '12px 8px', fontSize: 11, color: 'var(--text2)', fontStyle: 'italic', textAlign: 'center' }}>
                No matching nodes.
              </div>
            ) : pickable.map(({ chart, node: cand }) => (
              <button key={chart.id + ':' + cand.id}
                onClick={() => { onLink && onLink(node.id, chart.id, cand.id); onShowPicker(false); setQuery(''); }}
                style={{
                  textAlign: 'left', background: 'transparent', border: '1px solid transparent',
                  borderRadius: 6, padding: '5px 8px', cursor: 'pointer',
                  color: 'var(--text)', fontFamily: 'inherit', fontSize: 11,
                }}
                onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.background = 'var(--bg)'; }}
                onMouseLeave={e => { e.currentTarget.style.borderColor = 'transparent'; e.currentTarget.style.background = 'transparent'; }}
              >
                <strong style={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{cand.label || 'Untitled'}</strong>
                <span style={{ fontSize: 10, color: 'var(--text2)' }}>{chart.title}</span>
              </button>
            ))}
          </div>
          <FcBtn onClick={() => { onShowPicker(false); setQuery(''); }} style={{ alignSelf: 'flex-end' }}>Cancel</FcBtn>
        </div>
      )}
    </div>
  );
}

// ── Export ─────────────────────────────────────────────────────────────────
Object.assign(window, { Inspector, SettingsModal, LinkedNodesPanel });
