import type { HTMLAttributes, ReactNode } from 'react';
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
import './offcanvas.css';

export type OffCanvasPlacement = 'start' | 'end' | 'top' | 'bottom';
type TransitionState = 'enter' | 'entered' | 'exit';

export type OffCanvasProps = {
  isOpen?: boolean;
  title?: ReactNode;
  placement?: OffCanvasPlacement;
  /** Show backdrop behind the offcanvas (default true). */
  backdrop?: boolean;
  /** Allow body scrolling while open (default false). */
  scrollable?: boolean;
  /** Smooth open/close duration (ms). */
  transitionDurationMs?: number;
  /** Size for start/end placement (e.g. 360, '24rem'). */
  width?: number | string;
  /** Size for top/bottom placement (e.g. 280, '40vh'). */
  height?: number | string;
  ariaLabel?: string;
  hideCloseButton?: boolean;
  disableCloseButton?: boolean;
  onClose?: () => void;
  children?: ReactNode;
} & Omit<HTMLAttributes<HTMLDivElement>, 'title'>;

function toCssSize(v: number | string | undefined) {
  if (v == null) return undefined;
  return typeof v === 'number' ? `${v}px` : v;
}

export function OffCanvas({
  isOpen = false,
  title,
  placement = 'start',
  backdrop = true,
  scrollable = false,
  transitionDurationMs = 320,
  width,
  height,
  ariaLabel,
  hideCloseButton = false,
  disableCloseButton = false,
  onClose,
  children,
  className,
  ...rest
}: OffCanvasProps) {
  const uid = useId().replace(/:/g, '');
  const titleId = `ccl-offcanvas-${uid}-title`;
  const closeRef = useRef<HTMLButtonElement>(null);
  const prevActive = useRef<HTMLElement | null>(null);
  const closeTimeout = useRef<number | null>(null);

  const [rendered, setRendered] = useState(isOpen);
  const [tState, setTState] = useState<TransitionState>(isOpen ? 'entered' : 'exit');

  const hasTitle = title != null && (typeof title === 'string' ? title.trim().length > 0 : true);

  const close = useCallback(() => onClose?.(), [onClose]);

  useEffect(() => {
    if (!isOpen) return;
    prevActive.current = document.activeElement as HTMLElement;
    const t = window.setTimeout(() => {
      closeRef.current?.focus();
    }, 0);
    return () => window.clearTimeout(t);
  }, [isOpen]);

  useEffect(() => {
    if (isOpen) return;
    prevActive.current?.focus();
    prevActive.current = null;
  }, [isOpen]);

  useEffect(() => {
    if (!isOpen || scrollable || !backdrop) return;
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => {
      document.body.style.overflow = prevOverflow;
    };
  }, [backdrop, isOpen, scrollable]);

  useEffect(() => {
    if (closeTimeout.current != null) {
      window.clearTimeout(closeTimeout.current);
      closeTimeout.current = null;
    }

    if (isOpen) {
      setRendered(true);
      setTState('enter');
      const raf = window.requestAnimationFrame(() => setTState('entered'));
      return () => window.cancelAnimationFrame(raf);
    }

    if (rendered) {
      setTState('exit');
      closeTimeout.current = window.setTimeout(() => setRendered(false), transitionDurationMs);
    }
    return undefined;
  }, [isOpen, rendered, transitionDurationMs]);

  useEffect(() => {
    return () => {
      if (closeTimeout.current != null) window.clearTimeout(closeTimeout.current);
    };
  }, []);

  const overlayClassName = useMemo(() => {
    return [
      'ccl-offcanvas-overlay',
      backdrop ? 'ccl-offcanvas-overlay--backdrop' : 'ccl-offcanvas-overlay--no-backdrop',
      `ccl-offcanvas-overlay--${tState}`,
      className ?? '',
    ]
      .filter(Boolean)
      .join(' ');
  }, [backdrop, className, tState]);

  const panelClassName = useMemo(() => {
    return ['ccl-offcanvas', `ccl-offcanvas--${placement}`, `ccl-offcanvas--${tState}`].join(' ');
  }, [placement, tState]);

  const panelStyle = useMemo(() => {
    const style: React.CSSProperties = {
      ['--ccl-offcanvas-transition-ms' as never]: `${transitionDurationMs}ms`,
    };
    if (placement === 'start' || placement === 'end') style.width = toCssSize(width ?? 360);
    if (placement === 'top' || placement === 'bottom') style.height = toCssSize(height ?? 280);
    return style;
  }, [height, placement, transitionDurationMs, width]);

  const onOverlayClick = useCallback(
    (e: React.MouseEvent<HTMLDivElement>) => {
      if (!backdrop) return;
      if (e.target === e.currentTarget) close();
    },
    [backdrop, close]
  );

  const onKeyDown = useCallback(
    (e: React.KeyboardEvent) => {
      if (e.key === 'Escape') close();
    },
    [close]
  );

  if (!rendered) return null;

  return (
    <div
      className={overlayClassName}
      role="dialog"
      aria-modal={backdrop ? 'true' : undefined}
      aria-labelledby={hasTitle ? titleId : undefined}
      aria-label={!hasTitle ? ariaLabel : undefined}
      tabIndex={-1}
      onClick={onOverlayClick}
      onKeyDown={onKeyDown}
      {...rest}
    >
      <div className={panelClassName} style={panelStyle} onClick={(e) => e.stopPropagation()}>
        <div className="ccl-offcanvas__header">
          {hasTitle ? (
            typeof title === 'string' ? (
              <h2 id={titleId} className="ccl-offcanvas__title">
                {title}
              </h2>
            ) : (
              <div id={titleId} className="ccl-offcanvas__title">
                {title}
              </div>
            )
          ) : (
            <div className="ccl-offcanvas__title" />
          )}

          {!hideCloseButton ? (
            <button
              ref={closeRef}
              type="button"
              className="ccl-offcanvas__close"
              disabled={disableCloseButton}
              aria-label="Close"
              onClick={close}
            >
              <svg className="ccl-offcanvas__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>

        <div className="ccl-offcanvas__body">{children}</div>
      </div>
    </div>
  );
}

