import React, { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';

import styles from './Select.module.css';

export type SelectOption = {
  value: string;
  label: string;
  disabled?: boolean;
};

export type SelectProps = {
  value: string;
  options: readonly SelectOption[];
  onChange: (value: string) => void;
  disabled?: boolean;
  ariaLabel?: string;
  /** Доп. класс на кнопку-триггер (ширина и т.п.). */
  className?: string;
  placeholder?: string;
};

type MenuPos = { left: number; top: number; width: number; maxHeight: number };

function Chevron({ open }: { open: boolean }) {
  return (
    <svg
      className={[styles.chevron, open ? styles.chevronOpen : ''].filter(Boolean).join(' ')}
      viewBox="0 0 12 12"
      aria-hidden
    >
      <path
        d="M2.5 4.25L6 7.75L9.5 4.25"
        stroke="currentColor"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
        fill="none"
      />
    </svg>
  );
}

export function Select({
  value,
  options,
  onChange,
  disabled = false,
  ariaLabel,
  className,
  placeholder = '—',
}: SelectProps) {
  const listId = useId();
  const triggerRef = useRef<HTMLButtonElement | null>(null);
  const menuRef = useRef<HTMLDivElement | null>(null);
  const [open, setOpen] = useState(false);
  const [menuPos, setMenuPos] = useState<MenuPos | null>(null);
  const [activeIndex, setActiveIndex] = useState(0);

  const selected = useMemo(() => options.find((o) => o.value === value) ?? null, [options, value]);
  const enabledIndexes = useMemo(
    () => options.map((o, i) => (o.disabled ? -1 : i)).filter((i) => i >= 0),
    [options],
  );

  const close = useCallback(() => {
    setOpen(false);
    setMenuPos(null);
  }, []);

  const layoutMenu = useCallback(() => {
    const trigger = triggerRef.current;
    if (!trigger) return;
    const r = trigger.getBoundingClientRect();
    const pad = 8;
    const gap = 4;
    const preferredMax = Math.min(280, window.innerHeight * 0.5);
    const spaceBelow = window.innerHeight - r.bottom - pad;
    const spaceAbove = r.top - pad;
    const placeBelow = spaceBelow >= 120 || spaceBelow >= spaceAbove;
    const maxHeight = Math.max(96, Math.min(preferredMax, placeBelow ? spaceBelow - gap : spaceAbove - gap));
    const top = placeBelow ? r.bottom + gap : Math.max(pad, r.top - gap - maxHeight);
    setMenuPos({
      left: Math.max(pad, Math.min(r.left, window.innerWidth - r.width - pad)),
      top,
      width: r.width,
      maxHeight,
    });
  }, []);

  const openMenu = useCallback(() => {
    if (disabled) return;
    const selectedIdx = options.findIndex((o) => o.value === value && !o.disabled);
    const fallback = enabledIndexes[0] ?? 0;
    setActiveIndex(selectedIdx >= 0 ? selectedIdx : fallback);
    setOpen(true);
  }, [disabled, enabledIndexes, options, value]);

  useLayoutEffect(() => {
    if (!open) return;
    layoutMenu();
  }, [layoutMenu, open, options.length]);

  useEffect(() => {
    if (!open) return;
    const onWin = () => layoutMenu();
    window.addEventListener('resize', onWin);
    window.addEventListener('scroll', onWin, true);
    return () => {
      window.removeEventListener('resize', onWin);
      window.removeEventListener('scroll', onWin, true);
    };
  }, [layoutMenu, open]);

  useEffect(() => {
    if (!open) return;
    const onPointerDown = (e: PointerEvent) => {
      const t = e.target;
      if (!(t instanceof Node)) return;
      if (triggerRef.current?.contains(t)) return;
      if (menuRef.current?.contains(t)) return;
      close();
    };
    window.addEventListener('pointerdown', onPointerDown, true);
    return () => window.removeEventListener('pointerdown', onPointerDown, true);
  }, [close, open]);

  useEffect(() => {
    if (!open) return;
    const el = menuRef.current?.querySelector<HTMLElement>(`[data-select-index="${String(activeIndex)}"]`);
    el?.scrollIntoView({ block: 'nearest' });
  }, [activeIndex, open]);

  const moveActive = (dir: 1 | -1) => {
    if (enabledIndexes.length === 0) return;
    const pos = enabledIndexes.indexOf(activeIndex);
    const nextPos =
      pos < 0
        ? dir === 1
          ? 0
          : enabledIndexes.length - 1
        : (pos + dir + enabledIndexes.length) % enabledIndexes.length;
    setActiveIndex(enabledIndexes[nextPos]!);
  };

  const commitIndex = (index: number) => {
    const opt = options[index];
    if (!opt || opt.disabled) return;
    onChange(opt.value);
    close();
    triggerRef.current?.focus();
  };

  const onTriggerKeyDown = (e: React.KeyboardEvent) => {
    if (disabled) return;
    if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      if (!open) {
        openMenu();
        return;
      }
      if (e.key === 'ArrowDown') moveActive(1);
      else if (e.key === 'ArrowUp') moveActive(-1);
      else if (e.key === 'Enter' || e.key === ' ') commitIndex(activeIndex);
    } else if (e.key === 'Escape' && open) {
      e.preventDefault();
      close();
    }
  };

  const onMenuKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      moveActive(1);
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      moveActive(-1);
    } else if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      commitIndex(activeIndex);
    } else if (e.key === 'Escape') {
      e.preventDefault();
      close();
      triggerRef.current?.focus();
    } else if (e.key === 'Tab') {
      close();
    }
  };

  const triggerClass = [
    styles.trigger,
    open ? styles.triggerOpen : '',
    className ?? '',
  ]
    .filter(Boolean)
    .join(' ');

  const menu =
    open && menuPos && typeof document !== 'undefined'
      ? createPortal(
          <div
            ref={menuRef}
            id={listId}
            role="listbox"
            className={styles.menu}
            style={{
              left: menuPos.left,
              top: menuPos.top,
              width: menuPos.width,
              maxHeight: menuPos.maxHeight,
            }}
            onKeyDown={onMenuKeyDown}
          >
            {options.map((opt, index) => {
              const selectedOpt = opt.value === value;
              const active = index === activeIndex;
              return (
                <button
                  key={`${opt.value}::${String(index)}`}
                  type="button"
                  role="option"
                  data-select-index={index}
                  aria-selected={selectedOpt}
                  disabled={opt.disabled}
                  className={[
                    styles.option,
                    selectedOpt ? styles.optionSelected : '',
                    active ? styles.optionActive : '',
                  ]
                    .filter(Boolean)
                    .join(' ')}
                  onMouseEnter={() => {
                    if (!opt.disabled) setActiveIndex(index);
                  }}
                  onClick={() => commitIndex(index)}
                >
                  {opt.label}
                </button>
              );
            })}
          </div>,
          document.body,
        )
      : null;

  return (
    <div className={styles.root}>
      <button
        ref={triggerRef}
        type="button"
        className={triggerClass}
        disabled={disabled}
        aria-label={ariaLabel}
        aria-haspopup="listbox"
        aria-expanded={open}
        aria-controls={open ? listId : undefined}
        onClick={() => {
          if (open) close();
          else openMenu();
        }}
        onKeyDown={onTriggerKeyDown}
      >
        <span className={[styles.value, selected ? '' : styles.placeholder].filter(Boolean).join(' ')}>
          {selected?.label ?? placeholder}
        </span>
        <Chevron open={open} />
      </button>
      {menu}
    </div>
  );
}
