import { MinusIcon, PlusIcon } from "@heroicons/react/20/solid";
import { type ChangeEvent, useRef } from "react";
import { cn } from "~/utils/cn";

type InputNumberStepperProps = Omit<JSX.IntrinsicElements["input"], "min" | "max" | "step"> & {
  step?: number;
  min?: number;
  max?: number;
  round?: boolean;
  controlSize?: "base" | "large";
};

export function InputNumberStepper({
  value,
  onChange,
  step = 50,
  min,
  max,
  round = true,
  controlSize = "base",
  name,
  id,
  disabled = false,
  readOnly = false,
  className,
  placeholder = "Type a number",
  ...props
}: InputNumberStepperProps) {
  const inputRef = useRef<HTMLInputElement>(null);

  const handleStepUp = () => {
    if (!inputRef.current || disabled) return;

    // If rounding is enabled, ensure we start from a rounded base before stepping
    if (round) {
      // If field is empty, treat as 0 (or min if provided) before stepping up
      if (inputRef.current.value === "") {
        inputRef.current.value = String(min ?? 0);
      } else {
        commitRoundedFromInput();
      }
    }
    inputRef.current.stepUp();
    const event = new Event("change", { bubbles: true });
    inputRef.current.dispatchEvent(event);
  };

  const handleStepDown = () => {
    if (!inputRef.current || disabled) return;

    // If rounding is enabled, ensure we start from a rounded base before stepping
    if (round) {
      // If field is empty, treat as 0 (or min if provided) before stepping down
      if (inputRef.current.value === "") {
        inputRef.current.value = String(min ?? 0);
      } else {
        commitRoundedFromInput();
      }
    }
    inputRef.current.stepDown();
    const event = new Event("change", { bubbles: true });
    inputRef.current.dispatchEvent(event);
  };

  const numericValue = value === "" ? NaN : (value as number);
  const isMinDisabled = min !== undefined && !Number.isNaN(numericValue) && numericValue <= min;
  const isMaxDisabled = max !== undefined && !Number.isNaN(numericValue) && numericValue >= max;

  function clamp(val: number): number {
    if (Number.isNaN(val)) return typeof value === "number" ? value : (min ?? 0);
    let next = val;
    if (min !== undefined) next = Math.max(min, next);
    if (max !== undefined) next = Math.min(max, next);
    return next;
  }

  function roundToStep(val: number): number {
    if (step <= 0) return val;
    const base = min ?? 0;
    const shifted = val - base;
    const quotient = shifted / step;
    const floored = Math.floor(quotient);
    const ceiled = Math.ceil(quotient);
    const down = base + floored * step;
    const up = base + ceiled * step;
    const distDown = Math.abs(val - down);
    const distUp = Math.abs(up - val);
    return distUp < distDown ? up : down;
  }

  function commitRoundedFromInput() {
    if (!inputRef.current || disabled || readOnly) return;
    const el = inputRef.current;
    const raw = el.value;
    if (raw === "") return; // do not coerce empty to 0; keep placeholder visible
    const numeric = Number(raw);
    if (Number.isNaN(numeric)) return; // ignore non-numeric
    const rounded = clamp(roundToStep(numeric));
    if (String(rounded) === String(value)) return;
    // Update the real input's value for immediate UI feedback
    el.value = String(rounded);
    // Invoke consumer onChange with the real element as target/currentTarget
    onChange?.({
      target: el,
      currentTarget: el,
    } as unknown as ChangeEvent<HTMLInputElement>);
  }

  const sizeStyles = {
    base: {
      container: "h-9",
      input: "text-sm px-3",
      button: "size-6",
      icon: "size-3.5",
      gap: "gap-1 pr-1.5",
    },
    large: {
      container: "h-11 rounded-md",
      input: "text-base px-3.5",
      button: "size-8",
      icon: "size-5",
      gap: "gap-1.25 pr-1.25",
    },
  } as const;

  const size = sizeStyles[controlSize];

  return (
    <div
      className={cn(
        "flex items-center rounded border border-border-bright bg-tertiary transition hover:border-border-brighter/80 hover:bg-surface-control/80",
        size.container,
        "has-focus-visible:outline-solid has-focus-visible:outline-1 has-focus-visible:outline-offset-0 has-focus-visible:outline-text-link",
        disabled && "cursor-not-allowed opacity-50",
        className
      )}
    >
      <input
        ref={inputRef}
        type="number"
        id={id}
        name={name}
        value={value}
        placeholder={placeholder}
        onChange={(e) => {
          // Allow empty string to pass through so user can clear the field
          if (e.currentTarget.value === "") {
            // reflect emptiness in the input and notify consumer as empty
            if (inputRef.current) inputRef.current.value = "";
            onChange?.({
              target: e.currentTarget,
              currentTarget: e.currentTarget,
            } as ChangeEvent<HTMLInputElement>);
            return;
          }
          onChange?.(e);
        }}
        onBlur={(e) => {
          // If blur is caused by clicking our step buttons, we prevent pointerdown
          // so blur shouldn't fire. This is for safety in case of keyboard focus move.
          if (round) commitRoundedFromInput();
        }}
        onKeyDown={(e) => {
          if (e.key === "Enter" && round) {
            e.preventDefault();
            commitRoundedFromInput();
          }
        }}
        step={step}
        min={min}
        max={max}
        disabled={disabled}
        readOnly={readOnly}
        className={cn(
          "placeholder:text-muted-foreground h-full grow border-0 bg-transparent text-left text-text-bright outline-hidden ring-0 focus:border-0 focus:outline-hidden focus:ring-0 disabled:cursor-not-allowed",
          size.input,
          // Hide number input arrows
          "[&[type=number]]:border-0 [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
        )}
        {...props}
      />

      <div className={cn("flex items-center", size.gap)}>
        <button
          type="button"
          onClick={handleStepDown}
          onPointerDown={(e) => e.preventDefault()}
          disabled={disabled || isMinDisabled}
          aria-label={`Decrease by ${step}`}
          className={cn(
            "flex items-center justify-center rounded border border-error/30 bg-error/20 transition",
            size.button,
            "hover:border-error/50 hover:bg-error/30",
            "disabled:cursor-not-allowed disabled:opacity-40",
            "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-text-link"
          )}
        >
          <MinusIcon className={cn("text-error", size.icon)} />
        </button>

        <button
          type="button"
          onClick={handleStepUp}
          onPointerDown={(e) => e.preventDefault()}
          disabled={disabled || isMaxDisabled}
          aria-label={`Increase by ${step}`}
          className={cn(
            "flex items-center justify-center rounded border border-success/30 bg-success/10 transition",
            size.button,
            "hover:border-success/40 hover:bg-success/20",
            "disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent",
            "focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-text-link"
          )}
        >
          <PlusIcon className={cn("text-success", size.icon)} />
        </button>
      </div>
    </div>
  );
}
