'use client'

import type { ComponentProps } from 'react'

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

import { useCSSPropsPolyfill, useCSSState, useGDS } from '../hooks/index.ts'
import { cn, getCSSPropsAttributes, isReactNode } from '../utils/index.ts'
import { renderAddon, type AddonValue } from './base/Addon.tsx'
import { ButtonOrLink, type ButtonOrLinkProps } from './base/ButtonOrLink.tsx'
import { ButtonMeta } from './Button.meta.ts'
import { Tooltip, type TooltipProps } from './Tooltip.tsx'

export declare namespace ButtonProps {
  interface OwnProps {
    addonBefore?: AddonValue | undefined
    addonAfter?: AddonValue | undefined
    /** For icon-only buttons, defaults to the icon's `alt`, otherwise defaults to `undefined` */
    tooltip?: TooltipProps['content'] | Omit<TooltipProps, 'children'> | undefined
    /** @default false */
    loading?: boolean | undefined
  }
  interface CommonProps extends OwnProps, GDSComponentProps<typeof ButtonMeta> {}
  interface ButtonProps
    extends CommonProps, ButtonOrLinkProps.ButtonProps, ButtonOrLinkProps.DisableableProps {}
  interface ToggleButtonProps
    extends CommonProps, ButtonOrLinkProps.ToggleButtonProps, ButtonOrLinkProps.DisableableProps {}
  interface LinkProps
    extends CommonProps, ButtonOrLinkProps.LinkProps, ButtonOrLinkProps.DisableableProps {}
}

export type ButtonProps = ButtonProps.ButtonProps | ButtonProps.LinkProps

export function Button({
  ref: passedRef,
  variant,
  size,
  hideLabel,
  addonBefore: passedAddonBefore,
  addonAfter: passedAddonAfter,
  tooltip,
  loading = false,
  disabled = false,
  className,
  style,
  children,
  ...props
}: ButtonProps) {
  useGDS()

  const [statePassedRef, state] = useCSSState(
    {
      pointer: undefined,
      focus: undefined,
      checked: props.checked,
      disabled: Boolean(disabled),
      'addon-only': !children ? 'auto' : '0',
    },
    { ref: passedRef },
  )
  const [cssPropsPolyfillStatePassedRef, cssPropsPolyfillAttributes, cssProps] =
    useCSSPropsPolyfill(
      ButtonMeta,
      { variant, size, hideLabel },
      { ref: statePassedRef, returnPropValues: { variant, hideLabel } },
    )

  // If `loading`, replace one of the passed slots with a loading icon (the first of `addonAfter`, `addonBefore`, or `children`)
  const loadingIcon = (
    <span className="nested/button-loading absolute inset-0 animate flex items-center justify-center animate-opacity-from-0 animate-duration-500">
      <LoadingIcon alt="" />
    </span>
  )
  const getAddon = (passedAddon: AddonValue, replaceWithLoading: boolean) => {
    if (passedAddon && replaceWithLoading) {
      return (
        <>
          <span className="invisible">{renderAddon(passedAddon)}</span>
          {loadingIcon}
        </>
      )
    }
    return renderAddon(passedAddon)
  }
  const addonBefore = getAddon(passedAddonBefore, loading && !passedAddonAfter)
  const addonAfter = getAddon(passedAddonAfter, loading)
  const replaceChildrenWithLoading = loading && !addonBefore && !addonAfter

  return (
    <ButtonOrLink
      ref={cssPropsPolyfillStatePassedRef}
      disabled={disabled || (loading ? 'focusable' : false)}
      aria-busy={loading || undefined}
      data-variant={cssProps.variant}
      data-hide-label={cssProps.hideLabel || undefined}
      className={cn(
        `gds-button root-flex flex-col u:size-max u:max-w-full u:outline-0
        u:checked:state-checked
        u:hover:state-hover
        u:focus-visible:state-focus
        u:active:state-active
        u:disabled:state-disabled
        u:disabled:state-idle
        ${/* Determine whether the button is "addon-only" (`0` means no and `auto` means yes; we can improve this when `if()` is supported, e.g. `min-w-[if(style(--gds-state-addon-only):auto;else:0)]`) */ ''}
        state-[addon-only=0]
        data-hide-label:state-[addon-only=auto]
        not-has-nested/button-children:state-[addon-only=auto]
        has-nested-has-addon-compatible-only/button-children:state-[addon-only=auto]
        ${/* Ensure an addon-only button does not get squished in a flex container (like a button group) */ ''}
        u:min-w-(--gds-state-addon-only)
        ${/* Ensure a button in a horizontal button group grows by default, except if it is addon-only */ ''}
        u:@prop-orientation-horizontal/button-group:has-nested-not-has-addon-compatible-only/button-children:grow
        ${/* But if the button group is `fullWidth`, then all the buttons it contains should be equal width, regardless of their content */ ''}
        u:@prop-orientation-horizontal/button-group:@prop-full-width-true/button-group:grow
        u:@prop-orientation-horizontal/button-group:@prop-full-width-true/button-group:basis-0
        ${/* In a button group, the group itself should be the stacking context */ ''}
        u:in-button-group:isolation-auto
        ${/* Extend the tap area of a naked button */ ''}
        data-[variant=naked]:before:absolute
        data-[variant=naked]:before:-inset-2`,
        className,
      )}
      {...state.polyfillAttributes}
      {...getCSSPropsAttributes(ButtonMeta, { variant, size, hideLabel }, style)}
      {...cssPropsPolyfillAttributes}
      {...props}
      render={(renderProps, { Element, elementProps }) => (
        <Tooltip
          {...(isReactNode(tooltip) ? { content: tooltip } : tooltip)}
          triggerProps={renderProps}
        >
          <Element {...elementProps}>
            {/* Prevent the extended tap area of a naked button from overlapping the tap area of another naked button in the same group */}
            <span
              className={`
                absolute inset-[calc(-1*var(--gds-button-group-gap)/2)] z-10
                not-in-button-group:hidden
                @prop-not-variant-naked/button:hidden
              `}
            />
            <span
              className={`
                nested/button-paint pointer-events-none flex h-(--height) max-w-full min-w-full grow items-center justify-center
                rounded-(--gds-button-radius) border-(length:--border-width) border-(--gds-button-border)
                bg-(--gds-button-bg) text-(--gds-button-fg) transition [--border-width:1px]
                @state-[addon-only=auto]/button:w-(--height)
                @prop-variant-naked/button:transition-[color]
                @prop-variant-naked/button:[--border-width:0px]
                @prop-size-xsmall/button:px-1.25
                @prop-size-xsmall/button:text-12
                @prop-size-xsmall/button:[--height:--spacing(7)]
                @prop-size-small/button:px-1.75
                @prop-size-small/button:text-12
                @prop-size-small/button:[--height:--spacing(8)]
                @prop-size-medium/button:px-1.75
                @prop-size-medium/button:text-14
                @prop-size-medium/button:[--height:--spacing(10)]
                @prop-size-large/button:px-2.75
                @prop-size-large/button:text-16
                @prop-size-large/button:[--height:--spacing(12)]
                u:*:transition-opacity
                u:*:@state-disabled/button:opacity-disabled
                u:*:@state-disabled/button:grayscale
                u:@prop-size-xsmall/button:addon-small
                u:@prop-size-small/button:addon-small
                u:@prop-size-medium/button:addon-medium
                u:@prop-size-large/button:addon-large
                i:@state-active/button:transition-none
                i:@prop-variant-naked/button:px-0
                i:@prop-variant-naked/button:[--height:1lh]
              `}
            >
              {/* Focus ring (rendered separately for better spacing in naked buttons) */}
              <span
                className={`
                  absolute -inset-px rounded-inherit opacity-100 filter-none
                  @state-focus/button:outline
                  @prop-variant-naked/button:-inset-0.5
                `}
              />
              {addonBefore ? <ButtonAddon side="before">{addonBefore}</ButtonAddon> : null}
              {children ? (
                <span
                  data-loading={replaceChildrenWithLoading || undefined}
                  className={cn(
                    `nested/button-children
                    truncate
                    data-loading:invisible
                    @prop-hide-label-true/button:not-has-addon-compatible:sr-only
                    u:@prop-size-xsmall/button:px-0.5
                    u:@prop-size-small/button:px-0.5
                    u:@prop-size-medium/button:px-1
                    u:@prop-size-large/button:px-1
                    u:i:@state-[addon-only=auto]/button:px-0`,
                  )}
                >
                  <Tooltip.Collector>{children}</Tooltip.Collector>
                </span>
              ) : null}
              {replaceChildrenWithLoading ? loadingIcon : null}
              {addonAfter ? <ButtonAddon side="after">{addonAfter}</ButtonAddon> : null}
            </span>
          </Element>
        </Tooltip>
      )}
    />
  )
}

function ButtonAddon({
  side,
  className,
  children,
  ...props
}: ComponentProps<'span'> & { side: 'before' | 'after' }) {
  return (
    <span
      data-side={side}
      className={cn(
        `gds-addon root-flex items-center justify-center u:h-(--gds-addon-size) u:min-w-(--gds-addon-size) u:shrink-0
        u:has-avatar-group:data-[side=after]:ps-0.5
        u:has-tag:data-[side=after]:ps-0.5
        u:has-avatar-group:data-[side=before]:pe-0.5
        u:has-tag:data-[side=before]:pe-0.5
        u:has-nested/button-loading:opacity-100`,
        className,
      )}
      {...props}
    >
      {children}
    </span>
  )
}
