'use client'

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

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

import { useCSSPropsPolyfill, useCSSState } from '../../hooks/index.ts'
import { cn, getCSSPropsAttributes } from '../../utils/index.ts'
import { CheckableMeta } from './Checkable.meta.ts'
import { Render, type RenderProp } from './Render.tsx'

export type CheckableState = {
  inputProps: {
    name: string | undefined
    value: string | undefined
    checked: boolean | undefined
    'aria-checked': 'mixed' | undefined
    disabled: boolean | undefined
    'aria-label'?: string | undefined
    'aria-labelledby'?: string | undefined
    className: string
  }
}

export interface CheckableProps
  extends Omit<ComponentProps<'span'>, 'children'>, GDSComponentProps<typeof CheckableMeta> {
  label: ReactNode | undefined
  name: string | undefined
  value: string | undefined
  checked: boolean | 'indeterminate' | undefined
  disabled: boolean | undefined
  renderInput: RenderProp<CheckableState>
}

export function CheckableRoot({
  ref: passedRef,
  size,
  label,
  'aria-label': ariaLabel,
  'aria-labelledby': ariaLabelledBy,
  name,
  value,
  checked,
  disabled,
  className,
  style,
  renderInput,
  ...props
}: CheckableProps) {
  const hasAncestorLabel = useContext(CheckableLabelContext) !== null
  const hasAccessibleLabel = Boolean(
    hasAncestorLabel || label || ariaLabel !== undefined || ariaLabelledBy !== undefined,
  )
  if (!hasAccessibleLabel) {
    // oxlint-disable-next-line no-console
    console.warn(
      '[Checkable] One of `label`, `aria-label`, or `aria-labelledby` should be set for accessibility when there is no ancestor `CheckableLabel`',
    )
  }
  if (ariaLabel && ariaLabelledBy) {
    // oxlint-disable-next-line no-console
    console.warn(
      '[Checkable] `aria-label` and `aria-labelledby` should not be used together; `aria-labelledby` will be ignored',
    )
  }

  const [cssPropsPolyfillPassedRef, cssPropsPolyfillAttributes] = useCSSPropsPolyfill(
    CheckableMeta,
    { size },
    { ref: passedRef },
  )

  const uniqueId = useId()
  const labelId = label ? `gds-checkable-label-${uniqueId}` : undefined
  const inputProps: CheckableState['inputProps'] = {
    name,
    value,
    checked: typeof checked === 'boolean' ? checked : undefined,
    'aria-checked': checked === 'indeterminate' ? 'mixed' : undefined,
    disabled,
    className: cn('nested/checkable-ref u:pointer-events-none u:absolute u:size-full u:opacity-0'),
  }
  if (ariaLabel) {
    inputProps['aria-label'] = ariaLabel
  } else if (ariaLabelledBy) {
    inputProps['aria-labelledby'] = ariaLabelledBy
  } else if (labelId) {
    inputProps['aria-labelledby'] = labelId
  }

  return (
    <CheckableLabel
      ref={cssPropsPolyfillPassedRef}
      checked={checked}
      disabled={disabled}
      className={cn('gds-checkable root-flex u:w-max u:max-w-full u:outline-0 i:h-max', className)}
      data-name={name}
      data-value={value}
      {...getCSSPropsAttributes(CheckableMeta, { size }, style)}
      {...cssPropsPolyfillAttributes}
      {...props}
    >
      <span
        className={`
          flex
          @prop-size-small/checkable:gap-1.5
          @prop-size-small/checkable:text-12
          @prop-size-medium/checkable:gap-2
          @prop-size-medium/checkable:text-14
          @prop-size-large/checkable:gap-2
          @prop-size-large/checkable:text-16
        `}
      >
        <Render render={renderInput} state={{ inputProps }} />
        {label ? (
          <span
            id={labelId}
            className={`
              text-form-text transition
              disabled:**:pointer-events-none
              @state-active/checkable:text-form-placeholder
              @state-active/checkable:transition-none
              @state-disabled/checkable:text-form-disabled
            `}
          >
            {label}
          </span>
        ) : null}
      </span>
    </CheckableLabel>
  )
}
CheckableRoot.displayName = 'Checkable'

export function CheckableBox({ className, ...props }: ComponentProps<'span'>) {
  return (
    <span
      className={cn(
        `root-grid shrink-0 place-items-center border transition *:col-span-full *:row-span-full
        @state-active/checkable:transition-none
        u:size-lh
        u:border-form-idle
        u:@state-not-unchecked/checkable:border-form-checked-idle
        u:@state-not-unchecked/checkable:bg-form-checked-idle
        u:@state-hover/checkable:border-form-hover
        u:@state-not-unchecked/checkable:@state-hover/checkable:border-form-checked-hover
        u:@state-not-unchecked/checkable:@state-hover/checkable:bg-form-checked-hover
        u:@state-focus/checkable:outline
        u:@state-active/checkable:border-form-active
        u:@state-not-unchecked/checkable:@state-active/checkable:border-form-checked-active
        u:@state-not-unchecked/checkable:@state-active/checkable:bg-form-checked-active
        u:@state-disabled/checkable:border-form-disabled
        u:@state-disabled/checkable:bg-form-disabled
        u:@state-not-unchecked/checkable:@state-disabled/checkable:border-form-checked-disabled
        u:@state-not-unchecked/checkable:@state-disabled/checkable:bg-form-checked-disabled`,
        className,
      )}
      {...props}
    />
  )
}
CheckableBox.displayName = 'Checkable.Box'

const CheckableLabelContext = createContext<boolean>(false)

export interface CheckableLabelProps extends ComponentProps<'span'> {
  checked?: CheckableProps['checked'] | undefined
  disabled?: CheckableProps['disabled'] | undefined
  /**
   * Whether the focus ring should be on this wrapper element when the nested checkable is focused,
   * rather than on the nested checkable itself.
   *
   * @default true
   */
  focusRing?: boolean | undefined
  /**
   * Allows you to compose the component with another component that renders a `label` element (e.g.
   * `<Card as="label" />`). Defaults to `<label />`, unless there is an ancestor `CheckableLabel`,
   * in which case it defaults to `<span />`.
   */
  render?: RenderProp | undefined
}

export function CheckableLabel({
  ref: passedRef,
  checked,
  disabled,
  focusRing = true,
  render,
  className,
  ...props
}: CheckableLabelProps) {
  const hasAncestorLabel = useContext(CheckableLabelContext) !== false
  const renderLabel = !hasAncestorLabel

  const [statePassedRef, state] = useCSSState(
    { pointer: undefined, focus: undefined, checked, disabled },
    { ref: passedRef },
  )

  return (
    <CheckableLabelContext.Provider value={true}>
      <Render
        ref={statePassedRef}
        render={render ?? (renderLabel ? <label /> : <span />)}
        data-focus-ring={focusRing || undefined}
        className={cn(
          renderLabel ? 'group/label gds-checkable-label' : null,
          `uu:data-focus-ring:focus-visible:outline
          uu:has-nested-checked/checkable-ref:expose-checked
          uu:has-nested-indeterminate/checkable-ref:expose-indeterminate
          uu:has-nested-hover/checkable-ref:expose-hover
          uu:has-nested-focus-visible/checkable-ref:expose-focus
          uu:has-nested-active/checkable-ref:not-has-clickable-active:expose-active
          uu:has-nested-disabled/checkable-ref:expose-disabled
          uu:i:has-nested-disabled/checkable-ref:state-idle ${/* Do not show the hover/active state of a disabled checkable */ ''}
          uu:i:has-nested-[input[type=radio]:checked]/checkable-ref:state-idle ${/* Do not show the hover/active state of a checked radio */ ''}
          uu:i:group-data-focus-ring/label:state-not-focus ${/* Prevent multiple focus rings */ ''}
          u:i:not-data-focus-ring:state-not-focus ${/* Override any `state-focus` set by a composed component (e.g. `Card`) */ ''}`,
          className,
        )}
        {...state.exposedAttributes}
        {...state.polyfillAttributes}
        {...props}
      />
    </CheckableLabelContext.Provider>
  )
}
CheckableLabel.displayName = 'Checkable.Label'
