import type { HTMLAttributes } from 'react';
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { SelectOption } from './types';
import './select-advanced.css';

export type BaseSelectProps<TValue = string> = {
  label?: string;
  required?: boolean;
  placeholder?: string;
  disabled?: boolean;
  /** When true, behaves like disabled (no interactions). */
  loading?: boolean;
  error?: boolean;
  errorMessage?: string;
  helperText?: string;
  isMulti?: boolean;
  options: SelectOption<TValue>[];
  value?: TValue | null;
  values?: TValue[];
  defaultValue?: TValue | null;
  defaultValues?: TValue[];
  onChange?: (value: TValue | null) => void;
  onChangeMulti?: (values: TValue[]) => void;
  isLoading?: boolean;
  clearable?: boolean;
  closeMenuOnSelect?: boolean;
  /** When true, selected options are hidden from the menu (common multi-select behavior). */
  hideSelectedOptions?: boolean;
  /** Called whenever the search query changes. */
  onQueryChange?: (query: string) => void;
} & Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'>;

function isSameValue(a: unknown, b: unknown) {
  return a === b;
}

export function BaseSelect<TValue = string>({
  label,
  required,
  placeholder = 'Select…',
  disabled = false,
  loading = false,
  error = false,
  errorMessage = '',
  helperText = '',
  isMulti = false,
  options,
  value,
  values,
  defaultValue,
  defaultValues,
  onChange,
  onChangeMulti,
  isLoading = false,
  clearable = true,
  closeMenuOnSelect = true,
  hideSelectedOptions = isMulti,
  onQueryChange,
  className,
  ...rest
}: BaseSelectProps<TValue>) {
  const uid = useId().replace(/:/g, '');
  const hintId = `ccl-adv-select-${uid}-hint`;
  const errorId = `ccl-adv-select-${uid}-error`;
  const inputId = `ccl-adv-select-${uid}-input`;

  const rootRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);
  const menuRef = useRef<HTMLDivElement>(null);

  const isDisabled = disabled || loading;
  const showLoading = isLoading || loading;

  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState('');
  const [highlighted, setHighlighted] = useState(0);

  const [uncontrolledValue, setUncontrolledValue] = useState<TValue | null>(defaultValue ?? null);
  const [uncontrolledValues, setUncontrolledValues] = useState<TValue[]>(defaultValues ?? []);

  const selectedSingle = value !== undefined ? value : uncontrolledValue;
  const selectedMulti = values !== undefined ? values : uncontrolledValues;

  const selectedOptions = useMemo(() => {
    if (isMulti) {
      return options.filter((o) => selectedMulti.some((v) => isSameValue(v, o.value)));
    }
    return options.find((o) => (selectedSingle == null ? false : isSameValue(o.value, selectedSingle))) ?? null;
  }, [isMulti, options, selectedMulti, selectedSingle]);

  const filteredOptions = useMemo(() => {
    const q = query.trim().toLowerCase();
    let list = options;
    if (hideSelectedOptions) {
      list = isMulti
        ? list.filter((o) => !selectedMulti.some((v) => isSameValue(v, o.value)))
        : selectedSingle == null
          ? list
          : list.filter((o) => !isSameValue(o.value, selectedSingle));
    }
    if (!q) return list;
    return list.filter((o) => o.label.toLowerCase().includes(q));
  }, [hideSelectedOptions, isMulti, options, query, selectedMulti, selectedSingle]);

  const describedBy = useMemo(() => {
    const ids: string[] = [];
    if (helperText) ids.push(hintId);
    if (error && errorMessage) ids.push(errorId);
    return ids.length ? ids.join(' ') : undefined;
  }, [error, errorId, errorMessage, helperText, hintId]);

  const close = useCallback(() => {
    setOpen(false);
    setQuery('');
    setHighlighted(0);
  }, []);

  const openMenu = useCallback(() => {
    if (isDisabled) return;
    setOpen(true);
    window.setTimeout(() => inputRef.current?.focus(), 0);
  }, [isDisabled]);

  useLayoutEffect(() => {
    const onDoc = (e: MouseEvent) => {
      const t = e.target as Node;
      if (rootRef.current?.contains(t)) return;
      close();
    };
    document.addEventListener('mousedown', onDoc, true);
    return () => document.removeEventListener('mousedown', onDoc, true);
  }, [close]);

  // Close when focus leaves the component (keyboard tab, programmatic focus, etc.)
  useEffect(() => {
    const onFocusIn = (e: FocusEvent) => {
      const t = e.target as Node | null;
      if (!t) return;
      if (rootRef.current?.contains(t)) return;
      close();
    };
    document.addEventListener('focusin', onFocusIn, true);
    return () => document.removeEventListener('focusin', onFocusIn, true);
  }, [close]);

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

  const setSingle = useCallback(
    (v: TValue | null) => {
      if (value === undefined) setUncontrolledValue(v);
      onChange?.(v);
    },
    [onChange, value]
  );

  const setMulti = useCallback(
    (vals: TValue[]) => {
      if (values === undefined) setUncontrolledValues(vals);
      onChangeMulti?.(vals);
    },
    [onChangeMulti, values]
  );

  const toggleMultiValue = useCallback(
    (v: TValue) => {
      const has = selectedMulti.some((x) => isSameValue(x, v));
      const next = has ? selectedMulti.filter((x) => !isSameValue(x, v)) : [...selectedMulti, v];
      setMulti(next);
    },
    [selectedMulti, setMulti]
  );

  const onSelectOption = useCallback(
    (opt: SelectOption<TValue>) => {
      if (opt.disabled) return;
      if (isMulti) {
        toggleMultiValue(opt.value);
        if (closeMenuOnSelect) {
          // keep open by default for multi; allow override
          if (closeMenuOnSelect) close();
        }
      } else {
        setSingle(opt.value);
        if (closeMenuOnSelect) close();
      }
    },
    [close, closeMenuOnSelect, isMulti, setSingle, toggleMultiValue]
  );

  const clear = useCallback(() => {
    if (isDisabled) return;
    if (isMulti) setMulti([]);
    else setSingle(null);
    close();
  }, [close, isDisabled, isMulti, setMulti, setSingle]);

  const canClear = useMemo(() => {
    if (!clearable || isDisabled) return false;
    if (isMulti) return selectedMulti.length > 0;
    return selectedSingle != null;
  }, [clearable, isDisabled, isMulti, selectedMulti.length, selectedSingle]);

  const onKeyDown = useCallback(
    (e: React.KeyboardEvent<HTMLInputElement>) => {
      if (isDisabled) return;
      if (!open && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {
        e.preventDefault();
        openMenu();
        return;
      }
      if (!open) return;
      if (e.key === 'Escape') {
        e.preventDefault();
        close();
        return;
      }
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        setHighlighted((i) => Math.min(filteredOptions.length - 1, i + 1));
        return;
      }
      if (e.key === 'ArrowUp') {
        e.preventDefault();
        setHighlighted((i) => Math.max(0, i - 1));
        return;
      }
      if (e.key === 'Enter') {
        e.preventDefault();
        const opt = filteredOptions[highlighted];
        if (opt) onSelectOption(opt);
        return;
      }
      if (isMulti && e.key === 'Backspace' && query.length === 0 && selectedMulti.length > 0) {
        // remove last chip
        const next = selectedMulti.slice(0, -1);
        setMulti(next);
      }
    },
    [close, filteredOptions, highlighted, isDisabled, isMulti, onSelectOption, open, openMenu, query.length, selectedMulti, setMulti]
  );

  const controlClasses = [
    'ccl-adv-select__control',
    isDisabled ? 'ccl-adv-select__control--disabled' : '',
    error ? 'ccl-adv-select__control--invalid' : '',
  ]
    .filter(Boolean)
    .join(' ');

  return (
    <div ref={rootRef} className={['ccl-adv-select', className ?? ''].filter(Boolean).join(' ')} {...rest}>
      {label ? (
        <label htmlFor={inputId} className="ccl-adv-select__label">
          {label}
          {required ? (
            <span className="ccl-adv-select__required" aria-label="required">
              *
            </span>
          ) : null}
        </label>
      ) : null}

      <div
        className={controlClasses}
        onMouseDown={(e) => {
          if (isDisabled) return;
          // keep focus in input
          e.preventDefault();
          openMenu();
        }}
      >
        <div className={['ccl-adv-select__value', isMulti ? 'ccl-adv-select__value--multi' : ''].filter(Boolean).join(' ')}>
          {isMulti ? (
            (selectedOptions as SelectOption<TValue>[]).map((opt) => (
              <span key={String(opt.value)} className="ccl-adv-select__chip">
                <span className="ccl-adv-select__chip-label">{opt.label}</span>
                <button
                  type="button"
                  className="ccl-adv-select__chip-remove"
                  aria-label={`Remove ${opt.label}`}
                  onClick={(e) => {
                    e.stopPropagation();
                    toggleMultiValue(opt.value);
                  }}
                >
                  ×
                </button>
              </span>
            ))
          ) : selectedOptions ? (
            <span className="ccl-adv-select__single-value">{(selectedOptions as SelectOption<TValue>).label}</span>
          ) : null}

          <input
            id={inputId}
            ref={inputRef}
            className="ccl-adv-select__input"
            value={query}
            onChange={(e) => {
              const next = e.target.value;
              setQuery(next);
              onQueryChange?.(next);
              setHighlighted(0);
              if (!open) setOpen(true);
            }}
            onBlur={() => {
              // Allow focus to move into menu/buttons inside the root first.
              window.setTimeout(() => {
                const active = document.activeElement as Node | null;
                if (active && rootRef.current?.contains(active)) return;
                close();
              }, 0);
            }}
            onKeyDown={onKeyDown}
            aria-describedby={describedBy}
            aria-invalid={error || undefined}
            disabled={isDisabled}
            placeholder={!isMulti && !selectedOptions ? placeholder : selectedOptions ? '' : placeholder}
          />
          {!isMulti && selectedOptions == null && query.length === 0 ? (
            <span className="ccl-adv-select__placeholder">{/* placeholder handled by input */}</span>
          ) : null}
        </div>

        <div className="ccl-adv-select__indicators" aria-hidden>
          {showLoading ? <span>…</span> : null}
          {canClear ? (
            <button
              type="button"
              className="ccl-adv-select__clear"
              onClick={(e) => {
                e.stopPropagation();
                clear();
              }}
              aria-label="Clear"
            >
              ×
            </button>
          ) : null}
          <span className="ccl-adv-select__chevron">{open ? '▲' : '▼'}</span>
        </div>

        {open ? (
          <div ref={menuRef} className="ccl-adv-select__menu" role="listbox">
            <div className="ccl-adv-select__menu-list">
              {filteredOptions.length === 0 ? (
                <div className="ccl-adv-select__no-options">{isLoading ? 'Loading…' : 'No options'}</div>
              ) : (
                filteredOptions.map((opt, idx) => {
                  const selected = isMulti
                    ? selectedMulti.some((v) => isSameValue(v, opt.value))
                    : selectedSingle != null && isSameValue(selectedSingle, opt.value);
                  return (
                    <button
                      key={String(opt.value)}
                      type="button"
                      className={[
                        'ccl-adv-select__option',
                        idx === highlighted ? 'ccl-adv-select__option--highlighted' : '',
                        selected ? 'ccl-adv-select__option--selected' : '',
                        opt.disabled ? 'ccl-adv-select__option--disabled' : '',
                      ]
                        .filter(Boolean)
                        .join(' ')}
                      data-idx={idx}
                      disabled={opt.disabled}
                      onMouseEnter={() => setHighlighted(idx)}
                      onClick={(e) => {
                        e.preventDefault();
                        onSelectOption(opt);
                      }}
                    >
                      <span>{opt.label}</span>
                      {selected ? <span aria-hidden>✓</span> : null}
                    </button>
                  );
                })
              )}
            </div>
          </div>
        ) : null}
      </div>

      {helperText ? <span className="ccl-adv-select__helper">{helperText}</span> : null}
      {error && errorMessage ? (
        <span id={errorId} className="ccl-adv-select__error" role="alert">
          {errorMessage}
        </span>
      ) : null}
    </div>
  );
}

