'use client'

import { useId, type ComponentProps, type ReactNode } from 'react'

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

import { useCSSPropsPolyfill, useCSSState } from '../../hooks/index.ts'
import { CheckCircleIcon, WarningDiamondIcon, WarningIcon } from '../../icons/index.generated.ts'
import { cn, getCSSPropsAttributes, type OptionValue } from '../../utils/index.ts'
import { Label, type LabelProps } from '../Label.tsx'
import { FieldMeta } from './Field.meta.ts'
import { Render, type RenderProp } from './Render.tsx'

export type FieldState = {
  inputProps: {
    placeholder: string // Ensure there's always a `placeholder` attribute for `@state-blank` to work
    name: string | undefined
    value: string | undefined
    maxLength: number | undefined
    disabled: boolean | undefined
    readOnly: boolean | undefined
    'aria-readonly': boolean | undefined // For our overridden `read-only` Tailwind variant to work
    'aria-label'?: string | undefined
    'aria-labelledby'?: string | undefined
    'aria-describedby'?: string | undefined
    'aria-invalid'?: boolean | undefined
    'aria-errormessage'?: string | undefined
    className: string
  }
}

export interface FieldProps
  extends Omit<ComponentProps<'div'>, 'children'>, GDSComponentProps<typeof FieldMeta> {
  label:
    | LabelProps['children']
    | {
        content: LabelProps['children']
        addon?: LabelProps['addon']
      }
    | undefined
  description: ReactNode | undefined
  placeholder: string | undefined
  name: string | undefined
  value: string | { optionValue: OptionValue }
  maxLength?: number | undefined
  disabled: boolean | 'read-only'
  /** @default 'info' */
  status?: 'info' | 'warning' | 'error' | 'success' | undefined
  message?: ReactNode | undefined
  renderInput: RenderProp<FieldState>
}

export const FieldRoot = ({
  ref: passedRef,
  size,
  label: passedLabel,
  'aria-label': ariaLabel,
  'aria-labelledby': ariaLabelledBy,
  description: passedDescription,
  placeholder,
  name,
  value,
  maxLength: passedMaxLength,
  disabled,
  status = 'info',
  message,
  className,
  style,
  renderInput,
  ...props
}: FieldProps) => {
  const label = passedLabel
    ? typeof passedLabel === 'object' && 'content' in passedLabel
      ? passedLabel
      : { content: passedLabel }
    : undefined
  const hasAccessibleLabel = Boolean(
    label !== undefined ||
    ariaLabel !== undefined ||
    ariaLabelledBy !== undefined ||
    placeholder !== undefined,
  )
  if (!hasAccessibleLabel) {
    // oxlint-disable-next-line no-console
    console.warn(
      '[Field] One of `label`, `aria-label`, `aria-labelledby`, or `placeholder` should be set for accessibility',
    )
  }
  if (ariaLabel && ariaLabelledBy) {
    // oxlint-disable-next-line no-console
    console.warn(
      '[Field] `aria-label` and `aria-labelledby` should not be used together; `aria-labelledby` will be ignored',
    )
  }

  const [statePassedRef, state] = useCSSState(
    {
      pointer: undefined,
      focus: undefined,
      blank: typeof value === 'object' ? value.optionValue === null : !value,
      disabled,
    },
    { ref: passedRef },
  )
  const [cssPropsPolyfillStatePassedRef, cssPropsPolyfillAttributes] = useCSSPropsPolyfill(
    FieldMeta,
    { size },
    { ref: statePassedRef },
  )

  const uniqueId = useId()
  const descriptionId = passedDescription ? `gds-field-description-${uniqueId}` : undefined
  const description: LabelProps['description'] = passedDescription
    ? {
        content: passedDescription,
        id: descriptionId,
      }
    : undefined
  const labelProps =
    label || description
      ? {
          id: label ? `gds-field-label-${uniqueId}` : undefined,
          children: label?.content,
          addon: label?.addon,
          description,
        }
      : undefined

  const maxLength = passedMaxLength && passedMaxLength > 0 ? passedMaxLength : undefined
  const inputProps: FieldState['inputProps'] = {
    placeholder: placeholder ?? '',
    name,
    value: typeof value === 'string' ? value : undefined,
    maxLength,
    disabled: disabled === true || undefined,
    readOnly: disabled === 'read-only' || undefined,
    'aria-readonly': disabled === 'read-only' || undefined,
    className: cn(`
      nested/field-ref
      u:outline-0
      u:@state-focus/field:placeholder:text-transparent
      u:@state-disabled/field:resize-none
      u:@state-disabled/field:placeholder:text-form-disabled
      ${/* Prevent browsers from overriding the background color on autofill (see https://stackoverflow.com/a/68240841) */ ''}
      u:transition-[background-color]
      u:duration-[600000s]
    `),
  }
  if (ariaLabel) {
    inputProps['aria-label'] = ariaLabel
  } else if (ariaLabelledBy) {
    inputProps['aria-labelledby'] = ariaLabelledBy
  } else if (labelProps?.id) {
    inputProps['aria-labelledby'] = labelProps.id
  } else {
    inputProps['aria-label'] = placeholder
  }
  const messageId = `gds-field-message-${uniqueId}`
  inputProps['aria-describedby'] = [descriptionId, messageId].filter(Boolean).join(' ')
  if (status === 'error') {
    inputProps['aria-invalid'] = true
    if (message) {
      inputProps['aria-errormessage'] = messageId
    }
  }

  return (
    <div
      ref={cssPropsPolyfillStatePassedRef}
      data-name={name}
      data-status={status}
      className={cn(
        `group/field gds-field root-block u:max-w-full
        u:has-nested-open/field-ref:expose-focus
        u:has-nested-read-only/field-ref:expose-read-only
        u:has-nested-blank/field-ref:expose-blank
        u:has-nested-hover/field-ref:has-nested-enabled/field-ref:state-hover
        u:has-nested-focus/field-ref:has-nested-enabled/field-ref:expose-focus
        u:has-nested-active/field-ref:has-nested-enabled/field-ref:state-active
        u:has-nested-disabled/field-ref:expose-disabled`,
        className,
      )}
      {...state.exposedAttributes}
      {...state.polyfillAttributes}
      {...getCSSPropsAttributes(FieldMeta, { size }, style)}
      {...cssPropsPolyfillAttributes}
      {...props}
    >
      {labelProps || (maxLength !== undefined && typeof value === 'string') ? (
        <div className="mb-2 flex items-baseline gap-2">
          {labelProps ? (
            <Label
              {...labelProps}
              className={`
                @prop-size-small/field:default-size-small
                @prop-size-medium/field:default-size-medium
                @prop-size-large/field:default-size-large
              `}
            />
          ) : null}
          {maxLength !== undefined && typeof value === 'string' ? (
            <div
              className={`
                ms-auto shrink-0 text-end text-form-placeholder
                @prop-size-small/field:text-10
                @prop-size-medium/field:text-12
                @prop-size-large/field:text-14
              `}
            >
              <div className="sr-only">Character count</div>
              {/**
               * Limit how often this element changes width while typing in case it affects the main component's
               * width (e.g. if it's a flex item or it has `w-max`, `w-fit`, etc.).
               */}
              <div className="invisible">
                {'0'.repeat(Math.max(2, String(value.length).length) + `/${maxLength}`.length)}
              </div>
              <div className="absolute inset-0">
                {value.length}/{maxLength}
              </div>
            </div>
          ) : null}
        </div>
      ) : null}
      <Render
        render={renderInput}
        state={{ inputProps }}
        className={cn(`
          u:text-form-text
          u:@state-disabled/field:text-form-disabled
          u:@prop-size-small/field:text-12
          u:@prop-size-medium/field:text-14
          u:@prop-size-large/field:text-16
        `)}
      />
      <div
        data-visible={Boolean(message) || undefined}
        className={`
          mt-2 flex gap-1 text-12 text-form-helper
          not-data-visible:sr-only
          **:icon:h-lh
          **:icon:default-size-3
        `}
      >
        {(() => {
          switch (status) {
            case 'warning':
              return <WarningIcon alt="Warning" variant="fill" color="status-warning-default" />
            case 'error':
              return <WarningDiamondIcon alt="Error" variant="fill" color="status-error-default" />
            case 'success':
              return <CheckCircleIcon alt="Success" variant="fill" color="status-success-default" />
            default:
              return null
          }
        })()}
        {/* `w-0` is used to prevent the dynamic content from affecting the intrinsic width of the component */}
        <div
          id={messageId}
          role={status === 'error' ? 'alert' : 'status'}
          className="w-0 flex-1 wrap-anywhere"
        >
          {message}
        </div>
      </div>
    </div>
  )
}
FieldRoot.displayName = 'Field'

export const FieldBox = ({ className, children, ...props }: ComponentProps<'div'>) => {
  return (
    <div
      className={cn(
        `root-flex border transition
        @state-focus/field:transition-none
        u:border-form-idle
        u:group-data-[status=error]/field:border-form-status-error-idle
        u:group-data-[status=warning]/field:border-form-status-warning-idle
        u:@state-not-enabled/field:border-form-disabled
        u:@state-not-enabled/field:bg-form-disabled
        u:@state-hover/field:border-form-hover
        u:@state-hover/field:group-data-[status=error]/field:border-form-status-error-hover
        u:@state-hover/field:group-data-[status=warning]/field:border-form-status-warning-hover
        u:@state-focus/field:border-focus
        u:@state-focus/field:group-data-[status=error]/field:border-form-status-error-focus
        u:@state-focus/field:group-data-[status=warning]/field:border-form-status-warning-focus
        u:@prop-size-small/field:rounded-6
        u:@prop-size-medium/field:rounded-8
        u:@prop-size-large/field:rounded-10`,
        className,
      )}
      {...props}
    >
      {/* Offset the border */}
      <div className="-m-px flex-1">{children}</div>
    </div>
  )
}
FieldBox.displayName = 'Field.Box'
