import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import type { NotificationModel, NotificationsOptions } from './types';
import { useNotifications } from './NotificationsProvider';
import './notifications.css';

type TransitionState = 'enter' | 'entered' | 'exit';

function iconFor(variant: NotificationModel['variant']) {
  switch (variant) {
    case 'success':
      return '✓';
    case 'warning':
      return '⚠';
    case 'error':
      return '✕';
    case 'info':
    default:
      return 'ℹ';
  }
}

function normalizePositionToClass(p: NotificationsOptions['position']) {
  return p ?? 'toast-top-right';
}

export function NotificationsViewport() {
  const { notifications, options, clearById } = useNotifications();

  const grouped = useMemo(() => {
    const map = new Map<string, NotificationModel[]>();
    for (const n of notifications) {
      const key = n.position;
      map.set(key, [...(map.get(key) ?? []), n]);
    }
    return map;
  }, [notifications]);

  return (
    <>
      {[...grouped.entries()].map(([pos, list]) => (
        <div key={pos} className={['ccl-notify-viewport', `ccl-notify-viewport--${normalizePositionToClass(pos as any)}`].join(' ')}>
          {list.map((n) => (
            <NotificationCard key={n.id} n={n} options={options} onClose={() => clearById(n.id)} />
          ))}
        </div>
      ))}
    </>
  );
}

function NotificationCard({
  n,
  options,
  onClose,
}: {
  n: NotificationModel;
  options: Required<NotificationsOptions>;
  onClose: () => void;
}) {
  const [tState, setTState] = useState<TransitionState>('enter');
  const [progress, setProgress] = useState(100);
  const closeTimeout = useRef<number | null>(null);
  const progressTimer = useRef<number | null>(null);
  const remainingMsRef = useRef(n.durationMs);
  const startedAtRef = useRef<number | null>(null);

  const clearTimers = useCallback(() => {
    if (closeTimeout.current != null) window.clearTimeout(closeTimeout.current);
    if (progressTimer.current != null) window.clearInterval(progressTimer.current);
    closeTimeout.current = null;
    progressTimer.current = null;
  }, []);

  const close = useCallback(() => {
    if (tState === 'exit') return;
    setTState('exit');
    clearTimers();
    window.setTimeout(onClose, options.hideDurationMs);
  }, [clearTimers, onClose, options.hideDurationMs, tState]);

  const start = useCallback(
    (durationMs: number, startProgress: number) => {
      clearTimers();
      remainingMsRef.current = durationMs;
      startedAtRef.current = Date.now();

      if (durationMs <= 0) return;
      closeTimeout.current = window.setTimeout(() => close(), durationMs);

      if (options.progressBar) {
        setProgress(startProgress);
        const interval = 50;
        progressTimer.current = window.setInterval(() => {
          const startedAt = startedAtRef.current ?? Date.now();
          const elapsed = Date.now() - startedAt;
          const next = Math.max(0, 100 - (elapsed / durationMs) * 100);
          setProgress(next);
          if (next <= 0) {
            if (progressTimer.current != null) window.clearInterval(progressTimer.current);
            progressTimer.current = null;
          }
        }, interval);
      }
    },
    [clearTimers, close, options.progressBar]
  );

  useEffect(() => {
    const raf = window.requestAnimationFrame(() => setTState('entered'));
    start(n.durationMs, 100);
    return () => {
      window.cancelAnimationFrame(raf);
      clearTimers();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const onMouseEnter = useCallback(() => {
    if (!options.pauseOnHover) return;
    if (startedAtRef.current == null) return;
    const elapsed = Date.now() - startedAtRef.current;
    const remaining = Math.max(0, remainingMsRef.current - elapsed);
    remainingMsRef.current = remaining;
    clearTimers();
  }, [clearTimers, options.pauseOnHover]);

  const onMouseLeave = useCallback(() => {
    if (!options.pauseOnHover) return;
    if (remainingMsRef.current <= 0) return;
    const startProgress = options.progressBar ? (remainingMsRef.current / n.durationMs) * 100 : 100;
    start(remainingMsRef.current, startProgress);
  }, [n.durationMs, options.pauseOnHover, options.progressBar, start]);

  const animationClass = useMemo(() => {
    const show = options.showMethod === 'slideDown' ? 'slide' : 'fade';
    const hide = options.hideMethod === 'slideUp' ? 'slide' : 'fade';
    return `ccl-notify--anim-${tState === 'exit' ? hide : show}`;
  }, [options.hideMethod, options.showMethod, tState]);

  return (
    <div
      className={[
        'ccl-notify',
        `ccl-notify--${n.variant}`,
        `ccl-notify--${tState}`,
        animationClass,
      ].join(' ')}
      style={
        {
          ['--ccl-notify-show-ms' as never]: `${options.showDurationMs}ms`,
          ['--ccl-notify-hide-ms' as never]: `${options.hideDurationMs}ms`,
        } as React.CSSProperties
      }
      role="status"
      aria-live="polite"
      onMouseEnter={onMouseEnter}
      onMouseLeave={onMouseLeave}
    >
      <div className="ccl-notify__body">
        <span className="ccl-notify__icon" aria-hidden>
          {iconFor(n.variant)}
        </span>
        <div className="ccl-notify__content">
          {n.title ? <div className="ccl-notify__title">{n.title}</div> : null}
          <div className="ccl-notify__message">{n.message}</div>
        </div>
        {options.closeButton ? (
          <button type="button" className="ccl-notify__close" aria-label="Close" onClick={close}>
            <svg className="ccl-notify__close-icon" viewBox="0 0 16 16" aria-hidden focusable="false">
              <path
                d="M2.146 2.146a.5.5 0 0 1 .708 0L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854a.5.5 0 0 1 0-.708z"
                fill="currentColor"
              />
            </svg>
          </button>
        ) : null}
      </div>

      {options.progressBar ? (
        <div className="ccl-notify__progress" aria-hidden>
          <div className="ccl-notify__progress-bar" style={{ width: `${progress}%` }} />
        </div>
      ) : null}
    </div>
  );
}

