// =========================================================
// FBIF 资讯订阅 · Primitives
// Base controls (from FBIF Ops kit) + domain helpers:
// TierTag, QualityScore, ScoreRing, time formatting.
// All values come from the design tokens — no invented sizes.
// =========================================================

// Self-contained icon: React owns a stable <span>; the SVG is built
// imperatively INSIDE it so Lucide never replaces a React-managed node
// (avoids removeChild reconciliation crashes when icon-bearing lists re-render).
const Icon = ({ name, size = 16, color, style }) => {
  const ref = React.useRef(null);
  React.useEffect(() => {
    const el = ref.current;
    if (!el || !window.lucide) return;
    el.innerHTML = '';
    let svg = null;
    try {
      const pascal = String(name).split('-').map(s => s ? s[0].toUpperCase() + s.slice(1) : s).join('');
      const node = window.lucide.icons && window.lucide.icons[pascal];
      if (node && window.lucide.createElement) svg = window.lucide.createElement(node);
    } catch (e) { /* fall through */ }
    if (!svg) {
      const i = document.createElement('i');
      i.setAttribute('data-lucide', name);
      el.appendChild(i);
      try { window.lucide.createIcons(); } catch (e) {}
      svg = el.querySelector('svg');
    } else {
      el.appendChild(svg);
    }
    if (svg) {
      svg.setAttribute('width', size);
      svg.setAttribute('height', size);
      svg.setAttribute('stroke-width', 1.5);
      svg.style.width = size + 'px';
      svg.style.height = size + 'px';
      svg.style.color = color || 'currentColor';
      if (style && style.fill) svg.style.fill = style.fill;
    }
  }, [name, size, color, style && style.fill]);
  return <span ref={ref} aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: size, height: size, flexShrink: 0, color: color || 'currentColor', ...(style || {}) }} />;
};

// no-op: Icon self-manages its SVG now (kept for call-site compatibility)
function useLucide() {}

const Button = ({ variant = 'secondary', size = 'default', icon, iconRight, children, onClick, disabled, active, style, title }) => {
  const heights = { dense: 28, default: 32, large: 40 };
  const pads    = { dense: '0 12px', default: '0 16px', large: '0 20px' };
  const fs      = { dense: 13, default: 14, large: 14 };
  const base = {
    height: heights[size], padding: children ? pads[size] : 0,
    width: children ? undefined : heights[size],
    borderRadius: 6, fontFamily: 'inherit', fontSize: fs[size],
    fontWeight: variant === 'text' ? 400 : 500,
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6,
    cursor: disabled ? 'not-allowed' : 'pointer', border: '1px solid transparent',
    transition: 'background-color .15s ease, border-color .15s ease, color .15s ease',
    whiteSpace: 'nowrap', userSelect: 'none',
  };
  const variants = {
    primary:   { background: '#1456F0', color: '#fff', borderColor: '#1456F0' },
    secondary: { background: '#fff', color: '#1F2329', borderColor: '#DEE0E3' },
    text:      { background: 'transparent', color: '#1456F0', borderColor: 'transparent' },
    danger:    { background: '#F54A45', color: '#fff', borderColor: '#F54A45' },
    ghost:     { background: active ? '#EBF1FE' : 'transparent', color: active ? '#0F4DD1' : '#646A73', borderColor: 'transparent' },
  };
  if (disabled) {
    if (variant === 'primary') { variants.primary.background = '#A8C3FB'; variants.primary.borderColor = '#A8C3FB'; }
    if (variant === 'secondary') { variants.secondary.color = '#BBBFC4'; }
  }
  const [hover, setHover] = React.useState(false);
  const [press, setPress] = React.useState(false);
  let extra = {};
  if (!disabled && hover) {
    if (variant === 'primary')   extra = { background: '#0F4DD1', borderColor: '#0F4DD1' };
    if (variant === 'secondary') extra = { background: '#F2F3F5', borderColor: '#C9CDD4' };
    if (variant === 'text')      extra = { background: '#EBF1FE' };
    if (variant === 'danger')    extra = { background: '#D93832', borderColor: '#D93832' };
    if (variant === 'ghost')     extra = { background: '#F2F3F5' };
  }
  if (!disabled && press && variant === 'primary') extra = { background: '#0B3FBA', borderColor: '#0B3FBA' };
  return (
    <button title={title} disabled={disabled}
      style={{ ...base, ...variants[variant], ...extra, ...(style || {}) }}
      onClick={disabled ? undefined : onClick}
      onMouseEnter={() => setHover(true)} onMouseLeave={() => { setHover(false); setPress(false); }}
      onMouseDown={() => setPress(true)} onMouseUp={() => setPress(false)}>
      {icon && <Icon name={icon} size={size === 'large' ? 16 : 14} />}
      {children}
      {iconRight && <Icon name={iconRight} size={14} />}
    </button>
  );
};

const TONES = {
  info:    { bg: '#EBF1FE', fg: '#0F4DD1' },
  success: { bg: '#E8F7F0', fg: '#007A52' },
  warning: { bg: '#FFF3E5', fg: '#B45F00' },
  danger:  { bg: '#FEECEC', fg: '#B83430' },
  neutral: { bg: '#F2F3F5', fg: '#646A73' },
};

const Tag = ({ tone = 'neutral', dot = false, children, style }) => {
  const t = TONES[tone];
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 5,
      height: 20, padding: '0 7px', borderRadius: 4,
      background: t.bg, color: t.fg, fontSize: 12, lineHeight: '20px', whiteSpace: 'nowrap', ...style,
    }}>
      {dot && <span style={{ width: 6, height: 6, borderRadius: 9999, background: 'currentColor', opacity: .85 }} />}
      {children}
    </span>
  );
};

// Source avatar — rounded square w/ initial, brand color
const SourceMark = ({ short, color = '#1456F0', size = 20, radius = 5 }) => (
  <span style={{
    width: size, height: size, borderRadius: radius, background: color, color: '#fff',
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
    fontSize: size <= 20 ? 11 : 12, fontWeight: 500, letterSpacing: '-.02em',
  }}>{short}</span>
);

const Avatar = ({ name = '?', size = 28, color = '#1456F0' }) => (
  <span style={{
    width: size, height: size, borderRadius: 9999, background: color, color: '#fff',
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
    fontSize: size <= 24 ? 11 : 13, fontWeight: 500, flexShrink: 0,
  }}>{name.slice(0, 1)}</span>
);

const Input = ({ icon, placeholder, value, onChange, onKeyDown, width, height = 32, autoFocus }) => {
  const [focus, setFocus] = React.useState(false);
  return (
    <div style={{
      display: 'inline-flex', alignItems: 'center', gap: 8, height, padding: '0 10px', borderRadius: 6,
      border: `1px solid ${focus ? '#1456F0' : '#DEE0E3'}`,
      boxShadow: focus ? '0 0 0 3px rgba(20,86,240,.18)' : 'none',
      background: '#fff', width, transition: 'border-color .15s, box-shadow .15s', boxSizing: 'border-box',
    }}>
      {icon && <Icon name={icon} size={14} color="#8F959E" />}
      <input value={value} onChange={onChange} onKeyDown={onKeyDown} placeholder={placeholder} autoFocus={autoFocus}
        onFocus={() => setFocus(true)} onBlur={() => setFocus(false)}
        style={{ border: 'none', outline: 'none', flex: 1, background: 'transparent',
                 fontFamily: 'inherit', fontSize: 14, color: '#1F2329', minWidth: 0 }} />
    </div>
  );
};

const Divider = ({ vertical, style }) => (
  <div style={vertical
    ? { width: 1, alignSelf: 'stretch', background: '#E5E6EB', ...style }
    : { height: 1, width: '100%', background: '#E5E6EB', ...style }} />
);

// Portal-anchored dropdown — renders to document.body so it is NEVER clipped
// by an ancestor's overflow:hidden (e.g. the rounded source/tag table).
// Pass anchorRef (a ref on the trigger element); positions below it.
const AnchoredMenu = ({ anchorRef, align = 'right', width = 224, onClose, children }) => {
  const menuRef = React.useRef(null);
  const [pos, setPos] = React.useState(null);
  React.useLayoutEffect(() => {
    const el = anchorRef && anchorRef.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    const mh = menuRef.current ? menuRef.current.offsetHeight : 0;
    let left = align === 'right' ? r.right - width : r.left;
    left = Math.max(8, Math.min(left, window.innerWidth - width - 8));
    let top = r.bottom + 4;
    if (mh && top + mh > window.innerHeight - 8) top = Math.max(8, r.top - mh - 4); // flip up
    setPos({ top, left });
  }, []);
  const menu = (
    <>
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 300 }} />
      <div ref={menuRef} role="menu" style={{
        position: 'fixed', top: pos ? pos.top : -9999, left: pos ? pos.left : -9999, width,
        maxHeight: 'min(70vh, 460px)', overflowY: 'auto', zIndex: 301, background: '#fff', borderRadius: 8,
        border: '1px solid #DEE0E3', boxShadow: '0 8px 24px rgba(31,35,41,.12), 0 0 1px rgba(31,35,41,.2)', padding: 4,
        visibility: pos ? 'visible' : 'hidden',
      }}>{children}</div>
    </>
  );
  return ReactDOM.createPortal(menu, document.body);
};

// menu item row (for use inside AnchoredMenu)
const MenuRow = ({ icon, iconColor, label, danger, right, onClick }) => {
  const [hover, setHover] = React.useState(false);
  return (
    <div role="menuitem" onClick={onClick} onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)}
      style={{ display: 'flex', alignItems: 'center', gap: 8, minHeight: 34, padding: '0 10px', borderRadius: 6,
               cursor: onClick ? 'pointer' : 'default', fontSize: 13, color: danger ? '#B83430' : '#1F2329',
               background: hover ? (danger ? '#FEECEC' : '#F2F3F5') : 'transparent' }}>
      {icon && <Icon name={icon} size={15} color={danger ? '#B83430' : (iconColor || '#646A73')} />}
      <span style={{ flex: 1 }}>{label}</span>
      {right}
    </div>
  );
};

// Toggle switch (订阅开关)
const Switch = ({ on, onChange, disabled }) => (
  <button onClick={disabled ? undefined : () => onChange(!on)} disabled={disabled}
    style={{
      width: 36, height: 20, borderRadius: 9999, border: 'none', padding: 0, flexShrink: 0,
      background: on ? '#1456F0' : '#C9CDD4', cursor: disabled ? 'not-allowed' : 'pointer',
      position: 'relative', transition: 'background-color .15s ease', opacity: disabled ? .5 : 1,
    }}>
    <span style={{
      position: 'absolute', top: 2, left: on ? 18 : 2, width: 16, height: 16, borderRadius: 9999,
      background: '#fff', transition: 'left .15s ease', boxShadow: '0 1px 2px rgba(31,35,41,.2)',
    }} />
  </button>
);

// Source type chip (公众号 / RSS / 网站 / X)
const SourceTypeChip = ({ type, withLabel }) => {
  const m = (window.FBIF_DATA.TYPE_META || {})[type];
  if (!m) return null;
  return (
    <span title={m.label} style={{
      display: 'inline-flex', alignItems: 'center', gap: 4, height: 18, padding: withLabel ? '0 7px' : 0,
      width: withLabel ? undefined : 18, justifyContent: 'center', borderRadius: 4,
      background: '#F2F3F5', color: '#646A73', fontSize: 11, lineHeight: '18px', flexShrink: 0,
    }}>
      <Icon name={m.icon} size={12} color={m.color} />
      {withLabel && m.label}
    </span>
  );
};

// Source health badge (健康度)
const HealthBadge = ({ health, note, compact }) => {
  const m = (window.FBIF_DATA.HEALTH_META || {})[health] || {};
  if (compact) {
    return <span title={(m.label || '') + (note ? ' · ' + note : '')} style={{ width: 8, height: 8, borderRadius: 9999, background: m.color, flexShrink: 0, display: 'inline-block' }} />;
  }
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, fontSize: 12, color: m.color }}>
      <Icon name={m.icon} size={13} />
      <span>{m.label}</span>
    </span>
  );
};

// ---- Domain helpers ----------------------------------------------------

function tierTone(tier) {
  return tier === 'T1' ? 'info' : tier === 'T1.5' ? 'success' : 'neutral';
}

// Tier tag — color + TEXT (色弱友好，不只靠颜色)
const TierTag = ({ tier, compact }) => {
  const tone = tierTone(tier);
  return <Tag tone={tone} dot style={compact ? { height: 18, padding: '0 6px', fontSize: 11, lineHeight: '18px' } : undefined}>{tier}</Tag>;
};

// Quality score color ramp
function scoreColor(score) {
  if (score >= 85) return { fg: '#007A52', bg: '#E8F7F0' };
  if (score >= 70) return { fg: '#0F4DD1', bg: '#EBF1FE' };
  if (score >= 55) return { fg: '#B45F00', bg: '#FFF3E5' };
  return { fg: '#8F959E', bg: '#F2F3F5' };
}

// Quality score chip (角标) — number, mono
const QualityScore = ({ score, size = 'sm' }) => {
  const c = scoreColor(score);
  const h = size === 'lg' ? 24 : 20;
  return (
    <span title={`质量分 ${score}（MVP 为 mock 值）`} style={{
      display: 'inline-flex', alignItems: 'center', gap: 4, height: h, padding: '0 7px', borderRadius: 9999,
      background: c.bg, color: c.fg, fontFamily: 'var(--font-mono)', fontVariantNumeric: 'tabular-nums',
      fontSize: size === 'lg' ? 13 : 12, fontWeight: 500, lineHeight: `${h}px`,
    }}>
      <Icon name="sparkles" size={size === 'lg' ? 13 : 11} />
      {score}
    </span>
  );
};

// Score ring (used in reader header) — SVG donut
const ScoreRing = ({ score, size = 56, stroke = 5 }) => {
  const c = scoreColor(score);
  const r = (size - stroke) / 2;
  const circ = 2 * Math.PI * r;
  const off = circ * (1 - score / 100);
  return (
    <div style={{ position: 'relative', width: size, height: size, flexShrink: 0 }}>
      <svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="#EDEFF2" strokeWidth={stroke} />
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={c.fg} strokeWidth={stroke}
          strokeDasharray={circ} strokeDashoffset={off} strokeLinecap="round"
          style={{ transition: 'stroke-dashoffset .5s ease' }} />
      </svg>
      <div style={{
        position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      }}>
        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 16, fontWeight: 600, color: c.fg, lineHeight: 1 }}>{score}</span>
        <span style={{ fontSize: 9, color: '#8F959E', marginTop: 1 }}>质量分</span>
      </div>
    </div>
  );
};

// Horizontal score bar (for breakdown)
const ScoreBar = ({ label, value }) => {
  const c = scoreColor(value);
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
      <span style={{ fontSize: 12, color: '#646A73', width: 64, flexShrink: 0 }}>{label}</span>
      <div style={{ flex: 1, height: 6, borderRadius: 9999, background: '#EDEFF2', overflow: 'hidden' }}>
        <div style={{ width: `${value}%`, height: '100%', background: c.fg, borderRadius: 9999, transition: 'width .4s ease' }} />
      </div>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: '#1F2329', width: 24, textAlign: 'right' }}>{value}</span>
    </div>
  );
};

// time-ago formatting
function timeAgo(mins) {
  if (mins < 1) return '刚刚';
  if (mins < 60) return `${mins} 分钟前`;
  const h = Math.floor(mins / 60);
  if (h < 24) return `${h} 小时前`;
  const d = Math.floor(h / 24);
  return `${d} 天前`;
}
function fmtTime(iso) {
  const d = new Date(iso);
  const p = n => String(n).padStart(2, '0');
  return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
}

Object.assign(window, {
  Icon, useLucide, Button, Tag, SourceMark, SourceTypeChip, HealthBadge, Avatar, Input, Divider, Switch,
  AnchoredMenu, MenuRow,
  TierTag, QualityScore, ScoreRing, ScoreBar, tierTone, scoreColor, timeAgo, fmtTime,
});
