'use client';

import type { IntlTranslations } from '@zag-js/number-input';
import * as numberInput from '@zag-js/number-input';
import { normalizeProps, useMachine } from '@zag-js/react';
import { cva, VariantProps } from 'class-variance-authority';
import * as React from 'react';
import {
  BasicField,
  BasicFieldOptions,
  extractBasicFieldProps,
} from '../basic-field';
import { IconButton } from '../button';
import { cn, ComponentAnatomy, defineStyleAnatomy } from '../core/styling';
import {
  extractInputPartProps,
  InputAddon,
  InputAnatomy,
  InputContainer,
  InputIcon,
  InputStyling,
} from '../input';

/* -------------------------------------------------------------------------------------------------
 * Anatomy
 * -----------------------------------------------------------------------------------------------*/

export const NumberInputAnatomy = defineStyleAnatomy({
  root: cva(['UI-NumberInput__root', 'z-[2]'], {
    variants: {
      hideControls: {
        true: false,
        false: 'border-r border-r-transparent hover:border-r-[--border]',
      },
      size: {
        sm: null,
        md: null,
        lg: null,
      },
      intent: {
        basic: null,
        filled: null,
        unstyled: 'border-r-0 hover:border-r-transparent',
      },
    },
    defaultVariants: {
      hideControls: false,
    },
  }),
  control: cva(['UI-NumberInput__control', 'rounded-none h-[50%] ring-inset']),
  controlsContainer: cva(
    [
      'UI-NumberInput__controlsContainer',
      'form-input w-auto p-0 flex flex-col items-stretch justify-center overflow-hidden max-h-full',
      'border-l-0 relative z-[1]',
      'shadow-xs',
    ],
    {
      variants: {
        size: {
          sm: 'h-8',
          md: 'h-10',
          lg: 'h-12',
        },
        intent: {
          basic: null,
          filled: 'hover:bg-gray-100',
          unstyled: null,
        },
        hasRightAddon: {
          true: 'border-r-0',
          false: null,
        },
      },
    }
  ),
  chevronIcon: cva(['UI-Combobox__chevronIcon', 'h-4 w-4 shrink-0']),
});

/* -------------------------------------------------------------------------------------------------
 * NumberInput
 * -----------------------------------------------------------------------------------------------*/

export type NumberInputProps = Omit<
  React.ComponentPropsWithoutRef<'input'>,
  'value' | 'size' | 'defaultValue'
> &
  ComponentAnatomy<typeof NumberInputAnatomy> &
  Omit<VariantProps<typeof NumberInputAnatomy.root>, 'size' | 'intent'> &
  BasicFieldOptions &
  InputStyling & {
    /**
     * The value of the input
     */
    value?: number | string;
    /**
     * The callback to handle value changes
     */
    onValueChange?: (value: number, valueAsString: string) => void;
    /**
     * Default value when uncontrolled
     */
    defaultValue?: number | string;
    /**
     * The minimum value of the input
     */
    min?: number;
    /**
     * The maximum value of the input
     */
    max?: number;
    /**
     * The amount to increment or decrement the value by
     */
    step?: number;
    /**
     * Whether to allow mouse wheel to change the value
     */
    allowMouseWheel?: boolean;
    /**
     * Whether to allow the value overflow the min/max range
     */
    allowOverflow?: boolean;
    /**
     * Whether to hide the controls
     */
    hideControls?: boolean;
    /**
     * The format options for the value
     */
    formatOptions?: Intl.NumberFormatOptions;
    /**
     * Whether to clamp the value when the input loses focus (blur)
     */
    clampValueOnBlur?: boolean;
    /**
     * Accessibility
     *
     * Specifies the localized strings that identifies the accessibility elements and their states
     */
    translations?: IntlTranslations;
    /**
     * The current locale. Based on the BCP 47 definition.
     */
    locale?: string;
    /**
     * The document's text/writing direction.
     */
    dir?: 'ltr' | 'rtl';
  };

export const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
  (props, ref) => {
    const [props1, basicFieldProps] = extractBasicFieldProps<NumberInputProps>(
      props,
      React.useId()
    );

    const [
      {
        controlClass,
        controlsContainerClass,
        chevronIconClass,
        className,
        children,
        /**/
        size,
        intent,
        leftAddon,
        leftIcon,
        rightAddon,
        rightIcon,
        placeholder,
        onValueChange,
        hideControls,
        value: controlledValue,
        min = 0,
        max,
        step,
        allowMouseWheel = true,
        formatOptions = { maximumFractionDigits: 2 },
        clampValueOnBlur = true,
        translations,
        locale,
        dir,
        defaultValue,
        ...rest
      },
      {
        inputContainerProps,
        leftAddonProps,
        leftIconProps,
        rightAddonProps,
        rightIconProps,
      },
    ] = extractInputPartProps<NumberInputProps>({
      ...props1,
      size: props1.size ?? 'md',
      intent: props1.intent ?? 'basic',
      leftAddon: props1.leftAddon,
      leftIcon: props1.leftIcon,
      rightAddon: props1.rightAddon,
      rightIcon: props1.rightIcon,
    });
    const service = useMachine(numberInput.machine, {
      id: basicFieldProps.id,
      name: basicFieldProps.name,
      disabled: basicFieldProps.disabled,
      readOnly: basicFieldProps.readonly,
      value: controlledValue
        ? String(controlledValue)
        : defaultValue
          ? String(defaultValue)
          : undefined,
      min,
      max,
      step,
      allowMouseWheel,
      formatOptions,
      clampValueOnBlur,
      translations,
      locale,
      dir,
      onValueChange: (details: { valueAsNumber: number; value: string }) => {
        onValueChange?.(details.valueAsNumber, details.value);
      },
    });

    const api = numberInput.connect(service, normalizeProps);

    const isFirst = React.useRef(true);

    React.useEffect(() => {
      if (!isFirst.current) {
        if (
          typeof controlledValue === 'string' &&
          !isNaN(Number(controlledValue))
        ) {
          api.setValue(Number(controlledValue));
        } else if (typeof controlledValue === 'number') {
          api.setValue(controlledValue);
        } else if (controlledValue === undefined) {
          api.setValue(min);
        }
      }
      isFirst.current = false;
    }, [controlledValue]);

    return (
      <BasicField {...basicFieldProps} id={api.getInputProps().id}>
        <InputContainer {...inputContainerProps}>
          <InputAddon {...leftAddonProps} />
          <InputIcon {...leftIconProps} />

          <input
            ref={ref}
            type="number"
            name={basicFieldProps.name}
            className={cn(
              'form-input',
              InputAnatomy.root({
                size,
                intent,
                hasError: !!basicFieldProps.error,
                isDisabled: !!basicFieldProps.disabled,
                hasRightAddon: !!rightAddon || !hideControls,
                hasRightIcon: !!rightIcon,
                hasLeftAddon: !!leftAddon,
                hasLeftIcon: !!leftIcon,
              }),
              NumberInputAnatomy.root({ hideControls, intent, size }),
              className
            )}
            disabled={basicFieldProps.disabled || basicFieldProps.readonly}
            data-disabled={basicFieldProps.disabled}
            data-readonly={basicFieldProps.readonly}
            aria-readonly={basicFieldProps.readonly}
            required={basicFieldProps.required}
            {...api.getInputProps()}
            {...rest}
          />

          {!hideControls && (
            <div
              className={cn(
                InputAnatomy.root({
                  size,
                  intent,
                  hasError: !!basicFieldProps.error,
                  isDisabled: !!basicFieldProps.disabled,
                  hasRightAddon: !!rightAddon,
                  hasRightIcon: !!rightIcon,
                  hasLeftAddon: true,
                }),
                NumberInputAnatomy.controlsContainer({
                  size,
                  intent,
                  hasRightAddon: !!rightAddon,
                }),
                controlsContainerClass
              )}
            >
              <IconButton
                intent="gray-basic"
                size="sm"
                className={cn(NumberInputAnatomy.control(), controlClass)}
                {...api.getIncrementTriggerProps()}
                data-readonly={basicFieldProps.readonly}
                data-disabled={
                  basicFieldProps.disabled ||
                  api.getIncrementTriggerProps().disabled
                }
                disabled={
                  basicFieldProps.disabled ||
                  basicFieldProps.readonly ||
                  api.getIncrementTriggerProps().disabled
                }
                tabIndex={0}
                icon={
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    className={cn(
                      NumberInputAnatomy.chevronIcon(),
                      'rotate-180',
                      chevronIconClass
                    )}
                  >
                    <path d="m6 9 6 6 6-6" />
                  </svg>
                }
              />
              <IconButton
                intent="gray-basic"
                size="sm"
                className={cn(NumberInputAnatomy.control(), controlClass)}
                {...api.getDecrementTriggerProps()}
                data-readonly={basicFieldProps.readonly}
                data-disabled={
                  basicFieldProps.disabled ||
                  api.getDecrementTriggerProps().disabled
                }
                disabled={
                  basicFieldProps.disabled ||
                  basicFieldProps.readonly ||
                  api.getDecrementTriggerProps().disabled
                }
                tabIndex={0}
                icon={
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth="2"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    className={cn(
                      NumberInputAnatomy.chevronIcon(),
                      chevronIconClass
                    )}
                  >
                    <path d="m6 9 6 6 6-6" />
                  </svg>
                }
              />
            </div>
          )}

          <InputAddon {...rightAddonProps} />
          <InputIcon
            {...rightIconProps}
            className={cn(
              'z-3',
              rightIconProps.className,
              !rightAddon ? 'mr-6' : null
            )}
          />
        </InputContainer>
      </BasicField>
    );
  }
);

NumberInput.displayName = 'NumberInput';
