import React, { useState, useRef, useEffect, useCallback } from 'react';
import styled, { css } from 'styled-components';

import type { JSX } from 'react';

import { Tab } from '@redocly/theme/markdoc/components/Tabs/Tab';
import { TabItemProps, TabsSize } from '@redocly/theme/markdoc/components/Tabs/Tabs';
import { Dropdown } from '@redocly/theme/components/Dropdown/Dropdown';
import { DropdownMenu } from '@redocly/theme/components/Dropdown/DropdownMenu';
import { DropdownMenuItem } from '@redocly/theme/components/Dropdown/DropdownMenuItem';
import { Button } from '@redocly/theme/components/Button/Button';
import { useTabs } from '@redocly/theme/core/hooks';
import { getTabId } from '@redocly/theme/core/utils';

type TabListProps = {
  childrenArray: React.ReactElement<TabItemProps>[];
  size: TabsSize;
  activeTab: string;
  onTabChange: (tab: string) => void;
  containerRef: React.RefObject<HTMLUListElement | null>;
  onReadyChange?: (isReady: boolean) => void;
};

type UseHighlightBarAnimationProps = {
  childrenArray: React.ReactElement<TabItemProps>[];
  activeTab: string;
  tabsContainerRef: React.RefObject<HTMLElement | null>;
  visibleTabs: number[];
  overflowTabs: number[];
};

/**
 * Calculates optimal dropdown position relative to viewport to ensure visibility.
 * Positions below the button by default, but moves above if insufficient space.
 * Adjusts horizontal position to prevent overflow off screen edges.
 */
const calculateDropdownPosition = (
  buttonRect: DOMRect,
  dropdownRect: DOMRect,
): { top: number; left: number } => {
  const gap = 4;
  const margin = 16;
  const spaceBelow = window.innerHeight - buttonRect.bottom;
  const spaceAbove = buttonRect.top;

  // Position below button, or above if dropdown doesn't fit below
  const top =
    spaceBelow < dropdownRect.height + gap && spaceAbove > spaceBelow
      ? buttonRect.top - gap
      : buttonRect.bottom + gap;

  // Align with button left edge, adjust if overflows screen
  const idealLeft = buttonRect.left;
  const rightEdge = idealLeft + dropdownRect.width;
  const overflowsRight = rightEdge > window.innerWidth - margin;

  const left = overflowsRight
    ? window.innerWidth - dropdownRect.width - margin
    : Math.max(margin, idealLeft);

  return { top, left };
};

/**
 * Manages dropdown positioning and updates on scroll/resize events for TabList.
 */
const useDropdownPosition = (
  hasOverflow: boolean,
  dropdownRef: React.RefObject<HTMLDivElement | null>,
) => {
  const [dropdownPosition, setDropdownPosition] = useState<{ top?: number; left?: number }>({});
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);

  const updateDropdownPosition = useCallback(() => {
    if (!dropdownRef.current) return;

    const button = dropdownRef.current.querySelector('button');
    const dropdownMenu = dropdownRef.current.querySelector('div:last-child');
    if (!button || !dropdownMenu) return;

    const buttonRect = button.getBoundingClientRect();
    const dropdownRect = (dropdownMenu as HTMLElement).getBoundingClientRect();

    const position = calculateDropdownPosition(buttonRect, dropdownRect);
    setDropdownPosition(position);
  }, [dropdownRef]);

  // Track when dropdown menu appears and recalculate position
  useEffect(() => {
    if (!hasOverflow || !isDropdownOpen || !dropdownRef.current) return;

    const dropdownMenu = dropdownRef.current.querySelector('div:last-child') as HTMLElement;
    if (!dropdownMenu) return;

    // ResizeObserver tracks both initial render and size changes
    const resizeObserver = new ResizeObserver(() => {
      updateDropdownPosition();
    });

    resizeObserver.observe(dropdownMenu);

    return () => resizeObserver.disconnect();
  }, [hasOverflow, isDropdownOpen, dropdownRef, updateDropdownPosition]);

  // Update position on scroll/resize
  useEffect(() => {
    if (!hasOverflow || !isDropdownOpen) return;

    window.addEventListener('scroll', updateDropdownPosition, true);
    window.addEventListener('resize', updateDropdownPosition);

    return () => {
      window.removeEventListener('scroll', updateDropdownPosition, true);
      window.removeEventListener('resize', updateDropdownPosition);
    };
  }, [hasOverflow, isDropdownOpen, updateDropdownPosition]);

  return {
    dropdownPosition,
    isDropdownOpen,
    setIsDropdownOpen,
    setDropdownPosition,
    updateDropdownPosition,
  };
};

const renderTab = (
  child: React.ReactElement<TabItemProps>,
  index: number,
  size: TabsSize,
  setTabRef: (element: HTMLButtonElement | null, index: number) => void,
  handleKeyboard: (event: React.KeyboardEvent, index: number) => void,
  onTabClick: (labelOrIndex: string | number) => void,
) => {
  const { label, icon } = child.props;
  const tabId = getTabId(label, index);

  return (
    <Tab
      key={`key-${tabId}`}
      tabId={tabId}
      label={label}
      icon={icon}
      size={size}
      disabled={child.props.disable}
      setRef={(el: HTMLButtonElement | null) => setTabRef(el, index)}
      onKeyDown={(event) => handleKeyboard(event, index)}
      onClick={() => {
        child.props.onClick?.();
        onTabClick(label);
      }}
    />
  );
};

export function TabList({
  childrenArray,
  size,
  activeTab,
  onTabChange,
  containerRef,
  onReadyChange,
}: TabListProps): JSX.Element {
  const dropdownRef = useRef<HTMLDivElement>(null);
  const totalTabs = childrenArray.length;

  const { overflowTabs, visibleTabs, handleKeyboard, onTabClick, setTabRef, isReady } = useTabs({
    activeTab,
    onTabChange,
    containerRef,
    totalTabs,
  });

  useEffect(() => {
    onReadyChange?.(isReady);
  }, [isReady, onReadyChange]);

  const { highlightStyle } = useHighlightBarAnimation({
    activeTab,
    childrenArray,
    overflowTabs,
    tabsContainerRef: containerRef,
    visibleTabs,
  });

  const hasOverflow = overflowTabs.length > 0;
  const isMoreActive =
    hasOverflow &&
    overflowTabs.some((i) => childrenArray[i] && activeTab === childrenArray[i].props.label);

  // Show as selector when no visible tabs (all tabs in dropdown)
  const showAsSelector = visibleTabs.length === 0 && hasOverflow;

  const { dropdownPosition, setIsDropdownOpen, setDropdownPosition } = useDropdownPosition(
    hasOverflow,
    dropdownRef,
  );

  return (
    <TabListContainer role="tablist" ref={containerRef}>
      <HighlightBar size={size} style={highlightStyle}>
        <div />
      </HighlightBar>

      {childrenArray.map((child, index) => {
        // Show all tabs before ready (for measurement), then only visible ones
        const shouldRender = !isReady || visibleTabs.includes(index);
        if (!shouldRender) return null;
        return renderTab(child, index, size, setTabRef, handleKeyboard, onTabClick);
      })}

      {hasOverflow && (
        <TabItem
          size={size}
          active={isMoreActive || showAsSelector}
          tabIndex={0}
          className="dropdown-tab"
        >
          <DropdownWrapper
            $top={dropdownPosition.top}
            $left={dropdownPosition.left}
            onClickCapture={() => {
              setIsDropdownOpen(true);
            }}
          >
            <FixedPositionDropdown
              ref={dropdownRef}
              trigger={
                <TabButtonLink
                  size={size}
                  className={isMoreActive || showAsSelector ? 'active' : undefined}
                >
                  {showAsSelector ? <TabButtonText>{activeTab}</TabButtonText> : 'More'}
                </TabButtonLink>
              }
              alignment="start"
              withArrow
              onClose={() => {
                setIsDropdownOpen(false);
                setDropdownPosition({});
              }}
            >
              <DropdownMenu>
                {overflowTabs.map((index) => {
                  const child = childrenArray[index];
                  if (!child) return null;

                  const { label } = child.props;
                  const tabId = getTabId(label, index);

                  return (
                    <DropdownMenuItem
                      key={`more-${tabId}`}
                      active={activeTab === label}
                      onAction={() => {
                        child.props.onClick?.();
                        onTabClick(index);
                      }}
                      disabled={child.props.disable}
                    >
                      {label}
                    </DropdownMenuItem>
                  );
                })}
              </DropdownMenu>
            </FixedPositionDropdown>
          </DropdownWrapper>
        </TabItem>
      )}
    </TabListContainer>
  );
}

const useHighlightBarAnimation = (props: UseHighlightBarAnimationProps) => {
  const { childrenArray, activeTab, tabsContainerRef, visibleTabs, overflowTabs } = props;

  const [highlightStyle, setHighlightStyle] = React.useState<{ left: number; width: number }>({
    left: 0,
    width: 0,
  });

  useEffect(() => {
    const activeIndex = childrenArray.findIndex((child) => child.props.label === activeTab);
    const container = tabsContainerRef.current;

    if (!container || activeIndex === -1) {
      setHighlightStyle({ left: 0, width: 0 });
      return;
    }

    // Remove active class from all tabs first
    container.querySelectorAll('[data-label]').forEach((el) => {
      el.classList.remove('active');
    });

    // Check if active tab is in overflow first
    if (overflowTabs.includes(activeIndex)) {
      const moreButton = container.querySelector('button');
      if (!moreButton) return;

      const moreButtonRect = moreButton.getBoundingClientRect();
      const containerRect = container.getBoundingClientRect();

      setHighlightStyle({
        left: moreButtonRect.left - containerRect.left,
        width: moreButtonRect.width,
      });
      return;
    }

    // Active tab is visible, find its element
    const activeTabElement: HTMLElement | null = container.querySelector(
      `[data-label="${activeTab}"]`,
    );
    if (!activeTabElement) return;

    const { offsetLeft, offsetWidth } = activeTabElement;

    if (visibleTabs.includes(activeIndex)) {
      activeTabElement.classList.add('active');
      setHighlightStyle({ left: offsetLeft, width: offsetWidth });
      return;
    }
  }, [activeTab, childrenArray, visibleTabs, overflowTabs, tabsContainerRef]);

  return { highlightStyle };
};

export const TabListContainer = styled.ul`
  position: relative;
  display: flex;
  gap: var(--md-tabs-gap);
  width: 100%;
  min-width: 0;

  &::before {
    content: '';
    position: absolute;
    inset: 0;
    border: var(--md-tabs-border);
    border-width: var(--md-tabs-border-width);
    pointer-events: none;
  }

  && {
    padding: var(--md-tabs-padding);
    margin: 0;

    & > li {
      margin-bottom: 0;
      flex-shrink: 0;

      &.dropdown-tab {
        flex-shrink: 1;
        min-width: 0;
        max-width: 100%;
      }
    }
  }
`;

export const TabItem = styled.li<{ active?: boolean; size: TabsSize; tabIndex?: number }>`
  display: inline-flex;
  list-style: none;
  cursor: pointer;
  align-items: center;
  padding: var(--md-tabs-tab-wrapper-padding);
  z-index: var(--z-index-surface);

  ${({ active, size }) =>
    active
      ? css`
          border: solid var(--md-tabs-active-tab-border-color);
          border-width: var(--md-tabs-${size}-active-tab-border-width);
        `
      : css`
          border: solid var(--md-tabs-hover-tab-border-color);
          border-width: var(--md-tabs-${size}-hover-tab-border-width);
          &:hover {
            border: solid var(--md-tabs-hover-tab-border-color);
            border-width: var(--md-tabs-${size}-hover-tab-border-width);
          }
        `}

  div > div > ul {
    padding-left: var(--spacing-unit);
  }

  &:focus-visible {
    outline: none;
    position: relative;

    &::after {
      content: '';
      position: absolute;
      top: -2px;
      right: -4px;
      bottom: -2px;
      left: -4px;
      border: 1px solid var(--button-border-color-focused);
      border-radius: 6px;
      pointer-events: none;
    }
  }
`;

const DropdownWrapper = styled.div.attrs<{ $top?: number; $left?: number }>((props) => ({
  style: {
    ...(props.$top !== undefined && { '--dropdown-top': `${props.$top}px` }),
    ...(props.$left !== undefined && { '--dropdown-left': `${props.$left}px` }),
  },
}))<{ $top?: number; $left?: number }>`
  position: static;
  z-index: var(--z-index-raised);
  width: 100%;
  min-width: 0;
`;

const FixedPositionDropdown = styled(Dropdown)`
  position: static;
  width: 100%;
  min-width: 0;

  > div:first-child {
    width: 100%;
    min-width: 0;
  }

  > div:last-child {
    position: fixed;
    top: var(--dropdown-top, 0);
    left: var(--dropdown-left, 0);
    right: auto;
    bottom: auto;
    transform: none;
    padding-top: 0;
    max-width: min(400px, calc(100vw - 32px));
    max-height: calc(100vh - var(--dropdown-top, 0) - 32px);
    overflow-y: auto;
    z-index: var(--z-index-raised);

    ul {
      li {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
    }
  }
`;

const HighlightBar = styled.div<{ size: TabsSize }>`
  position: absolute;
  top: 0;
  bottom: 0;
  border: solid var(--md-tabs-active-tab-border-color);
  border-width: var(--md-tabs-${({ size }) => size}-active-tab-border-width);
  transition:
    left 300ms ease-in-out,
    width 300ms ease-in-out;
  z-index: 0;
  padding: var(--md-tabs-tab-wrapper-padding);

  & > div {
    width: 100%;
    height: 100%;
    background-color: var(--md-tabs-active-tab-bg-color);
    border-radius: var(--md-tabs-${({ size }) => size}-active-tab-border-radius);
  }
`;

const TabButtonText = styled.span`
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
`;

export const TabButtonLink = styled(Button)`
  color: var(--md-tabs-tab-text-color);
  font-family: var(--md-tabs-tab-font-family);
  font-style: var(--md-tabs-tab-font-style);
  background-color: var(--md-tabs-tab-bg-color);
  width: 100%;

  transition:
    background-color 300ms ease-in-out,
    color 300ms ease-in-out,
    padding 300ms ease-in-out,
    border-radius 300ms ease-in-out;

  ${({ size }) =>
    size &&
    css`
      padding: var(--md-tabs-${size}-tab-padding);
      font-size: var(--md-tabs-${size}-tab-font-size);
      font-weight: var(--md-tabs-${size}-tab-font-weight);
      line-height: var(--md-tabs-${size}-tab-line-height);
      border-radius: var(--md-tabs-${size}-tab-border-radius);
    `}

  &.active {
    color: var(--md-tabs-active-tab-text-color);
    font-family: var(--md-tabs-active-tab-font-family);
    font-style: var(--md-tabs-active-tab-font-style);
    font-size: var(--md-tabs-${({ size }) => size}-active-tab-font-size);
    font-weight: var(--md-tabs-${({ size }) => size}-active-tab-font-weight);
    line-height: var(--md-tabs-${({ size }) => size}-active-tab-line-height);
    background-color: var(--md-tabs-active-tab-bg-color);
    border-radius: var(--md-tabs-${({ size }) => size}-active-tab-border-radius);
    padding: var(--md-tabs-${({ size }) => size}-active-tab-padding);
  }

  &:hover {
    color: var(--md-tabs-hover-tab-text-color);
    font-family: var(--md-tabs-hover-tab-font-family);
    font-style: var(--md-tabs-hover-tab-font-style);
    font-size: var(--md-tabs-${({ size }) => size}-hover-tab-font-size);
    font-weight: var(--md-tabs-${({ size }) => size}-hover-tab-font-weight);
    line-height: var(--md-tabs-${({ size }) => size}-hover-tab-line-height);
    background-color: var(--md-tabs-hover-tab-bg-color);
    border-radius: var(--md-tabs-${({ size }) => size}-hover-tab-border-radius);
    padding: var(--md-tabs-${({ size }) => size}-hover-tab-padding);
  }

  ${({ disabled }) =>
    disabled &&
    css`
      color: var(--md-tabs-tab-text-disabled-color);
      cursor: not-allowed;

      &:hover {
        color: var(--md-tabs-tab-text-disabled-color);
        background-color: transparent;
      }
    `}

  svg {
    flex-shrink: 0;
  }
`;
