"use client";

import classNames from "classnames/dedupe";
import type React from "react";
import { type RefAttributes, useCallback, useEffect, useRef } from "react";
import {
  TextField,
  type TextFieldElementType,
  type TextFieldProps,
} from "../TextField";
import "./NumberField.css";

export type NumberFieldElementType = TextFieldElementType;

export interface NumberFieldProps
  extends
    Omit<TextFieldProps, "type" | "defaultValue" | "inputElementType">,
    RefAttributes<NumberFieldElementType> {
  /** The smallest value allowed for the input. */
  minValue?: number;
  /** The largest value allowed for the input. */
  maxValue?: number;
  /** The amount that the input value changes with each increment or decrement "tick". */
  step?: number; // ??
  /** The default value for the input. */
  defaultValue?: number;
  /** Flag to hide the spin buttons. */
  hideSpinButtons?: boolean;
}

export const NumberField = ({ ref, ...props }: NumberFieldProps) => {
  const {
    minValue,
    maxValue,
    step,
    defaultValue,
    hideSpinButtons,
    className,
    ...otherProps
  } = props;
  const {
    onFocus: customOnFocus,
    onBlur: customOnBlur,
    ...rest
  } = otherProps as Omit<TextFieldProps, "onFocus" | "onBlur"> & {
    onFocus?: React.FocusEventHandler<HTMLInputElement>;
    onBlur?: React.FocusEventHandler<HTMLInputElement>;
  };

  const focusedInputRef = useRef<HTMLInputElement | null>(null);
  const wheelHandler = useCallback((ev: Event) => ev.preventDefault(), []);

  useEffect(() => {
    return () => {
      focusedInputRef.current?.removeEventListener("wheel", wheelHandler);
      focusedInputRef.current = null;
    };
  }, [wheelHandler]);

  const containerClasses = classNames("mobius-number-field", className, {
    "--hide-spin-buttons": hideSpinButtons,
  });

  const handleBeforeInput = (
    event: React.FormEvent<NumberFieldElementType>,
  ) => {
    const { data } = event.nativeEvent as InputEvent;
    if (step && Number.isInteger(step) && data === ".") {
      event.preventDefault();
    }
    if (minValue && minValue >= 0 && data === "-") {
      event.preventDefault();
    }
  };

  const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
    const { value } = event.target;
    const numValue = parseFloat(value);

    // On blur, snap to nearest valid step value if step is defined
    if (step && step > 1 && !isNaN(numValue)) {
      const baseValue = minValue ?? 0;
      const steps = Math.round((numValue - baseValue) / step);
      const snappedValue = baseValue + steps * step;

      // Only update if the value changed
      if (snappedValue !== numValue) {
        event.target.value = snappedValue.toString();
        // Trigger change event for the corrected value
        const changeEvent = new Event("change", { bubbles: true });
        event.target.dispatchEvent(changeEvent);
        // Also call the onChange handler if provided
        const syntheticChangeEvent = {
          ...event,
          target: event.target,
          currentTarget: event.currentTarget,
        } as React.ChangeEvent<HTMLInputElement>;
        otherProps.onChange?.(syntheticChangeEvent);
      }
    }

    customOnBlur?.(event);
  };

  const forwardedProps: TextFieldProps = {
    ...rest,
    onFocus: e => {
      const el = e.currentTarget as HTMLInputElement;
      focusedInputRef.current?.removeEventListener("wheel", wheelHandler);
      focusedInputRef.current = el;
      el.addEventListener("wheel", wheelHandler, { passive: false });
      customOnFocus?.(e as React.FocusEvent<HTMLInputElement>);
    },
    onBlur: e => handleBlur(e as React.FocusEvent<HTMLInputElement>),
  };

  return (
    <TextField
      {...forwardedProps}
      className={containerClasses}
      onBeforeInput={handleBeforeInput}
      type="number"
      min={minValue ?? undefined}
      max={maxValue ?? undefined}
      step={step}
      ref={ref}
      defaultValue={
        defaultValue != null ? defaultValue?.toString() : defaultValue
      }
    />
  );
};

NumberField.displayName = "NumberField";
