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

export type SearchbarProps = {
  label?: string;
  error?: boolean;
  errorMessage?: string;
  helperText?: string;
  debounce?: number;
  clearable?: boolean;
  showIcon?: boolean;
  searchIcon?: ReactNode;
  clearIcon?: ReactNode;
  onSearch?: (value: string) => void;
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type'> & {
    onChange?: InputHTMLAttributes<HTMLInputElement>['onChange'];
  };

export function Searchbar({
  label = '',
  placeholder = 'Search...',
  value: controlledValue,
  defaultValue = '',
  error = false,
  errorMessage = '',
  helperText = '',
  required,
  'aria-label': ariaLabelProp,
  'aria-describedby': ariaDescribedByProp,
  debounce = 300,
  clearable = true,
  disabled = false,
  showIcon = true,
  searchIcon,
  clearIcon,
  onSearch,
  onChange,
  className,
  ...rest
}: SearchbarProps) {
  const uid = useId();
  const inputId = rest.id ?? `ccl-searchbar-${uid.replace(/:/g, '')}`;
  const labelId = `${inputId}-label`;
  const hintId = `${inputId}-hint`;
  const errorId = `${inputId}-error`;

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

  const inputRef = useRef<HTMLInputElement>(null);
  const timeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
  const [innerValue, setInnerValue] = useState(defaultValue);
  const isControlled = controlledValue !== undefined;
  const value = isControlled ? String(controlledValue) : innerValue;

  useEffect(() => () => clearTimeout(timeoutRef.current), []);

  const emit = useCallback(
    (v: string) => {
      onSearch?.(v);
    },
    [onSearch]
  );

  const handleInput = useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const v = e.target.value;
      if (!isControlled) setInnerValue(v);
      onChange?.(e);
      clearTimeout(timeoutRef.current);
      if (debounce > 0) {
        timeoutRef.current = setTimeout(() => emit(v), debounce);
      } else {
        emit(v);
      }
    },
    [debounce, emit, isControlled, onChange]
  );

  const handleEnter = useCallback(() => {
    clearTimeout(timeoutRef.current);
    emit(String(value));
  }, [emit, value]);

  const clear = useCallback(() => {
    if (!isControlled) setInnerValue('');
    clearTimeout(timeoutRef.current);
    emit('');
    inputRef.current?.focus();
  }, [emit, isControlled]);

  return (
    <div className="ccl-searchbar-wrapper">
      {label ? (
        <label htmlFor={inputId} id={labelId} className="ccl-searchbar__label">
          {label}
          {required ? (
            <span className="ccl-searchbar__required" aria-label="required">
              *
            </span>
          ) : null}
        </label>
      ) : null}

      <div className={['ccl-searchbar', className].filter(Boolean).join(' ')}>
        {showIcon ? (
          searchIcon ?? <span className="ccl-searchbar__icon" aria-hidden>🔍</span>
        ) : null}

        <input
          ref={inputRef}
          id={inputId}
          type="text"
          className="ccl-searchbar__input"
          role="searchbox"
          aria-label={ariaLabelProp ?? (label ? undefined : placeholder)}
          aria-describedby={describedBy}
          aria-invalid={error}
          aria-required={required}
          required={required}
          placeholder={placeholder}
          disabled={disabled}
          value={value}
          onChange={handleInput}
          onKeyUp={(e) => {
            if (e.key === 'Enter') handleEnter();
            if (e.key === 'Escape') clear();
          }}
          {...rest}
        />

        {clearable && value ? (
          <button
            type="button"
            className="ccl-searchbar__clear"
            aria-label="Clear search"
            onClick={clear}
            disabled={disabled}
          >
            {clearIcon ?? '✕'}
          </button>
        ) : null}
      </div>

      {helperText ? (
        <span id={hintId} className="ccl-searchbar__helper">
          {helperText}
        </span>
      ) : null}

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