import React, { cloneElement, useRef, forwardRef } from 'react';
import styled from 'styled-components';

import type { PropsWithChildren, ReactElement } from 'react';

import { useOutsideClick, useControlledState } from '@redocly/theme/core/hooks';
import { ChevronDownIcon } from '@redocly/theme/icons/ChevronDownIcon/ChevronDownIcon';
import { ChevronUpIcon } from '@redocly/theme/icons/ChevronUpIcon/ChevronUpIcon';

interface TriggerProps {
  onClick?: (event: React.UIEvent) => void;
  onKeyDown?: (event: React.KeyboardEvent) => void;
  icon?: React.ReactNode;
  iconPosition?: string;
  [key: string]: unknown;
}

export type DropdownProps = PropsWithChildren<{
  trigger: React.ReactNode;
  triggerEvent?: 'click' | 'hover';
  placement?: 'top' | 'bottom';
  alignment?: 'start' | 'end';

  active?: boolean;
  closeOnClick?: boolean;

  dataAttributes?: Record<string, string>;
  className?: string;
  withArrow?: boolean;

  onClick?: (event: React.UIEvent) => void;
  onClose?: () => void;
}>;

export const Dropdown = forwardRef<HTMLDivElement, DropdownProps>(
  (
    {
      children,
      className,
      active,
      trigger,
      triggerEvent = 'click',
      closeOnClick = true,
      withArrow,
      dataAttributes,
      placement,
      alignment,
      onClick,
      onClose,
    },
    ref,
  ) => {
    const dropdownRef = useRef<HTMLDivElement | null>(null);
    const [isOpen, setIsOpen] = useControlledState<boolean>(false, active);

    const handleOpen = () => {
      setIsOpen(true);
    };

    const handleClose = () => {
      setIsOpen(false);
      onClose?.();
    };

    const handleChildClick = () => {
      handleClose();
    };

    const handleToggle = (event: React.UIEvent) => {
      event.stopPropagation();
      event.preventDefault();
      setIsOpen(!isOpen);
    };

    const handleKeyDown = (event: React.KeyboardEvent) => {
      if (event.key === 'Enter' || event.key === ' ') {
        handleToggle(event);
      }
    };

    useOutsideClick((ref as React.RefObject<HTMLElement | null>) || dropdownRef, handleClose);

    const triggerChild = React.Children.only(trigger) as ReactElement<TriggerProps>;

    const dropdownTrigger = cloneElement(triggerChild, {
      onClick: triggerEvent === 'click' ? handleToggle : undefined,
      icon: withArrow ? isOpen ? <ChevronUpIcon /> : <ChevronDownIcon /> : undefined,
      ...(withArrow ? { iconPosition: 'right' } : {}),
      ...triggerChild.props,
      onKeyDown: triggerEvent === 'click' ? handleKeyDown : undefined,
    });

    return (
      <DropdownWrapper
        data-component-name="Dropdown/Dropdown"
        data-testid="dropdown"
        {...dataAttributes}
        className={className}
        ref={ref || dropdownRef}
        onPointerEnter={triggerEvent === 'hover' ? handleOpen : undefined}
        onPointerLeave={triggerEvent === 'hover' ? handleClose : undefined}
        onClick={onClick}
      >
        {dropdownTrigger}

        <ChildrenWrapper
          placement={placement}
          alignment={alignment}
          isOpen={isOpen}
          onClick={closeOnClick ? handleChildClick : undefined}
        >
          {children}
        </ChildrenWrapper>
      </DropdownWrapper>
    );
  },
);

const DropdownWrapper = styled.div`
  --button-gap: var(--spacing-xxs);

  display: flex;
  gap: var(--spacing-xxs);
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  height: 100%;
  appearance: none;
  padding: 0;
  margin: 0;
  position: relative;

  text-decoration: none;
`;

const ChildrenWrapper = styled.div<{ placement?: string; alignment?: string; isOpen?: boolean }>`
  padding-top: var(--dropdown-menu-padding-top);
  position: absolute;
  top: ${({ placement }) => (placement === 'top' ? 'auto' : '100%')};
  bottom: ${({ placement }) => (placement === 'top' ? '100%' : 'auto')};
  left: ${({ alignment }) => (alignment === 'start' ? '0' : 'auto')};
  right: ${({ alignment }) => (alignment === 'end' ? '0' : 'auto')};
  display: ${({ isOpen }) => (isOpen ? 'block' : 'none')};

  z-index: var(--dropdown-menu-z-index);
`;
