import type { ChangeEvent, CSSProperties, FocusEvent, MouseEvent, ReactNode } from 'react';
import { Check, ChevronDown, Minus } from 'lucide-react';
import { Spinner } from './feedback';

type ControlSize = 'sm' | 'md';

function classNames(...values: Array<string | false | null | undefined>): string {
  return values.filter(Boolean).join(' ');
}

export interface ButtonProps {
  children?: ReactNode;
  variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
  size?: ControlSize;
  startIcon?: ReactNode;
  endIcon?: ReactNode;
  disabled?: boolean;
  loading?: boolean;
  fullWidth?: boolean;
  type?: 'button' | 'submit' | 'reset';
  onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
  style?: CSSProperties;
  title?: string;
}

export function Button({
  children,
  variant = 'primary',
  size = 'md',
  startIcon,
  endIcon,
  disabled = false,
  loading = false,
  fullWidth = false,
  type = 'button',
  onClick,
  style,
  title
}: ButtonProps) {
  return (
    <button
      type={type}
      disabled={disabled || loading}
      onClick={onClick}
      title={title}
      className={classNames(
        'ctv-button',
        `ctv-button-${variant}`,
        size === 'sm' && 'ctv-button-sm',
        fullWidth && 'ctv-button-full'
      )}
      style={style}
    >
      {loading ? <Spinner size={14} stroke={3} /> : startIcon}
      <span>{children}</span>
      {!loading && endIcon}
    </button>
  );
}

export interface IconButtonProps {
  children?: ReactNode;
  size?: ControlSize;
  variant?: 'ghost' | 'solid';
  active?: boolean;
  disabled?: boolean;
  title?: string;
  onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
  style?: CSSProperties;
}

export function IconButton({
  children,
  size = 'md',
  variant = 'ghost',
  active = false,
  disabled = false,
  title,
  onClick,
  style
}: IconButtonProps) {
  return (
    <button
      type="button"
      title={title}
      aria-label={title}
      disabled={disabled}
      onClick={onClick}
      className={classNames(
        'ctv-icon-button',
        size === 'sm' && 'ctv-icon-button-sm',
        variant === 'solid' && 'ctv-icon-button-solid',
        active && 'ctv-icon-button-active'
      )}
      style={style}
    >
      {children}
    </button>
  );
}

export interface InputProps {
  label?: string;
  value?: string;
  onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
  placeholder?: string;
  leadingIcon?: ReactNode;
  trailing?: ReactNode;
  error?: string | null;
  disabled?: boolean;
  size?: ControlSize;
  type?: string;
  fullWidth?: boolean;
  style?: CSSProperties;
  // Native <input> passthroughs — added for numeric fields (e.g. the multi-collection weight input,
  // #404): min/max bound the stepper, inputMode hints the mobile keyboard, onBlur normalizes on commit.
  min?: number;
  max?: number;
  inputMode?: 'numeric' | 'decimal' | 'text';
  onBlur?: (e: FocusEvent<HTMLInputElement>) => void;
  ariaLabel?: string;
}

export function Input({
  label,
  value,
  onChange,
  placeholder,
  leadingIcon,
  trailing,
  error = null,
  disabled = false,
  size = 'md',
  type = 'text',
  fullWidth = true,
  style,
  min,
  max,
  inputMode,
  onBlur,
  ariaLabel
}: InputProps) {
  return (
    <label className={classNames('ctv-field', fullWidth && 'ctv-field-full')}>
      {label && <span className="ctv-field-label">{label}</span>}
      <span
        className={classNames(
          'ctv-field-frame',
          size === 'sm' && 'ctv-field-frame-sm',
          error && 'ctv-field-frame-error',
          disabled && 'ctv-field-frame-disabled'
        )}
        style={style}
      >
        {leadingIcon && <span className="ctv-field-icon">{leadingIcon}</span>}
        <input
          className="ctv-input"
          type={type}
          value={value}
          onChange={onChange}
          onBlur={onBlur}
          placeholder={placeholder}
          disabled={disabled}
          min={min}
          max={max}
          inputMode={inputMode}
          aria-label={ariaLabel}
        />
        {trailing && <span className="ctv-field-trailing">{trailing}</span>}
      </span>
      {error && <span className="ctv-field-error">{error}</span>}
    </label>
  );
}

export interface SelectOption {
  value: string;
  label: string;
}

export interface SelectProps {
  label?: string;
  value?: string;
  onChange?: (e: ChangeEvent<HTMLSelectElement>) => void;
  options?: Array<string | SelectOption>;
  disabled?: boolean;
  size?: ControlSize;
  fullWidth?: boolean;
  style?: CSSProperties;
}

export function Select({
  label,
  value,
  onChange,
  options = [],
  disabled = false,
  size = 'md',
  fullWidth = true,
  style
}: SelectProps) {
  const normalizedOptions = options.map((option) =>
    typeof option === 'string' ? { value: option, label: option } : option
  );

  return (
    <label className={classNames('ctv-field', fullWidth && 'ctv-field-full')}>
      {label && <span className="ctv-field-label">{label}</span>}
      <span
        className={classNames(
          'ctv-field-frame',
          'ctv-select-frame',
          size === 'sm' && 'ctv-field-frame-sm',
          disabled && 'ctv-field-frame-disabled'
        )}
        style={style}
      >
        <select className="ctv-select" value={value} onChange={onChange} disabled={disabled}>
          {normalizedOptions.map((option) => (
            <option key={option.value} value={option.value}>
              {option.label}
            </option>
          ))}
        </select>
        <ChevronDown className="ctv-select-chevron" aria-hidden="true" size={14} />
      </span>
    </label>
  );
}

export interface SwitchProps {
  checked?: boolean;
  onChange?: (next: boolean) => void;
  label?: string;
  // When true, `label` still becomes the accessible name (aria-label) but is not rendered as
  // visible text — for callers that already show the same caption elsewhere (e.g. a settings
  // Row label) and would otherwise duplicate it.
  hideLabel?: boolean;
  disabled?: boolean;
  size?: ControlSize;
  style?: CSSProperties;
}

export function Switch({
  checked = false,
  onChange,
  label,
  hideLabel = false,
  disabled = false,
  size = 'md',
  style
}: SwitchProps) {
  return (
    <span
      className={classNames(
        'ctv-switch',
        size === 'sm' && 'ctv-switch-sm',
        disabled && 'ctv-switch-disabled'
      )}
      style={style}
    >
      <button
        type="button"
        role="switch"
        aria-label={label}
        aria-checked={checked}
        aria-disabled={disabled}
        className="ctv-switch-control"
        onClick={() => {
          if (!disabled) {
            onChange?.(!checked);
          }
        }}
      >
        <span className="ctv-switch-knob" />
      </button>
      {label && !hideLabel && <span>{label}</span>}
    </span>
  );
}

export interface CheckboxProps {
  checked?: boolean;
  onChange?: (next: boolean) => void;
  label?: string;
  indeterminate?: boolean;
  disabled?: boolean;
  style?: CSSProperties;
}

export function Checkbox({
  checked = false,
  onChange,
  label,
  indeterminate = false,
  disabled = false,
  style
}: CheckboxProps) {
  return (
    <span className={classNames('ctv-checkbox', disabled && 'ctv-checkbox-disabled')} style={style}>
      <button
        type="button"
        role="checkbox"
        aria-label={label}
        aria-checked={indeterminate ? 'mixed' : checked}
        aria-disabled={disabled}
        className="ctv-checkbox-control"
        onClick={() => {
          if (!disabled) {
            onChange?.(!checked);
          }
        }}
      >
        {indeterminate ? (
          <Minus aria-hidden="true" size={11} strokeWidth={3} />
        ) : checked ? (
          <Check aria-hidden="true" size={12} strokeWidth={3} />
        ) : null}
      </button>
      {label && <span>{label}</span>}
    </span>
  );
}
