'use client'

import { useCallback, useRef, useState, type ComponentProps, type CSSProperties } from 'react'
import { useRenderCount, useThrottledEffect } from '@react-hookz/web'
import { isEqual } from '@ver0/deep-equal'

import type { GDSComponentProps } from '@graphprotocol/gds-css'
import { ArrowLeftInteractiveIcon, DotsThreeIcon } from '@graphprotocol/gds-react/icons'

import { useCSSPropsPolyfill, useCSSState, useGDS } from '../hooks/index.ts'
import { cn, getCSSPropsAttributes, splitProps } from '../utils/index.ts'
import { renderAddon, type AddonValue } from './base/Addon.tsx'
import { MaybeButtonOrLink, type MaybeButtonOrLinkProps } from './base/MaybeButtonOrLink.tsx'
import { BreadcrumbsMeta, type BreadcrumbsItemMeta } from './Breadcrumbs.meta.js'
import { Button } from './Button.tsx'
import { Menu } from './Menu.tsx'

export interface BreadcrumbsProps
  extends ComponentProps<'nav'>, GDSComponentProps<typeof BreadcrumbsMeta> {}

export function BreadcrumbsRoot({
  ref: passedRef,
  align,
  'aria-label': passedAriaLabel,
  className,
  style,
  children,
  ...props
}: BreadcrumbsProps) {
  useGDS()

  const [cssPropsPolyfillPassedRef, cssPropsPolyfillAttributes] = useCSSPropsPolyfill(
    BreadcrumbsMeta,
    { align },
    { ref: passedRef },
  )

  const ariaLabel = passedAriaLabel !== undefined ? passedAriaLabel : 'Breadcrumbs'

  const hiddenContainerRef = useRef<HTMLDivElement>(null)
  const [hiddenItems, setHiddenItems] = useState<Array<{ label: string; href?: string }>>([])

  // Detect how many items have wrapped in the hidden container
  const updateWrappedCount = useCallback(() => {
    if (!hiddenContainerRef.current) return

    const items = Array.from(hiddenContainerRef.current.querySelectorAll('.gds-breadcrumbs-item'))
    if (items.length === 0) return

    // Get the top offset of the first item
    const firstItem = items[0]
    if (!(firstItem instanceof HTMLElement)) return
    const firstItemTop = firstItem.offsetTop

    // Collect wrapped items' label and href
    const wrappedItems: Array<{ label: string; href?: string }> = []
    for (let i = 1; i < items.length - 1; i++) {
      const item = items[i]
      if (item instanceof HTMLElement && item.offsetTop > firstItemTop) {
        const titleElement = item.querySelector('.nested\\/breadcrumbs-item-title')
        const linkElement = item.querySelector('a')
        if (titleElement) {
          const item = {
            label: titleElement.textContent?.trim() || '',
          } as { label: string; href?: string }
          if (linkElement?.href) {
            item.href = linkElement.href
          }
          wrappedItems.push(item)
        }
      }
    }
    const newHiddenItems = wrappedItems.reverse()

    // Only update state if items actually changed
    setHiddenItems((previousHiddenItems) =>
      isEqual(previousHiddenItems, newHiddenItems) ? previousHiddenItems : newHiddenItems,
    )
  }, [])

  /**
   * Call `updateWrappedCount` after every render, with `useThrottledEffect` to prevent a "Maximum
   * update depth exceeded" error in the unlikely event of an infinite loop due to `hiddenItems`
   * causing a layout change that makes items wrap differently (change `-ms-10` below to `-ms-20` to
   * see how that can happen).
   */
  const renderCount = useRenderCount()
  useThrottledEffect(updateWrappedCount, [renderCount], 1)

  // Call `updateWrappedCount` on resize
  const resizeObserverRef = useRef<ResizeObserver | null>(null)
  const hiddenContainerRefCallback = useCallback(
    (element: HTMLDivElement | null) => {
      hiddenContainerRef.current = element
      if (resizeObserverRef.current) {
        resizeObserverRef.current.disconnect()
      }
      if (!element) return
      resizeObserverRef.current = new ResizeObserver(() => {
        updateWrappedCount()
      })
      resizeObserverRef.current.observe(element)
    },
    [updateWrappedCount],
  )

  return (
    <nav
      ref={cssPropsPolyfillPassedRef}
      aria-label={ariaLabel}
      className={cn(
        'gds-breadcrumbs root-grid *:col-span-full *:row-span-full u:max-w-full u:text-14 i:h-max',
        className,
      )}
      {...getCSSPropsAttributes(BreadcrumbsMeta, { align }, style)}
      {...cssPropsPolyfillAttributes}
      {...props}
    >
      <div
        className={`
          flex
          @prop-align-start/breadcrumbs:justify-start
          @prop-align-center/breadcrumbs:justify-center
          @prop-align-end/breadcrumbs:justify-end
        `}
      >
        <div
          role="list"
          className={`
            -m-0.5 scrollbar-none flex gap-4 overflow-x-auto gradient-mask-x p-0.5 whitespace-nowrap
            style-nested/breadcrumbs-item-wrapper:before:absolute
            style-nested/breadcrumbs-item-wrapper:before:inset-y-0
            style-nested/breadcrumbs-item-wrapper:before:end-full
            style-nested/breadcrumbs-item-wrapper:before:my-auto
            style-nested/breadcrumbs-item-wrapper:before:h-6
            style-nested/breadcrumbs-item-wrapper:before:w-4
            style-nested/breadcrumbs-item-wrapper:before:bg-(image:--bg-dark)
            light:style-nested/breadcrumbs-item-wrapper:before:bg-(image:--bg-light)
          `}
          style={
            {
              '--bg-dark': `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='24' viewBox='0 0 16 24'><rect x='9' y='4' width='1' height='16' transform='translate(1 0) rotate(15 9 4)' fill='%23afafb6'/></svg>")`,
              '--bg-light': `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='24' viewBox='0 0 16 24'><rect x='9' y='4' width='1' height='16' transform='translate(1 0) rotate(15 9 4)' fill='%2359556c'/></svg>")`,
            } as CSSProperties
          }
        >
          <div
            data-hidden-items={hiddenItems.length || undefined}
            // TODO: Replace the `data-[hidden-items=X]` approach with `if` and `sibling-index` when supported everywhere: https://codepen.io/benface/pen/azNbGpy
            className={`
              contents
              *:last:order-last
              data-[hidden-items=1]:*:nth-last-[-n+2]:not-first:not-last:hidden
              data-[hidden-items=2]:*:nth-last-[-n+3]:not-first:not-last:hidden
              data-[hidden-items=3]:*:nth-last-[-n+4]:not-first:not-last:hidden
              data-[hidden-items=4]:*:nth-last-[-n+5]:not-first:not-last:hidden
              data-[hidden-items=5]:*:nth-last-[-n+6]:not-first:not-last:hidden
              data-[hidden-items=6]:*:nth-last-[-n+7]:not-first:not-last:hidden
              data-[hidden-items=7]:*:nth-last-[-n+8]:not-first:not-last:hidden
              data-[hidden-items=8]:*:nth-last-[-n+9]:not-first:not-last:hidden
            `}
          >
            {children}
          </div>
          {hiddenItems.length > 0 ? (
            <div className="nested/breadcrumbs-item-wrapper z-10 flex shrink-0 items-center px-1">
              <Menu
                trigger={
                  <Button variant="naked" size="small">
                    <DotsThreeIcon alt="Show hidden items" className="top-1.25" />
                  </Button>
                }
                align="center"
                gap={4}
              >
                {hiddenItems.map((item, index) => (
                  <Menu.Item
                    key={index}
                    href={item.href}
                    disabled={item.href === undefined}
                    addonBefore={<ArrowLeftInteractiveIcon hiddenByDefault />}
                    addonAfter={null}
                  >
                    {item.label}
                  </Menu.Item>
                ))}
              </Menu>
            </div>
          ) : null}
        </div>
      </div>
      <div
        ref={hiddenContainerRefCallback}
        inert
        aria-hidden
        className="-m-0.5 flex h-0 flex-wrap gap-4 p-0.5 whitespace-nowrap opacity-0"
      >
        <div
          data-hidden-items={hiddenItems.length || undefined}
          className={`
            contents
            *:first:order-first
            *:last:order-first
            data-hidden-items:*:nth-last-[2]:-ms-10
          `}
        >
          {children}
        </div>
        {hiddenItems.length > 0 ? (
          <div className="order-first flex shrink-0 items-center px-1">
            <Button variant="naked" size="small" />
          </div>
        ) : null}
      </div>
    </nav>
  )
}
BreadcrumbsRoot.displayName = 'Breadcrumbs'

export declare namespace BreadcrumbsItemProps {
  interface OwnProps {
    addonBefore?: AddonValue | undefined
    /**
     * Whether this breadcrumb item represents the current page. Defaults to `true` if no `href` is
     * passed, or `false` otherwise.
     */
    current?: boolean | undefined
  }
  interface CommonProps extends OwnProps, GDSComponentProps<typeof BreadcrumbsItemMeta> {}
  interface ButtonProps extends CommonProps, MaybeButtonOrLinkProps.ButtonProps {}
  interface LinkProps extends CommonProps, MaybeButtonOrLinkProps.LinkProps {}
  interface OtherElementProps extends CommonProps, MaybeButtonOrLinkProps.OtherElementProps {}
}

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

export function BreadcrumbsItem({
  addonBefore,
  current: passedCurrent,
  className,
  children,
  ...props
}: BreadcrumbsItemProps) {
  const { rootProps, nestedProps } = splitProps(props)
  const current = passedCurrent ?? props.href === undefined

  const [stateRef, state] = useCSSState({
    pointer: undefined,
    focus: undefined,
    current,
  })

  return (
    <div
      ref={stateRef}
      role="listitem"
      className={cn(
        `nested/breadcrumbs-item-wrapper gds-breadcrumbs-item root-block
        u:has-nested-current/breadcrumbs-item-ref:expose-current
        u:has-nested-hover/breadcrumbs-item-ref:expose-hover
        u:has-nested-focus-visible/breadcrumbs-item-ref:expose-focus
        u:i:has-nested-not-clickable/breadcrumbs-item-ref:state-idle
        ii:shrink-0`,
        className,
      )}
      {...state.exposedAttributes}
      {...state.polyfillAttributes}
      {...rootProps}
    >
      <MaybeButtonOrLink
        aria-current={current ? 'page' : undefined}
        className={`
          nested/breadcrumbs-item-ref flex items-center gap-1 rounded-4 px-2 py-1 text-muted transition
          @state-current/breadcrumbs-item:text-default
          @state-hover/breadcrumbs-item:text-default
        `}
        {...nestedProps}
      >
        {/* Ensure rounded corners are clickable */}
        <span className="absolute inset-0 not-in-clickable:hidden" />
        {addonBefore ? (
          <span
            className={`
              gds-addon root-flex items-center justify-center
              u:h-lh u:min-w-(--gds-addon-size) u:shrink-0 u:addon-small
            `}
          >
            {renderAddon(addonBefore)}
          </span>
        ) : null}
        <span className="nested/breadcrumbs-item-title">{children}</span>
      </MaybeButtonOrLink>
    </div>
  )
}
BreadcrumbsItem.displayName = 'Breadcrumbs.Item'
