'use client'

import type { ComponentProps, ReactNode } from 'react'

import type { GDSComponentProps } from '@graphprotocol/gds-css'
import { parseNumber } from '@graphprotocol/gds-utils'

import { useControlled, useGDS } from '../hooks/index.ts'
import { cn, splitProps } from '../utils/index.ts'
import { renderAddon, type AddonValue } from './base/Addon.tsx'
import { Field, type FieldProps } from './base/Field.tsx'
import { Render, type RenderProp } from './base/Render.tsx'
import type { InputMeta } from './Input.meta.ts'

export type InputState = {
  inputProps: ComponentProps<'input'>
  decoratedInput: ReactNode
}

export interface InputProps
  extends
    Omit<ComponentProps<'input'>, 'children' | 'onChange' | 'size' | 'step'>,
    GDSComponentProps<typeof InputMeta> {
  /** @default 'text' */
  type?: 'text' | 'number' | 'email' | 'password' | 'search' | 'tel' | 'url' | undefined
  label?: FieldProps['label'] | undefined
  description?: FieldProps['description'] | undefined
  placeholder?: string | undefined
  name?: string | undefined
  value?: string | undefined
  defaultValue?: string | undefined
  onChange?: ((value: string) => void) | undefined
  maxLength?: number | undefined
  /** @default false */
  disabled?: boolean | undefined
  /** @default false */
  readOnly?: boolean | undefined
  autoComplete?: 'one-time-code' | (string & {}) | undefined
  status?: FieldProps['status'] | undefined
  message?: FieldProps['message'] | undefined
  addon?: AddonValue | undefined
  unit?: ReactNode | undefined
  action?: ReactNode | undefined
  renderInput?: RenderProp<InputState> | undefined
}

export function Input({
  type = 'text',
  size,
  label,
  'aria-label': ariaLabel,
  'aria-labelledby': ariaLabelledBy,
  description,
  placeholder,
  name,
  value: controlledValue,
  defaultValue,
  onChange,
  min,
  max,
  maxLength,
  disabled = false,
  readOnly = false,
  status,
  message,
  addon,
  unit,
  action,
  className,
  renderInput,
  ...props
}: InputProps) {
  useGDS()

  const { rootProps, nestedProps } = splitProps(props)

  const [value, setValue] = useControlled(controlledValue, defaultValue ?? '', onChange)

  return (
    <Field
      size={size}
      label={label}
      aria-label={ariaLabel}
      aria-labelledby={ariaLabelledBy}
      description={description}
      placeholder={placeholder}
      name={name}
      value={value}
      maxLength={maxLength}
      disabled={disabled || (readOnly ? 'read-only' : false)}
      status={status}
      message={message}
      className={cn('gds-input', className)}
      {...rootProps}
      renderInput={(renderProps, { inputProps: fieldInputProps }) => {
        const typeProps =
          type === 'number'
            ? {
                /**
                 * Using `type="text"` instead of `type="number"` because the latter doesn't allow
                 * us to format the number, and the value can unexpectedly change on mouse wheel
                 * (see https://github.com/facebook/react/issues/32156).
                 */
                type: 'text' as const,
                // Show the right keyboard on mobile
                inputMode: type === 'number' ? ('decimal' as const) : undefined,
                // Restore accessibility semantics of `type="number"`
                role: 'spinbutton',
                'aria-valuenow': parseNumber(value) ?? undefined,
                'aria-valuemin':
                  min !== undefined ? (parseNumber(String(min)) ?? undefined) : undefined,
                'aria-valuemax':
                  max !== undefined ? (parseNumber(String(max)) ?? undefined) : undefined,
              }
            : {
                type,
                min,
                max,
              }
        const inputProps: ComponentProps<'input'> & Record<string, unknown> = {
          ...fieldInputProps,
          ...typeProps,
          'data-has-addon': Boolean(addon) || undefined,
          onChange: (event) => {
            setValue(event.target.value)
          },
          className: cn(
            fieldInputProps.className,
            /**
             * The `w-0` and `flex-1` classes are to work around `input` having a default but
             * useless intrinsic width. When `field-sizing` is supported everywhere, we should
             * switch to that.
             */
            `w-0 min-w-16 flex-1
            data-has-addon:ps-(--height)
            @prop-size-small/field:px-2
            @prop-size-medium/field:px-3
            @prop-size-large/field:px-3`,
          ),
          ...nestedProps,
        }

        const decoratedInput = (
          <div
            className={`
              flex h-(--height)
              @prop-size-small/field:[--height:--spacing(8)]
              @prop-size-medium/field:[--height:--spacing(10)]
              @prop-size-large/field:[--height:--spacing(12)]
            `}
          >
            <input {...inputProps} />
            {addon ? (
              <div
                className={`
                  gds-addon pointer-events-none absolute start-0 top-0
                  root-flex aspect-square h-full items-center justify-center text-form-placeholder
                  u:@prop-size-small/field:addon-small
                  u:@prop-size-medium/field:addon-medium
                  u:@prop-size-large/field:addon-medium
                `}
              >
                {renderAddon(addon)}
              </div>
            ) : null}
            {unit || action ? (
              <div className="flex shrink-0 items-center">
                {unit ? (
                  <div
                    className={`
                      cursor-default text-10 text-caption text-form-placeholder transition
                      @state-disabled/field:text-form-disabled
                      @prop-size-small/field:pe-2
                      @prop-size-medium/field:pe-2.5
                      @prop-size-large/field:pe-3
                    `}
                  >
                    {unit}
                  </div>
                ) : null}
                {action ? (
                  <div
                    className={`
                      **:button:default-variant-tertiary
                      @prop-size-small/field:pe-0.5
                      @prop-size-small/field:**:button:var-[radius=var(--radius-4)]
                      @prop-size-small/field:**:button-group:var-[gap=--spacing(0.5)]
                      @prop-size-medium/field:pe-1.5
                      @prop-size-large/field:pe-2
                      @prop-size-small/field:**:button-or-group:prop-size-xsmall
                      @prop-size-medium/field:**:button-or-group:prop-size-xsmall
                      @prop-size-large/field:**:button-or-group:prop-size-small
                    `}
                  >
                    {action}
                  </div>
                ) : null}
              </div>
            ) : null}
          </div>
        )

        return (
          <Render
            render={renderInput ?? <Field.Box>{decoratedInput}</Field.Box>}
            state={{
              inputProps,
              decoratedInput,
            }}
            {...renderProps}
          />
        )
      }}
    />
  )
}
