'use client'

import {
  useEffect,
  useRef,
  useState,
  type ComponentProps,
  type KeyboardEvent,
  type MouseEvent,
} from 'react'
import { Radio } from '@base-ui/react/radio'
import { RadioGroup } from '@base-ui/react/radio-group'
import { useMergedRefs } from '@base-ui/utils/useMergedRefs'

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

import {
  useAutoValue,
  useControlled,
  useCSSPropsPolyfill,
  useCSSState,
  useGDS,
} from '../hooks/index.ts'
import {
  cn,
  getCSSPropsAttributes,
  isReactNode,
  splitProps,
  type OptionValue,
} from '../utils/index.ts'
import { renderAddon, type AddonValue } from './base/Addon.tsx'
import { SegmentedControlMeta, type SegmentedControlOptionMeta } from './SegmentedControl.meta.ts'
import { Tooltip, type TooltipProps } from './Tooltip.tsx'

export interface SegmentedControlProps<T extends OptionValue = OptionValue>
  extends
    Omit<ComponentProps<'div'>, 'defaultValue' | 'onChange'>,
    GDSComponentProps<typeof SegmentedControlMeta> {
  value?: T | undefined
  defaultValue?: T | undefined
  onChange?: ((value: T) => void) | undefined
  // TODO: Add `noToggle` prop to disable toggling behavior when there are only two options?
}

export function SegmentedControlRoot<T extends OptionValue>({
  ref: passedRef,
  size,
  value: controlledValue,
  defaultValue,
  onChange,
  className,
  style,
  children,
  ...props
}: SegmentedControlProps<T>) {
  useGDS()

  if (props['aria-label'] === undefined && props['aria-labelledby'] === undefined) {
    // oxlint-disable-next-line no-console
    console.warn(
      '[SegmentedControl] One of `aria-label` or `aria-labelledby` should be set for accessibility',
    )
  }

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

  const [value, setValue] = useControlled(controlledValue, defaultValue ?? null, onChange)
  const [valueJustChanged, setValueJustChanged] = useState(false)

  // Reset `valueJustChanged` after the transition
  useEffect(() => {
    if (!valueJustChanged) return
    const timeout = window.setTimeout(() => {
      setValueJustChanged(false)
    }, 250)
    return () => window.clearTimeout(timeout)
  }, [valueJustChanged])

  return (
    <RadioGroup
      ref={cssPropsPolyfillPassedRef}
      value={value}
      onValueChange={(newValue) => {
        setValue(newValue as T)
        setValueJustChanged(true)
      }}
      className={cn(
        'gds-segmented-control root-block [anchor-scope:all] u:w-max u:max-w-full i:h-max',
        className,
      )}
      {...getCSSPropsAttributes(SegmentedControlMeta, { size }, style)}
      {...cssPropsPolyfillAttributes}
      {...props}
    >
      <div
        className={`
          group/segmented-control border border-button-secondary-idle p-px
          has-focus-visible:outline
          @prop-size-small/segmented-control:rounded-6
          @prop-size-small/segmented-control:text-12
          @prop-size-medium/segmented-control:rounded-8
          @prop-size-medium/segmented-control:text-14
        `}
      >
        <span
          data-value-just-changed={valueJustChanged || undefined}
          className={`
            absolute inset-x-[anchor(inside)] inset-y-px border border-muted bg-button-secondary-idle transition-[inset,scale]
            [position-anchor:--gds-segmented-control-checked]
            not-data-value-just-changed:duration-0
            not-supports-[position-anchor:--foo]:hidden
            group-not-has-data-checked/segmented-control:invisible
            group-not-has-data-checked/segmented-control:scale-95
            @prop-size-small/segmented-control:rounded-4
            @prop-size-medium/segmented-control:rounded-6
          `}
        />
        <span className="group/segmented-control-options grid grid-flow-col">{children}</span>
      </div>
    </RadioGroup>
  )
}
SegmentedControlRoot.displayName = 'SegmentedControl'

export interface SegmentedControlOptionProps<T extends OptionValue = OptionValue>
  extends ComponentProps<'span'>, GDSComponentProps<typeof SegmentedControlOptionMeta> {
  value?: T | undefined
  addonBefore?: AddonValue | undefined
  addonAfter?: AddonValue | undefined
  /** For icon-only options, defaults to the icon's `alt`, otherwise defaults to `undefined` */
  tooltip?: TooltipProps['content'] | Omit<TooltipProps, 'children'> | undefined
}

export function SegmentedControlOption<T extends OptionValue>({
  ref: passedRef,
  value: passedValue,
  addonBefore,
  addonAfter,
  tooltip,
  className,
  children,
  ...props
}: SegmentedControlOptionProps<T>) {
  const { rootProps, nestedProps } = splitProps(props)

  const buttonRef = useRef<HTMLSpanElement>(null)
  const buttonPassedRef = useMergedRefs(buttonRef, passedRef)

  const autoValue = useAutoValue(children)
  const value = passedValue !== undefined ? passedValue : autoValue

  const [stateRef, state] = useCSSState({
    pointer: undefined,
    checked: undefined,
    'is-toggle': undefined,
  })

  const toggle = (event: MouseEvent | KeyboardEvent) => {
    if (!buttonRef.current) return
    const optionButtons =
      buttonRef.current
        .closest('.group\\/segmented-control-options')
        ?.querySelectorAll('.nested\\/segmented-control-option-ref') ?? []
    if (optionButtons.length !== 2) return
    const otherOptionButton = Array.from(optionButtons).find(
      (button) => button !== buttonRef.current,
    )
    if (!otherOptionButton) return
    event.preventDefault()
    buttonRef.current.dispatchEvent(
      new KeyboardEvent('keydown', {
        key: 'ArrowUp',
        code: 'ArrowUp',
        keyCode: 38,
        which: 38,
        bubbles: true,
        cancelable: true,
      }),
    )
  }

  return (
    <Tooltip {...(isReactNode(tooltip) ? { content: tooltip } : tooltip)}>
      {/**
       * This wrapper is necessary to ensure this component returns a single element (which some Tailwind
       * classes might assume) because `Radio.Root` renders a `<input type="radio">` as a sibling of the
       * button (actually a `<span>`). Ideally, we would make `Radio.Root` the root and it would render an
       * internal component (e.g. `SegmentedControlOptionButton`) that would itself render a wrapper
       * `<div>` around the button and the input (as well as calling `useCSSState` with a proper initial
       * `checked` value, from the `render` state), but that is not possible currently (see
       * https://github.com/mui/base-ui/issues/3143).
       */}
      <div
        ref={stateRef}
        className={cn(
          `gds-segmented-control-option root-block
          state-[is-toggle=false]
          group-[:has([data-checked]):has(>:nth-child(2)):not(:has(>:nth-child(3)))]/segmented-control-options:state-[is-toggle]
          u:group-has-nested-hover/segmented-control--segmented-control-toggle:state-hover
          u:group-has-nested-active/segmented-control--segmented-control-toggle:state-active
          u:has-nested-hover/segmented-control-option-ref:state-hover
          u:has-nested-active/segmented-control-option-ref:state-active
          u:has-nested-[[data-checked]]/segmented-control-option-ref:state-checked`,
          className,
        )}
        {...state.polyfillAttributes}
        {...rootProps}
      >
        <Radio.Root
          ref={buttonPassedRef}
          value={value}
          onKeyDown={(event) => {
            // If there are only two options, toggle between them when pressing Space, like a checkbox
            if (event.key === ' ') {
              toggle(event)
            }
          }}
          className={`
            nested/segmented-control-option-ref block w-full outline-0
            @state-checked/segmented-control-option:[anchor-name:--gds-segmented-control-checked]
          `}
          {...nestedProps}
        >
          <span
            className={`
              flex h-(--height) min-w-(--height) items-center justify-center border border-transparent text-button-naked-idle transition
              not-supports-[position-anchor:--foo]:@state-checked/segmented-control-option:border-muted
              not-supports-[position-anchor:--foo]:@state-checked/segmented-control-option:bg-button-secondary-idle
              @state-hover/segmented-control-option:text-button-naked-hover
              @state-active/segmented-control-option:text-button-naked-active
              @state-active/segmented-control-option:transition-none
              @prop-size-small/segmented-control:rounded-4
              @prop-size-small/segmented-control:px-0.5
              @prop-size-small/segmented-control:[--height:--spacing(7)]
              @prop-size-medium/segmented-control:rounded-6
              @prop-size-medium/segmented-control:px-1
              @prop-size-medium/segmented-control:[--height:--spacing(9)]
              u:@prop-size-small/segmented-control:addon-small
              u:@prop-size-medium/segmented-control:addon-medium
              i:@state-checked/segmented-control-option:text-button-naked-toggled-idle
            `}
          >
            {addonBefore ? (
              <SegmentedControlOptionAddon>{renderAddon(addonBefore)}</SegmentedControlOptionAddon>
            ) : null}
            {children ? (
              <span className="truncate px-1">
                <Tooltip.Collector>{children}</Tooltip.Collector>
              </span>
            ) : null}
            {addonAfter ? (
              <SegmentedControlOptionAddon>{renderAddon(addonAfter)}</SegmentedControlOptionAddon>
            ) : null}
          </span>
          {/* If there are only two options, allow clicking on either to toggle between them */}
          <span
            onClick={toggle}
            className={`
              nested/segmented-control-toggle absolute inset-0 cursor-pointer
              @state-not-[is-toggle]/segmented-control-option:hidden
              @state-unchecked/segmented-control-option:hidden
            `}
          />
        </Radio.Root>
      </div>
    </Tooltip>
  )
}
SegmentedControlOption.displayName = 'SegmentedControl.Option'

function SegmentedControlOptionAddon({ className, children, ...props }: ComponentProps<'span'>) {
  return (
    <span
      className={cn(
        'gds-addon root-flex items-center justify-center u:h-(--gds-addon-size) u:min-w-(--gds-addon-size) u:shrink-0',
        className,
      )}
      {...props}
    >
      {children}
    </span>
  )
}
