/* global React */
// Small primitives: icons, Chip, ConsensioMark, CopyButton, Skeleton helpers

const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ── Icons (16px stroke, hairline) ─────────────────────────────────────────────
const Icon = ({ d, size = 16, stroke = 1.5, fill = 'none' }) => (
  <svg width={size} height={size} viewBox="0 0 16 16" fill={fill} stroke="currentColor"
       strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round"
       aria-hidden="true">
    {typeof d === 'string' ? <path d={d} /> : d}
  </svg>
);

const I = {
  home:      <path d="M2.5 7.5L8 2.5l5.5 5M3.5 7v6h9V7" />,
  folder:    <path d="M2 4.5A1.5 1.5 0 013.5 3H6l1.5 1.5h5A1.5 1.5 0 0114 6v6a1.5 1.5 0 01-1.5 1.5h-9A1.5 1.5 0 012 12V4.5z" />,
  bell:      <path d="M4 6.5a4 4 0 118 0V9l1.5 2h-11L4 9V6.5zM6 12.5a2 2 0 004 0" />,
  bolt:      <path d="M8.5 1.5L3 9h4l-.5 5.5L13 7H9l-.5-5.5z" />,
  search:    <g><circle cx="7" cy="7" r="4.5" /><path d="M10.5 10.5L14 14" /></g>,
  chevronR:  <path d="M6 3l4 5-4 5" />,
  chevronD:  <path d="M3 6l5 4 5-4" />,
  chevronL:  <path d="M10 3L6 8l4 5" />,
  copy:      <g><rect x="4" y="4" width="8" height="9" rx="1.5" /><path d="M6 4V2.5h6V11" /></g>,
  external:  <g><path d="M6.5 3.5h-3v9h9v-3" /><path d="M9 3.5h3.5V7M7 9l5.5-5.5" /></g>,
  user:      <g><circle cx="8" cy="6" r="2.5" /><path d="M3.5 13c.5-2 2.3-3.5 4.5-3.5s4 1.5 4.5 3.5" /></g>,
  plus:      <path d="M8 3v10M3 8h10" />,
  filter:    <path d="M2 3.5h12L9.5 8.5v4l-3-1.5v-2.5L2 3.5z" />,
  star:      <path d="M8 2l1.8 3.7 4.1.6-3 2.9.7 4.1L8 11.4l-3.6 1.9.7-4.1-3-2.9 4.1-.6L8 2z" />,
  flask:     <g><path d="M6 2v4.5L3 13a1 1 0 00.9 1.5h8.2A1 1 0 0013 13L10 6.5V2" /><path d="M5.5 2h5" /></g>,
  paper:     <g><path d="M4 2h6l2 2v10H4V2z" /><path d="M10 2v2h2M6 7h4M6 9.5h4M6 12h2.5" /></g>,
  check:     <path d="M3 8l3.5 3.5L13 5" />,
  alert:     <g><path d="M8 2l6 11H2L8 2z" /><path d="M8 6v3M8 11v.5" /></g>,
  clock:     <g><circle cx="8" cy="8" r="5.5" /><path d="M8 5v3.2l2 1.5" /></g>,
  x:         <path d="M3.5 3.5l9 9M12.5 3.5l-9 9" />,
  cog:       <g><circle cx="8" cy="8" r="2" /><path d="M8 1.5v2M8 12.5v2M1.5 8h2M12.5 8h2M3.3 3.3l1.4 1.4M11.3 11.3l1.4 1.4M3.3 12.7l1.4-1.4M11.3 4.7l1.4-1.4" /></g>,
  helix:     null, // drawn manually below
};

// Consensio brand mark — abstract DNA double helix in brand gradient
const ConsensioMark = ({ size = 24 }) => (
  <svg width={size} height={size} viewBox="0 0 32 32" fill="none" aria-hidden="true">
    <defs>
      <linearGradient id="cx-grad" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
        <stop offset="0%" stopColor="var(--brand-1)" />
        <stop offset="55%" stopColor="var(--brand-2)" />
        <stop offset="100%" stopColor="var(--brand-3)" />
      </linearGradient>
    </defs>
    {/* two helix strands */}
    <path d="M8 4 C 18 8, 18 12, 8 16 C 18 20, 18 24, 8 28"
          stroke="url(#cx-grad)" strokeWidth="2" strokeLinecap="round" fill="none" />
    <path d="M24 4 C 14 8, 14 12, 24 16 C 14 20, 14 24, 24 28"
          stroke="url(#cx-grad)" strokeWidth="2" strokeLinecap="round" fill="none" />
    {/* rungs */}
    <path d="M11.5 7 L20.5 7 M12.5 11 L19.5 11 M12.5 13 L19.5 13 M11.5 17 L20.5 17 M12.5 21 L19.5 21 M12.5 23 L19.5 23 M11.5 27 L20.5 27"
          stroke="url(#cx-grad)" strokeWidth="1.4" strokeLinecap="round" opacity="0.55" />
  </svg>
);

const ConsensioWordmark = ({ size = 18 }) => (
  <div style={{ display: 'flex', alignItems: 'center', height: 32 }}>
    <img src="consensio-logo.png" alt="Consensio"
         style={{ height: 28, width: 'auto', display: 'block',
                  borderRadius: 3, objectFit: 'contain' }} />
  </div>
);

// ── Chip with semantic color ─────────────────────────────────────────────────
const ACCENT = {
  info:     'var(--accent-info)',
  success:  'var(--accent-success)',
  warning:  'var(--accent-warning)',
  danger:   'var(--accent-danger)',
  paper:    'var(--accent-paper)',
  request:  'var(--accent-request)',
  neutral:  'var(--text-secondary)',
};

const Chip = ({ tone = 'neutral', children, dot = true, style = {} }) => {
  const c = ACCENT[tone] || ACCENT.neutral;
  return (
    <span className="chip" style={{
      color: c,
      background: `color-mix(in oklab, ${c} 14%, transparent)`,
      borderColor: `color-mix(in oklab, ${c} 28%, transparent)`,
      ...style,
    }}>
      {dot && <span className="chip-dot" style={{ background: c }} />}
      {children}
    </span>
  );
};

// ── CopyButton ───────────────────────────────────────────────────────────────
const CopyButton = ({ value, label = 'Copy' }) => {
  const [copied, setCopied] = useState(false);
  const onClick = (e) => {
    e.stopPropagation();
    try { navigator.clipboard?.writeText(value); } catch (_) {}
    setCopied(true);
    setTimeout(() => setCopied(false), 1400);
  };
  return (
    <button className="btn btn-ghost btn-sm" onClick={onClick}
            title={copied ? 'Copied' : label}
            style={{ color: copied ? 'var(--accent-success)' : 'var(--text-tertiary)', padding: '0 6px' }}>
      <Icon d={copied ? I.check : I.copy} size={13} />
    </button>
  );
};

// ── Variant string renderer — monospace, never truncated, copy affordance ────
const Variant = ({ gene, hgvs, protein }) => (
  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, whiteSpace: 'nowrap' }}>
    <span className="mono" style={{ fontWeight: 600, color: 'var(--text-primary)' }}>{gene}</span>
    <span className="mono" style={{ color: 'var(--text-secondary)' }}>{hgvs}</span>
    {protein && <span className="mono" style={{ color: 'var(--text-tertiary)' }}>{protein}</span>}
    <CopyButton value={`${gene} ${hgvs}${protein ? ' ' + protein : ''}`} />
  </span>
);

// ── Skeleton row factory for the AI evidence table ───────────────────────────
const SkeletonRow = ({ cols }) => (
  <tr>
    {cols.map((w, i) => (
      <td key={i}>
        <div className="skeleton" style={{ height: 10, width: w }} />
      </td>
    ))}
  </tr>
);

// ── FlagPill — dual encoding (icon + color + text), per flag taxonomy ────────
// PAPER (indigo, paper icon) | TIME (amber, clock) | REQUEST (teal, user)
const FLAG_META = {
  PAPER:   { tone: 'paper',   icon: 'paper',   label: 'PAPER'   },
  TIME:    { tone: 'warning', icon: 'clock',   label: 'TIME'    },
  REQUEST: { tone: 'request', icon: 'user',    label: 'REQUEST' },
};
const MATCH_TONE = { High: 'success', Med: 'warning', Low: 'neutral' };

const FlagPill = ({ flag, expanded, onClick }) => {
  const meta = FLAG_META[flag.type] || FLAG_META.PAPER;
  const c = ACCENT[meta.tone];
  const expandable = flag.type === 'PAPER' || flag.type === 'REQUEST' || flag.type === 'TIME';
  return (
    <button onClick={(e) => { e.stopPropagation(); onClick && onClick(flag); }}
            style={{
              display: 'inline-flex', alignItems: 'center', gap: 5,
              height: 22, padding: '0 8px', borderRadius: 4,
              fontSize: 11, fontWeight: 500,
              border: '1px solid ' + `color-mix(in oklab, ${c} 32%, transparent)`,
              background: expanded
                ? `color-mix(in oklab, ${c} 24%, transparent)`
                : `color-mix(in oklab, ${c} 12%, transparent)`,
              color: c,
              cursor: expandable ? 'pointer' : 'default',
              fontFamily: 'inherit',
              whiteSpace: 'nowrap',
              transition: 'background 120ms ease',
            }}
            title={flag.type}>
      <Icon d={I[meta.icon]} size={11} />
      <span style={{ fontWeight: 700, letterSpacing: 0.04 }}>{meta.label}</span>
      {flag.type === 'PAPER' && (
        <>
          <span className="mono" style={{ fontSize: 11, fontWeight: 600 }}>{flag.gene}</span>
          <span style={{ opacity: 0.7, fontSize: 10 }}>{flag.journal}</span>
          <span style={{
            display: 'inline-flex', alignItems: 'center',
            height: 14, padding: '0 4px', borderRadius: 2, fontSize: 9, fontWeight: 700,
            background: `color-mix(in oklab, ${ACCENT[MATCH_TONE[flag.match]]} 22%, transparent)`,
            color: ACCENT[MATCH_TONE[flag.match]],
          }}>{flag.match}</span>
        </>
      )}
      {flag.type === 'TIME' && (
        <span style={{ opacity: 0.85 }}>{flag.months}mo</span>
      )}
      {flag.type === 'REQUEST' && (
        <span style={{ opacity: 0.9 }}>{flag.requester}</span>
      )}
    </button>
  );
};

// Sort flags within a case row according to taxonomy:
// PAPER (High > Med > Low) > REQUEST > TIME
const sortFlags = (flags = []) => {
  const order = (f) => {
    if (f.type === 'PAPER') {
      return 0 + ({ High: 0, Med: 1, Low: 2 }[f.match] ?? 3);
    }
    if (f.type === 'REQUEST') return 10;
    if (f.type === 'TIME')    return 20;
    return 99;
  };
  return [...flags].sort((a, b) => order(a) - order(b));
};

// Sort cases for default load order
const sortCasesByFlags = (cases) => {
  const score = (c) => {
    if (!c.flags || !c.flags.length) return 1000;
    const sorted = sortFlags(c.flags);
    const top = sorted[0];
    if (top.type === 'PAPER') return ({ High: 1, Med: 2, Low: 3 }[top.match] ?? 4);
    if (top.type === 'REQUEST') return 100;
    if (top.type === 'TIME')    return 200;
    return 500;
  };
  return [...cases].sort((a, b) => score(a) - score(b));
};

// ── AI badge — persistent, indigo, can't be dismissed (regulatory requirement) ─
const AiBadge = ({ size = 'sm', label = 'AI' }) => (
  <span style={{
    display: 'inline-flex', alignItems: 'center', gap: 3,
    fontSize: size === 'xs' ? 9 : 10, fontWeight: 700, letterSpacing: 0.04,
    padding: size === 'xs' ? '1px 4px' : '2px 5px',
    borderRadius: 3,
    background: 'color-mix(in oklab, var(--accent-paper) 18%, transparent)',
    color: 'var(--accent-paper)',
    border: '1px solid color-mix(in oklab, var(--accent-paper) 32%, transparent)',
    flexShrink: 0,
    fontFamily: 'inherit',
  }} title="AI-generated content">
    <svg width="9" height="9" viewBox="0 0 16 16" fill="none" stroke="currentColor"
         strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <rect x="3" y="5" width="10" height="8" rx="2" />
      <path d="M8 2v3M5.5 9.5h.01M10.5 9.5h.01M6 13l-1 1M10 13l1 1" />
    </svg>
    {label}
  </span>
);

// ── ACMG criterion chip + popover (3-state per criterion) ───────────────────
const ACMG_STATE = {
  met:           { tone: 'danger',  label: 'Met' },        // pathogenic side default
  not_evaluated: { tone: 'neutral', label: 'Not evaluated' },
  not_met:       { tone: 'success', label: 'Not met' },
};

const isPathogenicCriterion = (code) => /^P/.test(code);

const AcmgChip = ({ code, state = 'met', onClick }) => {
  // Pathogenic criteria → red-orange tones; benign → green
  const isP = isPathogenicCriterion(code);
  const isMet = state === 'met';
  const tone = isMet ? (isP ? 'danger' : 'success') : 'neutral';
  const c = ACCENT[tone];
  return (
    <button onClick={(e) => { e.stopPropagation(); onClick && onClick(); }}
            className="mono"
            style={{
              display: 'inline-flex', alignItems: 'center', gap: 4,
              height: 18, padding: '0 5px', borderRadius: 3,
              fontSize: 10, fontWeight: 600, letterSpacing: 0.01,
              background: isMet
                ? `color-mix(in oklab, ${c} 18%, transparent)`
                : 'var(--bg-base)',
              color: isMet ? c : 'var(--text-tertiary)',
              border: '1px solid ' + (isMet
                ? `color-mix(in oklab, ${c} 32%, transparent)`
                : 'var(--border)'),
              cursor: 'pointer', fontFamily: 'var(--font-mono)',
              textDecoration: state === 'not_met' ? 'line-through' : 'none',
            }}
            title={`${code} · ${ACMG_STATE[state].label}`}>
      {code}
    </button>
  );
};

// Tier ordering for the popover
const ACMG_TIERS = [
  { key: 'PVS', label: 'Pathogenic (Very Strong)' },
  { key: 'PS',  label: 'Pathogenic (Strong)' },
  { key: 'PM',  label: 'Pathogenic (Moderate)' },
  { key: 'PP',  label: 'Pathogenic (Supporting)' },
  { key: 'BA',  label: 'Benign (Standalone)' },
  { key: 'BS',  label: 'Benign (Strong)' },
  { key: 'BP',  label: 'Benign (Supporting)' },
];

const AcmgCriteriaPopover = ({ anchor, criteriaState, dict, onChange, onClose }) => {
  // anchor: { left, top, width } DOMRect-like
  const ref = useRef(null);

  // Close on outside click
  useEffect(() => {
    const onDown = (e) => {
      if (ref.current && !ref.current.contains(e.target)) onClose && onClose();
    };
    setTimeout(() => document.addEventListener('mousedown', onDown), 0);
    return () => document.removeEventListener('mousedown', onDown);
  }, [onClose]);

  // Group criteria by tier
  const byTier = useMemo(() => {
    const m = {};
    Object.entries(dict).forEach(([code, meta]) => {
      (m[meta.tier] = m[meta.tier] || []).push({ code, ...meta });
    });
    return m;
  }, [dict]);

  const setState = (code, next) => {
    onChange && onChange({ ...criteriaState, [code]: next });
  };

  return (
    <div ref={ref}
         style={{
           position: 'fixed',
           left: Math.min(anchor.left, window.innerWidth - 460),
           top: Math.min(anchor.top + anchor.height + 6, window.innerHeight - 540),
           width: 440, maxHeight: 520,
           background: 'var(--bg-raised)',
           border: '1px solid var(--border-hover)',
           borderRadius: 8,
           boxShadow: '0 12px 32px rgba(0,0,0,0.45)',
           zIndex: 300,
           display: 'flex', flexDirection: 'column',
           overflow: 'hidden',
         }}
         onClick={(e) => e.stopPropagation()}>
      <header style={{
        padding: '12px 14px', borderBottom: '1px solid var(--border)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      }}>
        <div>
          <div style={{ fontSize: 13, fontWeight: 600 }}>ACMG criteria</div>
          <div className="t-meta">Toggle Met / Not evaluated / Not met</div>
        </div>
        <button onClick={onClose} className="btn btn-ghost btn-sm" style={{ padding: '0 4px' }}>
          <Icon d={I.x} size={12} />
        </button>
      </header>

      <div style={{ overflow: 'auto', padding: '8px 0' }}>
        {ACMG_TIERS.map(tier => {
          const list = byTier[tier.key];
          if (!list) return null;
          return (
            <section key={tier.key} style={{ padding: '4px 0' }}>
              <div className="t-label" style={{ padding: '6px 14px', color: isPathogenicCriterion(tier.key[0] === 'P' ? 'P' : 'B') ? 'var(--accent-danger)' : 'var(--accent-success)' }}>
                {tier.label}
              </div>
              <ul style={{ listStyle: 'none', margin: 0, padding: 0 }}>
                {list.map(item => {
                  const cur = criteriaState[item.code] || 'not_evaluated';
                  return (
                    <li key={item.code} style={{
                      display: 'grid', gridTemplateColumns: '60px 1fr auto',
                      gap: 10, alignItems: 'center',
                      padding: '8px 14px',
                      borderRadius: 4,
                    }}>
                      <span className="mono" style={{ fontSize: 11, fontWeight: 600, color: 'var(--text-primary)' }}>
                        {item.code}
                      </span>
                      <span style={{ fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.45 }}>
                        <span style={{ color: 'var(--text-primary)', fontWeight: 500 }}>{item.label}.</span>{' '}
                        {item.desc}
                      </span>
                      <SegControl
                        value={cur}
                        options={[
                          { value: 'met',           label: 'Met' },
                          { value: 'not_evaluated', label: '—' },
                          { value: 'not_met',       label: 'Not' },
                        ]}
                        onChange={(v) => setState(item.code, v)}
                      />
                    </li>
                  );
                })}
              </ul>
            </section>
          );
        })}
      </div>

      <footer style={{
        padding: 10, borderTop: '1px solid var(--border)',
        display: 'flex', justifyContent: 'space-between', alignItems: 'center',
        background: 'var(--bg-surface)',
      }}>
        <div className="t-meta">
          {Object.values(criteriaState).filter(v => v === 'met').length} criteria met
        </div>
        <button className="btn btn-primary btn-sm" onClick={onClose}>Done</button>
      </footer>
    </div>
  );
};

// 3-state segmented control
const SegControl = ({ value, options, onChange }) => (
  <div style={{
    display: 'inline-flex',
    background: 'var(--bg-base)',
    borderRadius: 4, padding: 2,
    border: '1px solid var(--border)',
  }}>
    {options.map(o => {
      const active = value === o.value;
      const tone = o.value === 'met' ? 'danger' : o.value === 'not_met' ? 'success' : 'neutral';
      const c = ACCENT[tone];
      return (
        <button key={o.value}
                onClick={() => onChange(o.value)}
                style={{
                  appearance: 'none', border: 0, cursor: 'pointer',
                  background: active
                    ? (o.value === 'not_evaluated' ? 'var(--bg-overlay)' : `color-mix(in oklab, ${c} 22%, transparent)`)
                    : 'transparent',
                  color: active ? (o.value === 'not_evaluated' ? 'var(--text-secondary)' : c) : 'var(--text-tertiary)',
                  fontSize: 10, fontWeight: 600, fontFamily: 'inherit',
                  padding: '3px 8px', borderRadius: 3, minWidth: 24,
                  transition: 'background 100ms ease',
                }}>
          {o.label}
        </button>
      );
    })}
  </div>
);

// ── Tag badges + popover ────────────────────────────────────────────────────
const TagBadge = ({ tag }) => {
  const meta = window.CONSENSIO_DATA.evidenceTags[tag];
  if (!meta) return null;
  const c = ACCENT[meta.tone];
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 3,
      height: 16, padding: '0 5px', borderRadius: 3,
      fontSize: 10, fontWeight: 500,
      background: `color-mix(in oklab, ${c} 14%, transparent)`,
      color: c,
      border: '1px solid ' + `color-mix(in oklab, ${c} 26%, transparent)`,
    }}>
      {meta.dot && <span style={{ width: 5, height: 5, borderRadius: 3, background: c }} />}
      {meta.icon && <Icon d={I[meta.icon]} size={9} />}
      {meta.label}
    </span>
  );
};

const TagPopover = ({ anchor, tags = [], onChange, onClose }) => {
  const ref = useRef(null);
  useEffect(() => {
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose && onClose(); };
    setTimeout(() => document.addEventListener('mousedown', onDown), 0);
    return () => document.removeEventListener('mousedown', onDown);
  }, [onClose]);
  const toggle = (key) => {
    const next = tags.includes(key) ? tags.filter(t => t !== key) : [...tags, key];
    onChange && onChange(next);
  };
  const allTags = window.CONSENSIO_DATA.evidenceTags;
  return (
    <div ref={ref}
         style={{
           position: 'fixed',
           left: Math.min(anchor.left, window.innerWidth - 240),
           top: Math.min(anchor.top + anchor.height + 6, window.innerHeight - 220),
           width: 220,
           background: 'var(--bg-raised)',
           border: '1px solid var(--border-hover)',
           borderRadius: 6,
           boxShadow: '0 12px 32px rgba(0,0,0,0.45)',
           zIndex: 300,
           padding: 6,
         }}
         onClick={(e) => e.stopPropagation()}>
      <div className="t-label" style={{ padding: '6px 8px 4px' }}>Toggle tags</div>
      {Object.entries(allTags).map(([key, meta]) => {
        const active = tags.includes(key);
        const c = ACCENT[meta.tone];
        return (
          <button key={key} onClick={() => toggle(key)}
                  style={{
                    width: '100%', appearance: 'none',
                    display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                    padding: '8px 8px', borderRadius: 4,
                    background: active ? 'var(--bg-overlay)' : 'transparent',
                    border: 0, color: 'var(--text-primary)',
                    fontSize: 12, cursor: 'pointer', fontFamily: 'inherit',
                  }}>
            <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
              {meta.dot && <span style={{ width: 6, height: 6, borderRadius: 3, background: c }} />}
              {meta.icon && <Icon d={I[meta.icon]} size={11} />}
              {meta.label}
            </span>
            {active && <Icon d={I.check} size={12} />}
          </button>
        );
      })}
    </div>
  );
};

// ── PHI mask utility ────────────────────────────────────────────────────────
const maskPHI = (s) => {
  if (!window.__phiMask) return s;
  if (typeof s !== 'string') return s;
  // case ID family-2024-018 → FAM-XXXX-018; bare family names → FAM-XXXX
  if (/^[a-z]+-\d{4}-\d{3}$/i.test(s)) {
    return s.replace(/^[a-z]+/i, 'FAM-XXXX');
  }
  if (/^[A-Z][a-z]+$/.test(s) && s.length < 14) return 'FAM-XXXX';
  if (/^F-\d{3}-\d{2}$/.test(s)) return 'IND-XXXX-' + s.slice(-2);
  return s;
};

// ── Audit event utility ─────────────────────────────────────────────────────
// HIPAA access-log requirement — fired on classification, note edits,
// AI evidence views, case exports, presentation generation.
const audit = (action, resourceId, extra = {}) => {
  const event = {
    userId: window.CONSENSIO_DATA?.user?.name || 'unknown',
    action, resourceId,
    timestamp: new Date().toISOString(),
    ...extra,
  };
  window.__audit = window.__audit || [];
  window.__audit.push(event);
  try {
    const stored = JSON.parse(localStorage.getItem('consensio_audit_log') || '[]');
    stored.push(event);
    localStorage.setItem('consensio_audit_log', JSON.stringify(stored.slice(-5000)));
  } catch (_) {}
  // eslint-disable-next-line no-console
  console.info('[audit]', action, resourceId, event);
};

// ── HPOTerm — external-link affordance for HPO ontology terms ───────────────
const HPOTerm = ({ hpoId, label, onset }) => (
  <a
    href={`https://hpo.jax.org/app/browse/term/${hpoId}`}
    target="_blank"
    rel="noopener noreferrer"
    style={{
      display: 'inline-flex', alignItems: 'center', gap: 4,
      color: 'var(--text-primary)',
      textDecoration: 'none',
      borderBottom: '1px dotted var(--text-tertiary)',
      paddingBottom: 1,
    }}
    onMouseEnter={e => e.currentTarget.style.borderBottomColor = 'var(--accent-info)'}
    onMouseLeave={e => e.currentTarget.style.borderBottomColor = 'var(--text-tertiary)'}
    title={`Open ${hpoId} on hpo.jax.org`}>
    <span>{label}</span>
    <Icon d={I.external} size={10} />
  </a>
);

Object.assign(window, {
  Icon, I, Chip, ACCENT, CopyButton, Variant, SkeletonRow,
  ConsensioMark, ConsensioWordmark,
  FlagPill, sortFlags, sortCasesByFlags, FLAG_META, MATCH_TONE,
  AiBadge, AcmgChip, AcmgCriteriaPopover, ACMG_STATE, isPathogenicCriterion,
  TagBadge, TagPopover, SegControl, maskPHI,
  audit, HPOTerm,
});
