/* ============================================================
   AGENDA — Componentes base (exportados em window)
   ============================================================ */
const { useState, useEffect, useRef, useCallback } = React;

/* ---------- Ícones (line icons) ---------- */
const ICONS = {
  dashboard: 'M3 3h7v7H3zM14 3h7v4h-7zM14 11h7v10h-7zM3 14h7v7H3z',
  calendar: 'M8 2v4M16 2v4M3 9h18M3 6a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v13a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z',
  users: 'M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75',
  user: 'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8',
  userX: 'M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8M17 8l5 5M22 8l-5 5',
  tag: 'M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82zM7 7h.01',
  team: 'M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75',
  wallet: 'M21 12V7H5a2 2 0 0 1 0-4h14v4M3 5v14a2 2 0 0 0 2 2h16v-5M18 12a2 2 0 0 0 0 4h4v-4z',
  check2: 'M9 11l3 3L22 4M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11',
  history: 'M3 3v5h5M3.05 13A9 9 0 1 0 6 5.3L3 8M12 7v5l4 2',
  bell: 'M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 0 1-3.46 0',
  settings: 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z',
  plus: 'M12 5v14M5 12h14',
  search: 'M21 21l-4.35-4.35M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z',
  sun: 'M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10zM12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4',
  moon: 'M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z',
  chevDown: 'M6 9l6 6 6-6',
  chevRight: 'M9 18l6-6-6-6',
  chevLeft: 'M15 18l-6-6 6-6',
  x: 'M18 6L6 18M6 6l12 12',
  check: 'M20 6L9 17l-5-5',
  clock: 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 6v6l4 2',
  phone: 'M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z',
  mail: 'M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2zM22 6l-10 7L2 6',
  dots: 'M12 13a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM12 6a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM12 20a1 1 0 1 0 0-2 1 1 0 0 0 0 2z',
  edit: 'M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4z',
  trash: 'M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2',
  filter: 'M22 3H2l8 9.46V19l4 2v-8.54z',
  trendUp: 'M23 6l-9.5 9.5-5-5L1 18M17 6h6v6',
  trendDown: 'M23 18l-9.5-9.5-5 5L1 6M17 18h6v-6',
  card: 'M1 4h22v16H1zM1 10h22',
  building: 'M3 21h18M5 21V7l8-4v18M19 21V11l-6-3M9 9h.01M9 12h.01M9 15h.01M9 18h.01',
  health: 'M22 12h-4l-3 9L9 3l-3 9H2',
  scissors: 'M6 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM20 4L8.12 15.88M14.47 14.48L20 20M8.12 8.12L12 12',
  briefcase: 'M20 7h-16a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2zM16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16',
  whatsapp: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8z',
  pin: 'M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0zM12 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',
  star: 'M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01z',
  logout: 'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9',
  grid: 'M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z',
  list: 'M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01',
  arrowRight: 'M5 12h14M12 5l7 7-7 7',
  zap: 'M13 2L3 14h9l-1 8 10-12h-9z',
  download: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3',
  print: 'M6 9V2h12v7M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2M6 14h12v8H6zM18 12h.01',
  copy: 'M20 9H11a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2zM5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1',
  info: 'M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20zM12 16v-4M12 8h.01',
  doc: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M16 13H8M16 17H8M10 9H8',
  refresh: 'M23 4v6h-6M20.49 15a9 9 0 1 1-2.12-9.36L23 10',
};
function Icon({ name, size = 18, stroke = 2, fill = false, style }) {
  const d = ICONS[name] || ICONS.info;
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill={fill ? 'currentColor' : 'none'}
      stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" style={style}>
      {d.split('M').filter(Boolean).map((seg, i) => <path key={i} d={'M' + seg} />)}
    </svg>
  );
}

/* ---------- Avatar ---------- */
const AV_COLORS = {
  blue: 'var(--ev-blue)', teal: 'var(--ev-teal)', violet: 'var(--ev-violet)',
  cyan: 'var(--ev-cyan)', amber: 'var(--ev-amber)', rose: 'var(--ev-rose)',
  green: 'var(--ev-green)', gray: 'var(--ev-gray)',
};
function Avatar({ initials, color = 'blue', size = 36, ring = false }) {
  return (
    <div className={'avatar' + (ring ? ' ring' : '')}
      style={{ width: size, height: size, background: AV_COLORS[color] || color, fontSize: size * 0.38 }}>
      {initials}
    </div>
  );
}

/* ---------- Badge ---------- */
function Badge({ children, cls = 'plain', dot = false }) {
  return <span className={'badge ' + cls}>{dot && <i className="d" />}{children}</span>;
}

/* ---------- Button ---------- */
function Button({ children, variant = 'default', size, icon, iconRight, block, onClick, disabled, type }) {
  const cls = ['btn', variant !== 'default' ? variant : '', size || '', block ? 'block' : ''].filter(Boolean).join(' ');
  return (
    <button type={type || 'button'} className={cls} onClick={onClick} disabled={disabled}>
      {icon && <Icon name={icon} size={16} stroke={2.2} />}
      {children}
      {iconRight && <Icon name={iconRight} size={16} stroke={2.2} />}
    </button>
  );
}

/* ---------- Modal ---------- */
function Modal({ title, sub, children, footer, onClose, wide }) {
  useEffect(() => {
    const h = (e) => e.key === 'Escape' && onClose();
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [onClose]);
  // Portal para document.body: imune a ancestrais com transform/filter
  // (que quebrariam o position:fixed do overlay).
  return ReactDOM.createPortal(
    <div className="overlay" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
      <div className={'modal' + (wide ? ' wide' : '')}>
        <div className="modal-head">
          <div style={{ flex: 1 }}>
            <h3>{title}</h3>
            {sub && <div className="muted" style={{ fontSize: 12.5, marginTop: 2 }}>{sub}</div>}
          </div>
          <button className="iconbtn" onClick={onClose}><Icon name="x" size={18} /></button>
        </div>
        <div className="modal-body">{children}</div>
        {footer && <div className="modal-foot">{footer}</div>}
      </div>
    </div>,
    document.body
  );
}

/* ---------- Drawer ---------- */
function Drawer({ children, onClose, head, footer, className }) {
  useEffect(() => {
    const h = (e) => e.key === 'Escape' && onClose();
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [onClose]);
  // Portal para document.body: garante que o drawer ocupe a viewport inteira
  // mesmo quando aberto a partir de uma tela com animação/transform.
  return ReactDOM.createPortal(
    <div className="drawer-wrap" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
      <div className="overlay" style={{ position: 'absolute', zIndex: 0, animation: 'fade .2s forwards' }} onMouseDown={onClose} />
      <div className={'drawer' + (className ? ' ' + className : '')} style={{ position: 'relative', zIndex: 1 }}>
        <div className="drawer-head">{head}</div>
        <div className="drawer-body">{children}</div>
        {footer && <div className="drawer-foot">{footer}</div>}
      </div>
    </div>,
    document.body
  );
}

/* ---------- Field ---------- */
function Field({ label, optional, children, full }) {
  return (
    <div className={'field' + (full ? ' full' : '')}>
      {label && <label>{label}{optional && <span className="opt"> (opcional)</span>}</label>}
      {children}
    </div>
  );
}
function Input(props) { return <input className="input" {...props} />; }
function Textarea(props) { return <textarea className="textarea" {...props} />; }
function Select({ children, ...props }) { return <select className="selectbox" {...props}>{children}</select>; }

function ColorPicker({ value, onChange, usedColors = [], currentColor = '', label = 'Cor da agenda', optional = true }) {
  const DB = window.DB || {};
  const normalize = DB.normalizeColor || ((c) => String(c || '').trim().toLowerCase() || 'gray');
  const swatches = DB.personColorSwatches || [];
  const selected = normalize(value);
  const current = normalize(currentColor || value);
  const used = new Set((usedColors || []).map(normalize).filter((c) => c && c !== 'gray' && c !== current));
  const taken = selected !== 'gray' && used.has(selected);
  const customValue = /^#[0-9a-f]{6}$/i.test(selected) ? selected : '#d1d5db';
  return (
    <Field label={label} optional={optional} full>
      <div className="row wrap" style={{ gap: 8 }}>
        {swatches.map((item) => {
          const color = normalize(item.value);
          const disabled = used.has(color);
          const active = selected === color;
          return (
            <button type="button" key={item.value} disabled={disabled}
              title={disabled ? `${item.label} ja esta em uso` : item.label}
              aria-label={item.label}
              onClick={() => onChange && onChange(item.value)}
              style={{
                width: 32, height: 32, borderRadius: 8, border: active ? '2px solid var(--text)' : '1px solid var(--border-strong)',
                background: DB.colorValue ? DB.colorValue(item.value) : item.value,
                opacity: disabled ? .28 : 1, cursor: disabled ? 'not-allowed' : 'pointer',
                boxShadow: active ? '0 0 0 3px var(--primary-soft)' : 'none',
              }}
            />
          );
        })}
        <label className="btn outline sm" style={{ height: 32, gap: 8, cursor: 'pointer' }}>
          <input type="color" value={customValue} onChange={(e) => onChange && onChange(e.target.value.toLowerCase())}
            style={{ width: 18, height: 18, border: 0, padding: 0, background: 'transparent' }} />
          Personalizada
        </label>
      </div>
      <div className={'field-hint' + (taken ? ' err' : '')} style={{ marginTop: 6 }}>
        {taken ? 'Esta cor ja esta sendo usada por outra pessoa.' : 'Se nenhuma cor for escolhida, sera usado cinza claro.'}
      </div>
    </Field>
  );
}

/* ---------- Input com máscara (telefone, CPF, CEP) ----------
   `mask` recebe o texto e devolve já formatado. O onChange recebe um
   evento com o valor mascarado — compatível com os handlers `set('campo')`. */
function MaskInput({ mask, value, onChange, ...props }) {
  const apply = mask ? (v) => window.DB.mask[mask](v) : (v) => v;
  return (
    <input className="input" inputMode="numeric" value={value || ''}
      onChange={(e) => onChange && onChange({ target: { value: apply(e.target.value) } })}
      {...props} />
  );
}

/* ---------- Input monetário (R$) ----------
   Mantém o valor como número; exibe formatado. onChange recebe o número. */
function MoneyInput({ value, onChange, ...props }) {
  return (
    <input className="input" inputMode="numeric" placeholder="R$ 0,00"
      value={window.DB.moneyDisplay(value)}
      onChange={(e) => onChange && onChange(window.DB.moneyFromDigits(e.target.value))}
      {...props} />
  );
}

/* ---------- Toggle ---------- */
function Switch({ on, onClick, disabled = false }) {
  return <button type="button" className={'switch' + (on ? ' on' : '')} onClick={disabled ? undefined : onClick} aria-pressed={on} disabled={disabled} />;
}

/* ---------- Segmented ---------- */
function Seg({ options, value, onChange }) {
  return (
    <div className="seg">
      {options.map(o => (
        <button key={o.value} className={value === o.value ? 'active' : ''} onClick={() => onChange(o.value)}>{o.label}</button>
      ))}
    </div>
  );
}

/* ---------- Checkbox ---------- */
function Checkbox({ on, onClick }) {
  return <button className={'cbx' + (on ? ' on' : '')} onClick={onClick}><Icon name="check" size={13} stroke={3} /></button>;
}

/* ---------- Dropdown menu ---------- */
function Menu({ trigger, children, align = 'right', width }) {
  const [open, setOpen] = useState(false);
  const [pos, setPos] = useState(null);
  const ref = useRef(null);
  const menuRef = useRef(null);
  useEffect(() => {
    if (!open) return;
    const h = (e) => {
      if (ref.current && !ref.current.contains(e.target) && (!menuRef.current || !menuRef.current.contains(e.target))) setOpen(false);
    };
    const close = () => setOpen(false);
    document.addEventListener('mousedown', h);
    window.addEventListener('scroll', close, true);
    window.addEventListener('resize', close);
    return () => {
      document.removeEventListener('mousedown', h);
      window.removeEventListener('scroll', close, true);
      window.removeEventListener('resize', close);
    };
  }, [open]);
  const toggle = () => {
    if (!open && ref.current) {
      const r = ref.current.getBoundingClientRect();
      const up = window.innerHeight - r.bottom < 280 && r.top > 280; // perto do rodapé, abre para cima
      setPos({
        top: up ? 'auto' : r.bottom + 6,
        bottom: up ? window.innerHeight - r.top + 6 : 'auto',
        left: align === 'left' ? r.left : 'auto',
        right: align === 'left' ? 'auto' : window.innerWidth - r.right,
      });
    }
    setOpen(o => !o);
  };
  // Portal + position:fixed: o menu nunca é cortado por containers
  // com overflow (cards de tabela, drawers).
  return (
    <div ref={ref} style={{ position: 'relative' }}>
      <div onClick={toggle}>{trigger}</div>
      {open && pos && ReactDOM.createPortal(
        <div ref={menuRef} className="menu" style={{ position: 'fixed', ...pos, minWidth: width || 190 }}
          onClick={() => setOpen(false)}>{children}</div>,
        document.body
      )}
    </div>
  );
}

/* ---------- EmptyState ---------- */
function Empty({ icon = 'search', title, desc, action }) {
  return (
    <div className="empty">
      <div className="ic"><Icon name={icon} size={24} /></div>
      <h4>{title}</h4>
      {desc && <div style={{ marginBottom: action ? 16 : 0 }}>{desc}</div>}
      {action}
    </div>
  );
}

/* ---------- Toast ---------- */
function useToasts() {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((msg, icon = 'check') => {
    const id = Math.random();
    setToasts(t => [...t, { id, msg, icon }]);
    setTimeout(() => setToasts(t => t.filter(x => x.id !== id)), 4000);
  }, []);
  const close = useCallback((id) => {
    setToasts(t => t.filter(x => x.id !== id));
  }, []);
  const node = (
    <div className="toast-wrap">
      {toasts.map(t => (
        <div className="toast" key={t.id}>
          <span className="ic"><Icon name={t.icon} size={17} stroke={2.5} /></span>
          <span className="toast-msg">{t.msg}</span>
          <button type="button" className="toast-close" title="Fechar aviso" onClick={() => close(t.id)}>
            <Icon name="x" size={15} stroke={2.4} />
          </button>
        </div>
      ))}
    </div>
  );
  return [push, node];
}

/* helpers */
function timeToMin(t) {
  const [h, m] = String(t || '').split(':').map(Number);
  return Number.isFinite(h) && Number.isFinite(m) ? h * 60 + m : 0;
}
function minToTime(m) {
  const v = Math.max(0, Math.min(1439, Number(m) || 0));
  return `${String(Math.floor(v / 60)).padStart(2, '0')}:${String(v % 60).padStart(2, '0')}`;
}
function addMinutes(start, minutes) { return minToTime(timeToMin(start) + minutes); }
function endTime(start, dur) { return addMinutes(start, dur); }
function apptEnd(a) { return a && a.end ? a.end : endTime(a.start, a.dur || 30); }
function apptDur(a) {
  const dur = timeToMin(apptEnd(a)) - timeToMin(a.start);
  return dur > 0 ? dur : (a && a.dur) || 30;
}

Object.assign(window, {
  Icon, Avatar, Badge, Button, Modal, Drawer, Field, Input, Textarea, Select, ColorPicker,
  MaskInput, MoneyInput,
  Switch, Seg, Checkbox, Menu, Empty, useToasts, AV_COLORS,
  timeToMin, minToTime, addMinutes, endTime, apptEnd, apptDur,
});
