/* global React, Icon */

function classNames(...values) {
  return values.filter(Boolean).join(' ');
}

function Button({
  children,
  className = '',
  disabled = false,
  href = '',
  loading = false,
  loadingLabel = '',
  onClick = undefined,
  rel = undefined,
  size = 'md',
  target = undefined,
  type = 'button',
  variant = 'secondary',
  ...props
}) {
  const variantClass = variant === 'primary'
    ? 'btn-primary'
    : variant === 'danger'
      ? 'btn-danger'
      : variant === 'ghost'
        ? 'btn-ghost'
        : '';
  const sizeClass = size === 'sm'
    ? 'btn-sm'
    : size === 'icon'
      ? 'btn-icon'
      : '';
  const buttonType = type === 'submit' || type === 'reset' ? type : 'button';
  const isDisabled = disabled || loading;
  const content = loading ? (
    <>
      <LoadingSpinner size="sm" label={loadingLabel || 'Loading'} />
      {loadingLabel || children}
    </>
  ) : children;

  function handleLinkClick(event) {
    if (isDisabled) {
      event.preventDefault();
      event.stopPropagation();
      return;
    }
    onClick?.(event);
  }

  if (href) {
    return (
      <a
        {...props}
        href={isDisabled ? undefined : href}
        target={target}
        rel={rel}
        aria-disabled={isDisabled ? 'true' : undefined}
        onClick={handleLinkClick}
        className={classNames('btn', variantClass, sizeClass, className)}>
        {content}
      </a>
    );
  }

  return (
    <button
      {...props}
      onClick={onClick}
      type={buttonType}
      disabled={isDisabled}
      aria-busy={loading ? 'true' : undefined}
      className={classNames('btn', variantClass, sizeClass, className)}>
      {content}
    </button>
  );
}

function LoadingSpinner({ className = '', label = 'Loading', size = 'md', decorative = false }) {
  const sizeClass = size === 'sm'
    ? 'loading-spinner-sm'
    : size === 'lg'
      ? 'loading-spinner-lg'
      : '';
  return (
    <span
      className={classNames('loading-spinner', sizeClass, className)}
      role={decorative ? undefined : 'img'}
      aria-label={decorative ? undefined : label}
      aria-hidden={decorative ? true : undefined} />
  );
}

function PlanLimitMeter({ label, state }) {
  if (!state) return null;
  const value = !state.known
    ? '-'
    : state.limit == null
      ? 'Unlimited'
      : `${state.usage}/${state.limit}`;
  return (
    <div className="plan-limit-meter">
      <span>{label}</span>
      <strong>{value}</strong>
    </div>
  );
}

const escapeCloseStack = [];

function handleEscapeKeyDown(event) {
  if (event.key !== 'Escape') return;
  const active = escapeCloseStack[escapeCloseStack.length - 1];
  if (!active) return;
  const state = active.stateRef.current;
  event.preventDefault();
  event.stopPropagation();
  event.stopImmediatePropagation?.();
  if (state.disabled) return;
  state.onClose?.(event);
}

function useEscapeToClose({ enabled = true, disabled = false, onClose }) {
  const stateRef = React.useRef({ disabled, onClose });

  React.useLayoutEffect(() => {
    stateRef.current = { disabled, onClose };
  }, [disabled, onClose]);

  React.useEffect(() => {
    if (!enabled) return undefined;
    const entry = { stateRef };
    escapeCloseStack.push(entry);
    if (escapeCloseStack.length === 1) {
      document.addEventListener('keydown', handleEscapeKeyDown, true);
    }
    return () => {
      const index = escapeCloseStack.indexOf(entry);
      if (index >= 0) escapeCloseStack.splice(index, 1);
      if (escapeCloseStack.length === 0) {
        document.removeEventListener('keydown', handleEscapeKeyDown, true);
      }
    };
  }, [enabled]);
}

function ConfirmDialog({
  open,
  title,
  description,
  confirmLabel = 'Confirm',
  confirmBusyLabel = 'Working...',
  cancelLabel = 'Cancel',
  tone = 'default',
  busy = false,
  onConfirm,
  onCancel,
}) {
  const cancelRef = React.useRef(null);
  const dialogRef = React.useRef(null);
  const busyRef = React.useRef(busy);
  const onCancelRef = React.useRef(onCancel);
  const titleId = React.useId();
  const descriptionId = React.useId();

  React.useLayoutEffect(() => {
    busyRef.current = busy;
    onCancelRef.current = onCancel;
  }, [busy, onCancel]);

  React.useEffect(() => {
    if (!open) return undefined;

    const previousActiveElement = document.activeElement;
    const handleKeyDown = (event) => {
      if (event.key === 'Escape' && !busyRef.current) onCancelRef.current();
      if (event.key !== 'Tab' || !dialogRef.current) return;

      const focusable = Array.from(dialogRef.current.querySelectorAll('button:not(:disabled)'));
      if (focusable.length === 0) return;

      const first = focusable[0];
      const last = focusable[focusable.length - 1];
      if (event.shiftKey && document.activeElement === first) {
        event.preventDefault();
        if (last instanceof HTMLElement) last.focus();
      } else if (!event.shiftKey && document.activeElement === last) {
        event.preventDefault();
        if (first instanceof HTMLElement) first.focus();
      }
    };

    document.addEventListener('keydown', handleKeyDown);
    requestAnimationFrame(() => cancelRef.current?.focus());

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
      if (previousActiveElement instanceof HTMLElement && previousActiveElement.isConnected) {
        previousActiveElement.focus();
      }
    };
  }, [open]);

  if (!open) return null;

  const isDanger = tone === 'danger';

  return (
    <div
      className="dialog-backdrop"
      onMouseDown={(event) => {
        if (event.target === event.currentTarget && !busy) onCancel();
      }}>
      <div
        ref={dialogRef}
        className="confirm-dialog"
        role="alertdialog"
        aria-modal="true"
        aria-labelledby={titleId}
        aria-describedby={descriptionId}>
        <div className={isDanger ? 'confirm-dialog-icon danger' : 'confirm-dialog-icon'}>
          <Icon name={isDanger ? 'alert' : 'check'} size={18} />
        </div>
        <div className="confirm-dialog-copy">
          <div className="confirm-dialog-title" id={titleId}>{title}</div>
          <div className="confirm-dialog-description" id={descriptionId}>{description}</div>
          <div className="confirm-dialog-actions">
            <button ref={cancelRef} type="button" className="btn" disabled={busy} onClick={onCancel}>{cancelLabel}</button>
            <Button
              variant={isDanger ? 'danger' : 'primary'}
              loading={busy}
              loadingLabel={confirmBusyLabel}
              onClick={onConfirm}>
              {confirmLabel}
            </Button>
          </div>
        </div>
      </div>
    </div>
  );
}

function Drawer({
  open,
  onClose,
  title,
  subtitle,
  size = 'md',
  children,
  footer = null,
}) {
  const drawerRef = React.useRef(null);
  const onCloseRef = React.useRef(onClose);

  React.useLayoutEffect(() => {
    onCloseRef.current = onClose;
  }, [onClose]);

  React.useEffect(() => {
    if (!open) return undefined;
    const previousActiveElement = document.activeElement;
    function handleKeyDown(event) {
      if (event.key === 'Escape') {
        event.stopPropagation();
        onCloseRef.current?.();
      }
    }
    document.addEventListener('keydown', handleKeyDown);
    const focusFrame = window.requestAnimationFrame(() => {
      const focusable = drawerRef.current?.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
      if (focusable instanceof HTMLElement) focusable.focus();
    });
    document.body.style.overflow = 'hidden';
    return () => {
      document.removeEventListener('keydown', handleKeyDown);
      window.cancelAnimationFrame(focusFrame);
      document.body.style.overflow = '';
      if (previousActiveElement instanceof HTMLElement && previousActiveElement.isConnected) {
        previousActiveElement.focus();
      }
    };
  }, [open]);

  if (!open) return null;
  const sizeClass = size === 'lg' ? 'ui-drawer-lg' : size === 'sm' ? '' : 'ui-drawer-md';

  return (
    <>
      <div
        className="ui-drawer-backdrop"
        onMouseDown={(event) => {
          if (event.target === event.currentTarget) onClose?.();
        }} />
      <aside
        ref={drawerRef}
        className={classNames('ui-drawer', sizeClass)}
        role="dialog"
        aria-modal="true"
        aria-label={typeof title === 'string' ? title : undefined}>
        <header className="ui-drawer-header">
          <div className="ui-col" style={{ flex: 1, minWidth: 0, gap: 2 }}>
            {title && <div className="ui-drawer-title">{title}</div>}
            {subtitle && <div className="ui-drawer-sub">{subtitle}</div>}
          </div>
          <button
            type="button"
            className="ui-drawer-close"
            onClick={onClose}
            aria-label="Close">
            <Icon name="x" size={14} />
          </button>
        </header>
        <div className="ui-drawer-body">{children}</div>
        {footer && <footer className="ui-drawer-footer">{footer}</footer>}
      </aside>
    </>
  );
}

/** @type {React.Context<{ show: (opts: any) => number, dismiss: (id: number) => void }>} */
const ToastContext = React.createContext({
  show: (/** @type {any} */ _opts) => /** @type {number} */ (0),
  dismiss: (/** @type {number} */ _id) => {},
});

function ToastProvider({ children }) {
  const [items, setItems] = React.useState([]);
  const idCounter = React.useRef(0);
  const timersRef = React.useRef(new Map());

  const dismiss = React.useCallback((id) => {
    setItems((cur) => cur.map((t) => (t.id === id ? { ...t, leaving: true } : t)));
    const removeTimer = setTimeout(() => {
      setItems((cur) => cur.filter((t) => t.id !== id));
      timersRef.current.delete(`r-${id}`);
    }, 240);
    timersRef.current.set(`r-${id}`, removeTimer);
  }, []);

  const show = React.useCallback((opts) => {
    const o = typeof opts === 'string' ? { title: opts } : (opts || {});
    idCounter.current += 1;
    const id = idCounter.current;
    const next = {
      id,
      tone: o.tone || 'info',
      title: o.title || '',
      description: o.description || '',
      duration: o.duration === 0 ? 0 : (o.duration || 3500),
      leaving: false,
    };
    setItems((cur) => [...cur, next]);
    if (next.duration > 0) {
      const timer = setTimeout(() => dismiss(id), next.duration);
      timersRef.current.set(id, timer);
    }
    return id;
  }, [dismiss]);

  React.useEffect(() => () => {
    timersRef.current.forEach((t) => clearTimeout(t));
    timersRef.current.clear();
  }, []);

  const contextValue = React.useMemo(() => ({ show, dismiss }), [show, dismiss]);

  return (
    <ToastContext.Provider value={contextValue}>
      {children}
      <div className="ui-toast-host" role="status" aria-live="polite">
        {items.map((t) => {
          const iconName = t.tone === 'ok'
            ? 'check'
            : t.tone === 'bad'
              ? 'alert'
              : t.tone === 'warn'
                ? 'alert'
                : 'activity';
          return (
            <div
              key={t.id}
              className={classNames('ui-toast', `ui-toast-${t.tone}`, t.leaving && 'leaving')}>
              <span className="ui-toast-icon"><Icon name={iconName} size={13} /></span>
              <div className="ui-toast-body">
                {t.title && <div className="ui-toast-title">{t.title}</div>}
                {t.description && <div className="ui-toast-desc">{t.description}</div>}
              </div>
              <button
                type="button"
                className="ui-toast-close"
                aria-label="Dismiss"
                onClick={() => dismiss(t.id)}>
                <Icon name="x" size={12} />
              </button>
            </div>
          );
        })}
      </div>
    </ToastContext.Provider>
  );
}

function useToast() {
  return React.useContext(ToastContext);
}

function Pill({ tone = 'neutral', outline = false, dot = false, children, className = '', ...props }) {
  const toneClass = tone === 'neutral' ? '' : `ui-pill-${tone}`;
  return (
    <span
      {...props}
      className={classNames('ui-pill', outline && 'ui-pill-outline', toneClass, className)}>
      {dot && <span className="ui-pill-dot" />}
      {children}
    </span>
  );
}

function StatusDot({ tone = 'neutral', className = '', label = '', ...props }) {
  const toneClass = tone === 'neutral' ? '' : `ui-status-dot-${tone}`;
  return (
    <span
      {...props}
      className={classNames('ui-status-dot', toneClass, className)}
      role={label ? 'img' : undefined}
      aria-label={label || undefined} />
  );
}

function Sparkline({ values = [], width = 88, height = 22, tone = 'neutral', showArea = true, showLastPoint = true, ariaLabel = '' }) {
  const safe = Array.isArray(values) ? values.filter((v) => Number.isFinite(v)) : [];
  if (safe.length === 0) {
    return (
      <svg className="ui-sparkline" width={width} height={height} aria-hidden="true">
        <line x1="0" y1={height - 1} x2={width} y2={height - 1} className="ui-sparkline-baseline" />
      </svg>
    );
  }
  const min = Math.min(...safe);
  const max = Math.max(...safe);
  const span = max - min || 1;
  const padX = 1;
  const padY = 2;
  const innerW = Math.max(1, width - padX * 2);
  const innerH = Math.max(1, height - padY * 2);
  const points = safe.map((v, i) => {
    const x = padX + (safe.length === 1 ? innerW / 2 : (innerW * i) / (safe.length - 1));
    const y = padY + innerH - ((v - min) / span) * innerH;
    return [x, y];
  });
  const linePath = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0].toFixed(1)},${p[1].toFixed(1)}`).join(' ');
  const areaPath = `${linePath} L${points[points.length - 1][0].toFixed(1)},${(height - padY).toFixed(1)} L${padX.toFixed(1)},${(height - padY).toFixed(1)} Z`;
  const last = points[points.length - 1];
  const toneClass = tone === 'neutral' ? '' : `ui-sparkline-tone-${tone}`;
  return (
    <svg
      className={classNames('ui-sparkline', toneClass)}
      width={width}
      height={height}
      role={ariaLabel ? 'img' : undefined}
      aria-label={ariaLabel || undefined}
      aria-hidden={ariaLabel ? undefined : 'true'}
      viewBox={`0 0 ${width} ${height}`}>
      {showArea && <path d={areaPath} className="ui-sparkline-area" />}
      <path d={linePath} className="ui-sparkline-line" />
      {showLastPoint && <circle cx={last[0]} cy={last[1]} r="1.7" className="ui-sparkline-point" />}
    </svg>
  );
}

function Select({
  value,
  options = [],
  onChange,
  placeholder = 'Select…',
  label = '',
  disabled = false,
  searchable = false,
  searchPlaceholder = 'Search…',
  className = '',
  ariaLabel = '',
  portal = false,
}) {
  const [open, setOpen] = React.useState(false);
  const [query, setQuery] = React.useState('');
  const [menuStyle, setMenuStyle] = React.useState(null);
  const rootRef = React.useRef(null);
  const menuRef = React.useRef(null);
  const searchRef = React.useRef(null);
  const updatePortalPosition = React.useCallback(() => {
    if (!portal || !rootRef.current || typeof window === 'undefined') return;
    const trigger = rootRef.current.querySelector('.custom-select-trigger');
    const rect = trigger?.getBoundingClientRect();
    if (!rect) return;
    const edge = 12;
    const gap = 6;
    const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
    const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
    const width = Math.max(120, Math.min(rect.width, viewportWidth - edge * 2));
    const left = Math.min(Math.max(edge, rect.left), Math.max(edge, viewportWidth - edge - width));
    const below = Math.max(0, viewportHeight - rect.bottom - gap - edge);
    const above = Math.max(0, rect.top - gap - edge);
    const openUp = below < 128 && above > below;
    const available = openUp ? above : below;
    setMenuStyle({
      position: 'fixed',
      left,
      width,
      maxHeight: Math.max(96, Math.min(236, available)),
      ...(openUp ? { top: 'auto', bottom: viewportHeight - rect.top + gap } : { top: rect.bottom + gap }),
    });
  }, [portal]);

  React.useEffect(() => {
    if (!open) return undefined;
    function onDocClick(event) {
      if (!rootRef.current?.contains(event.target) && !menuRef.current?.contains(event.target)) {
        setOpen(false);
        setQuery('');
      }
    }
    function onKey(event) {
      if (event.key === 'Escape') {
        event.preventDefault();
        event.stopPropagation();
        event.stopImmediatePropagation?.();
        setOpen(false);
        setQuery('');
      }
    }
    document.addEventListener('mousedown', onDocClick);
    document.addEventListener('keydown', onKey, true);
    if (searchable) {
      const t = window.requestAnimationFrame(() => searchRef.current?.focus());
      return () => {
        document.removeEventListener('mousedown', onDocClick);
        document.removeEventListener('keydown', onKey, true);
        window.cancelAnimationFrame(t);
      };
    }
    return () => {
      document.removeEventListener('mousedown', onDocClick);
      document.removeEventListener('keydown', onKey, true);
    };
  }, [open, searchable]);

  React.useLayoutEffect(() => {
    if (!open || !portal) {
      setMenuStyle(null);
      return undefined;
    }
    updatePortalPosition();
    window.addEventListener('resize', updatePortalPosition);
    window.addEventListener('scroll', updatePortalPosition, true);
    return () => {
      window.removeEventListener('resize', updatePortalPosition);
      window.removeEventListener('scroll', updatePortalPosition, true);
    };
  }, [open, portal, updatePortalPosition]);

  const selected = options.find((o) => o.value === value);
  const filtered = searchable && query
    ? options.filter((o) => String(o.label).toLowerCase().includes(query.toLowerCase()))
    : options;
  const menu = open ? (
    <div
      ref={menuRef}
      className={classNames('custom-select-menu', portal && 'custom-select-menu-portal')}
      style={portal ? (menuStyle || { position: 'fixed', visibility: 'hidden' }) : undefined}
      role="listbox"
      aria-label={ariaLabel || label || placeholder}>
      {searchable && (
        <div className="custom-select-search">
          <Icon name="search" size={12} />
          <input
            ref={searchRef}
            value={query}
            placeholder={searchPlaceholder}
            onChange={(e) => setQuery(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter' && filtered.length > 0) {
              onChange?.(filtered[0].value);
              setOpen(false);
              setQuery('');
            } }} />
        </div>
      )}
      {filtered.length === 0 ? (
        <div className="custom-select-empty">No results</div>
      ) : filtered.map((o) => (
        <button
          key={String(o.value)}
          type="button"
          className={classNames('custom-select-option', value === o.value && 'selected')}
          role="option"
          aria-selected={value === o.value}
          onClick={() => { onChange?.(o.value); setOpen(false); setQuery(''); }}>
          {o.icon && <Icon name={o.icon} size={13} />}
          <span>{o.label}</span>
          {value === o.value && <Icon name="check" size={12} className="custom-select-tick" />}
        </button>
      ))}
    </div>
  ) : null;
  const canPortalMenu = portal && typeof ReactDOM !== 'undefined' && ReactDOM.createPortal && typeof document !== 'undefined';

  return (
    <div ref={rootRef} className={classNames('custom-select', open && 'open', className)}>
      <button
        type="button"
        className="custom-select-trigger"
        disabled={disabled}
        onClick={() => setOpen((v) => {
          if (v) setQuery('');
          return !v;
        })}
        aria-expanded={open}
        aria-haspopup="listbox"
        aria-label={ariaLabel || label || placeholder}>
        {label && <span className="custom-select-label">{label}:</span>}
        <span className="custom-select-value">
          {selected ? selected.label : <span className="muted">{placeholder}</span>}
        </span>
        <Icon name="chevronDown" size={12} className="icon" />
      </button>
      {open && (canPortalMenu ? ReactDOM.createPortal(menu, document.body) : menu)}
    </div>
  );
}

window.Button = Button;
window.LoadingSpinner = LoadingSpinner;
window.PlanLimitMeter = PlanLimitMeter;
window.useEscapeToClose = useEscapeToClose;
window.ConfirmDialog = ConfirmDialog;
window.Drawer = Drawer;
window.ToastProvider = ToastProvider;
window.useToast = useToast;
window.Pill = Pill;
window.StatusDot = StatusDot;
window.Sparkline = Sparkline;
window.Select = Select;
