import { h, type FunctionComponent } from 'preact'
import { useCallback, useEffect, useRef, useState } from 'preact/hooks'
import cn from 'classnames'
import mergeStyles from '../utils/merge_styles.ts'

type Styles<C extends string, D extends string, S extends string> = {
  base?: string
  touched?: string
  element?: string
  label?: string
  icon?: string
} & Record<C, string> &
  Record<D, string> &
  Record<S, string>

type Options<C extends string, D extends string, S extends string> = {
  styles: Styles<C, D, S>
}

type Props<C extends string, D extends string, S extends string> = {
  autoFocus?: boolean
  className?: string
  color?: C
  defaultValue?: string
  design?: D
  disabled?: boolean
  icon: ANY
  label: string
  name: string
  noEmpty?: boolean
  onChange?: ANY
  options: string[] | number[] | { label: string; value: string | number }[]
  placeholder?: string
  required?: boolean
  size: S
}

// designs / color / size
export default function selectFactory<C extends string = never, D extends string = never, S extends string = never>({
  styles,
}: Options<C, D, S>) {
  if (Array.isArray(styles)) {
    styles = mergeStyles(styles)
  }

  const Select: FunctionComponent<Props<C, D, S>> = ({
    autoFocus,
    className,
    color,
    defaultValue,
    design,
    disabled,
    icon,
    name,
    label,
    noEmpty,
    onChange,
    placeholder,
    required,
    size,
    options,
  }) => {
    const [touched, setTouched] = useState(false)
    const selectRef = useRef<HTMLSelectElement>(null)

    const onBlur = useCallback(() => setTouched(true), [])

    useEffect(() => {
      const form = selectRef.current!.form
      const resetTouched = setTouched.bind(null, false)

      form!.addEventListener('reset', resetTouched)

      return () => form!.removeEventListener('reset', resetTouched)
    }, [])

    useEffect(() => {
      if (autoFocus) selectRef.current!.focus()
    }, [autoFocus])

    return (
      <div
        className={cn(
          styles.base,
          design && styles[design],
          color && styles[color],
          size && styles[size],
          touched && styles.touched,
          className,
        )}
      >
        {icon && <div className={cn(styles.icon)} style={{ maskImage: icon }} />}

        <select
          autoFocus={autoFocus}
          className={styles.element}
          disabled={disabled}
          name={name}
          onBlur={onBlur}
          onChange={onChange}
          ref={selectRef}
          defaultValue={defaultValue}
          required={required}
        >
          {!noEmpty && <option value=''>{placeholder || label}</option>}

          {options &&
            options.map((option) => (
              <option
                key={typeof option === 'object' ? option.value : option}
                value={typeof option === 'object' ? option.value : option}
              >
                {typeof option === 'object' ? option.label : option}
              </option>
            ))}
        </select>

        {/**
         * <label> comes after <input> to be able to use next sibling CSS selector
         * (see public input.module.scss). Use `flexbox` and `order` if <label> needs to be
         * displayed before <input> (see admin input.module.scss)
         */}
        {label && (
          <label className={styles.label} htmlFor={name}>
            {label}
          </label>
        )}
      </div>
    )
  }

  return Select
}
