'use client'

import { useId, type ReactNode } from 'react'

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

import { useCSSPropsPolyfill, useCSSState, useGDS } from '../hooks/index.ts'
import { cn, getCSSPropsAttributes, splitProps } from '../utils/index.ts'
import { MaybeButtonOrLink, type MaybeButtonOrLinkProps } from './base/MaybeButtonOrLink.tsx'
import { CardMeta } from './Card.meta.ts'

export declare namespace CardProps {
  interface BaseButtonOrLinkProps {
    /**
     * Allows the card to contain interactive elements (e.g. buttons, links, etc.) while being
     * clickable on its own. When set to `true`, `children` is rendered as a sibling of the
     * `MaybeButtonOrLink` component, rather than as its children. When set to a `ReactNode`,
     * `children` is rendered in its normal place (so it shouldn't contain interactive elements),
     * and the `interactiveContent` is rendered as a sibling, absolutely-positioned on top of the
     * card's content.
     *
     * @default false
     */
    interactiveContent?: boolean | Exclude<ReactNode, boolean> | undefined
    /**
     * Whether to render an `ArrowUpRight` icon in the top right corner of the card if `href` is set
     * and `target` is `_blank`.
     *
     * @default true
     */
    autoIcon?: boolean | undefined
  }
  interface ButtonProps
    extends
      GDSComponentProps<typeof CardMeta>,
      BaseButtonOrLinkProps,
      MaybeButtonOrLinkProps.ButtonProps,
      MaybeButtonOrLinkProps.DisableableProps {}
  interface LinkProps
    extends
      GDSComponentProps<typeof CardMeta>,
      BaseButtonOrLinkProps,
      MaybeButtonOrLinkProps.LinkProps,
      MaybeButtonOrLinkProps.DisableableProps {}
  interface OtherElementProps
    extends GDSComponentProps<typeof CardMeta>, MaybeButtonOrLinkProps.OtherElementProps {}
}

export type CardProps = CardProps.ButtonProps | CardProps.LinkProps | CardProps.OtherElementProps

export function Card({
  ref: passedRef,
  as,
  variant,
  disabled,
  className,
  style,
  children,
  ...props
}: CardProps) {
  useGDS()

  const {
    interactiveContent = false,
    autoIcon = true,
    ...remainingProps
  } = props as CardProps.ButtonProps | CardProps.LinkProps
  const { rootProps, nestedProps } = splitProps(remainingProps)

  const [statePassedRef, state] = useCSSState(
    {
      pointer: undefined,
      focus: undefined,
      disabled: Boolean(disabled),
    },
    { ref: passedRef },
  )
  const [cssPropsPolyfillStatePassedRef, cssPropsPolyfillAttributes] = useCSSPropsPolyfill(
    CardMeta,
    { variant },
    { ref: statePassedRef },
  )

  const Element = as ?? 'div'
  const uniqueId = useId()

  return (
    <Element
      ref={cssPropsPolyfillStatePassedRef}
      className={cn(
        `gds-card root-grid *:col-span-full *:row-span-full
        u:outline-0
        u:hover:state-hover
        u:focus-visible:state-focus
        u:active:state-active
        u:has-nested-active/card-interactive-content:state-hover ${/* Do not show the active state when clicking on interactive content */ ''}
        u:has-nested-focus-visible/card-ref:expose-focus
        u:has-nested-clickable-active/card-ref:expose-active
        u:has-nested-clickable-disabled/card-ref:expose-disabled
        u:has-nested-has-clickable-active/card-ref:state-hover ${/* Do not show the active state when clicking on e.g. a link in a `label` card */ ''}
        u:i:not-clickable:not-has-nested-clickable/card-ref:state-idle ${/* Do not show the hover/active state of a non-clickable card */ ''}
        u:i:has-nested-[input[type=radio]:checked]/checkable-ref:state-idle ${/* Do not show the hover/active state of a checked radio card */ ''}`,
        className,
      )}
      {...state.exposedAttributes}
      {...state.polyfillAttributes}
      {...getCSSPropsAttributes(CardMeta, { variant }, style)}
      {...cssPropsPolyfillAttributes}
      {...rootProps}
    >
      <MaybeButtonOrLink
        disabled={disabled}
        className={`
          nested/card-ref flex flex-col rounded-(--gds-card-radius) bg-(--gds-card-bg) outline-0 transition
          @state-focus/card:outline
          @state-active/card:transition-none
          u:*:not-has-checkable:transition-opacity
          u:*:not-has-checkable:@state-disabled/card:opacity-disabled
          u:*:not-has-checkable:@state-disabled/card:grayscale
        `}
        {...state.polyfillAttributes}
        {...nestedProps}
        {...(as !== undefined && ({ as: 'span' } as Record<string, unknown>))}
        render={(renderProps, { Element, elementProps, category, target }) => {
          const renderChildrenOutside = interactiveContent === true && category !== 'other'
          const contentId =
            renderChildrenOutside && !('aria-label' in props || 'aria-labelledby' in props)
              ? `gds-card-content-${uniqueId}`
              : undefined
          return (
            <>
              <Element aria-labelledby={contentId} {...renderProps} {...elementProps}>
                {/**
                 * This `span` ensures that the rounded corners are always hoverable/clickable, instead of only when
                 * `children` includes an absolutely-positioned element that fills the card, and not even
                 * consistently across browsers:
                 *
                 * - `https://bugs.webkit.org/show_bug.cgi?id=294401`
                 * - `https://issues.chromium.org/u/1/issues/424552561`
                 */}
                <span className="absolute inset-0" />
                {autoIcon && target === '_blank' ? (
                  // Wrapping the `ArrowUpRightInteractiveIcon` in a `span` to prevent a conflict between the parent `*:transition-opacity` and its own
                  <span className="absolute end-2 top-2">
                    <ArrowUpRightInteractiveIcon hiddenByDefault="if-can-hover" />
                  </span>
                ) : null}
                {!renderChildrenOutside ? (
                  /**
                   * This `span` is necessary to allow the children to be rendered in a block
                   * formatting context (as opposed to flex) while still being "stretchable" (i.e.
                   * `children` can contain a `<span className="h-full">...</span>`). Note that this
                   * could also be achieved by making the parent `MaybeButtonOrLink` a `block`
                   * instead of a `flex`, but it would vertically center the content whenever the
                   * `MaybeButtonOrLink` renders a `button` due to some browser magic that cannot be
                   * disabled (see https://github.com/w3c/csswg-drafts/issues/3226), which would be
                   * inconsistent behavior.
                   */
                  <span className="w-full flex-1 rounded-inherit border border-transparent px-(--gds-card-padding-x) py-(--gds-card-padding-y)">
                    {children}
                  </span>
                ) : null}
              </Element>
              {renderChildrenOutside ? (
                <span
                  id={contentId}
                  className={`
                    nested/card-interactive-content pointer-events-none block
                    rounded-(--gds-card-radius) border border-transparent px-(--gds-card-padding-x) py-(--gds-card-padding-y)
                    u:**:clickable:pointer-events-previous
                  `}
                >
                  {children}
                </span>
              ) : interactiveContent ? (
                <span
                  className={`
                    nested/card-interactive-content pointer-events-none absolute inset-0
                    rounded-(--gds-card-radius) border border-transparent px-(--gds-card-padding-x) py-(--gds-card-padding-y)
                  `}
                >
                  <div className="pointer-events-previous static rounded-inherit">
                    {interactiveContent}
                  </div>
                </span>
              ) : null}
            </>
          )
        }}
      />
      {/**
       * Render the border here so that it appears on top of any element passed in `children` (at least by
       * default; z-index can be used to override that), including an absolutely-positioned element that
       * fills the card (e.g. a background image).
       */}
      <span
        className={`
          pointer-events-none absolute inset-0 rounded-(--gds-card-radius) border border-(--gds-card-border) transition
          @state-active/card:transition-none
        `}
      />
    </Element>
  )
}
