import { isValidElement, memo, useMemo } from "react";

import { Flex } from "../flex";
import { Icons } from "../icons";
import { Link } from "../link";
import { IconButton } from "../icon-button";
import { Text } from "../text";
import { styled } from "../../theme";
import { BaseCarousel } from "./base-carousel";
import { useCarousel } from "./use-carousel";

import type { UrlObject } from "url";
import type { motion } from "framer-motion";
import type { Grid } from "../grid";

const StyledNavButtonContainer = styled(Flex, {
  ml: "auto",
  gap: "$4",
});

interface Props extends Omit<React.ComponentProps<typeof Grid>, "columns" | "onChange"> {
  /**
   * The items of the grid
   */
  children: Array<React.ReactElement>;
  /**
   * The number of rows in the grid
   */
  rows: number;
  /**
   * The number of columns in the grid
   */
  columns: number;
  /**
   * The number of columns that will be shifted when using internal navigation buttons
   */
  stepAmount?: number;
  /**
   * The controlled value for the current "step" of the carousel. Step 0 represents the initial state of the carousel
   */
  step?: number;
  /**
   * This prop only has effect when `step` is set.
   * If `true` the carousel will not scroll over the first and last item in the grid even if `step` is set to a value
   * that would otherwise cause this.
   */
  clampStep?: boolean;
  /**
   * Customise the grid movement transition value
   */
  transition?: React.ComponentProps<typeof motion.div>["transition"];
  /**
   * ARIA label for the next button
   */
  nextAriaLabel?: string;
  /**
   * ARIA label for the carousel container.
   */
  carouselAriaLabel?: string;
  /**
   * ARIA label for the previous button.
   */
  previousAriaLabel?: string;
  /**
   * Toggle the internal navigation buttons
   */
  navigation?: boolean;
  /**
   * Optional Link Header
   */
  header?:
    | { title: string; href: string; linkProps?: React.ComponentProps<typeof Link> }
    | React.ReactElement<{
        href: string | UrlObject;
        children: string;
        style?: React.CSSProperties;
      }>;
  /**
   * Event handler called when `value` of the carousel changes
   */
  onStepChange?: (currentStep: number) => void;
  /** If `true` disables drag functionality */
  disableDrag?: boolean;
  /**
   * Base element class name
   */
  className?: string;
}

// eslint-disable-next-line max-lines-per-function
const CarouselComponent: React.FC<Props> = ({
  children,
  rows,
  columns,
  stepAmount,
  step,
  clampStep = true,
  nextAriaLabel,
  previousAriaLabel,
  carouselAriaLabel,
  navigation = true,
  header,
  onStepChange,
  disableDrag = false,
  className,
  ...gridProps
}) => {
  const {
    gridX,
    viewWindowWidth,
    viewWindowHeight,
    gridWidth,
    disableNext,
    disablePrevious,
    firstElementRef,
    increment,
    decrement,
    onItemFocus,
    handleDragEnd,
    onMouseDown,
    onMouseUp,
  } = useCarousel({
    gridProps,
    children,
    columns,
    rows,
    stepAmount,
    step,
    clampStep,
    onStepChange,
  });

  const baseGrid = useMemo(
    () => (
      <BaseCarousel
        viewWindowHeight={viewWindowHeight}
        viewWindowWidth={viewWindowWidth}
        gridWidth={gridWidth}
        gridX={gridX}
        // @ts-expect-error typescript bug
        onDragEnd={handleDragEnd}
        rows={rows}
        firstElementRef={firstElementRef}
        onItemFocus={onItemFocus}
        onMouseDown={onMouseDown}
        onMouseUp={onMouseUp}
        disableDrag={disableDrag}
        {...gridProps}
      >
        {children}
      </BaseCarousel>
    ),
    [
      children,
      rows,
      gridX,
      gridProps,
      gridWidth,
      viewWindowWidth,
      viewWindowHeight,
      firstElementRef,
      disableDrag,
      onItemFocus,
      handleDragEnd,
      onMouseUp,
      onMouseDown,
    ]
  );

  return !navigation && !header ? (
    baseGrid
  ) : (
    <Flex
      direction="column"
      css={{
        height: "fit-content",
        width: "fit-content",
        gap: "$4",
      }}
      aria-label={carouselAriaLabel}
      className={className}
    >
      <Flex align="center">
        {header && (
          <Flex align="center" gap="2">
            {isValidElement(header) ? (
              header
            ) : (
              <Link href={header.href}>
                <Text variant="h5">{header.title}</Text>
              </Link>
            )}

            <Icons.ChevronRight color="$light-off-white" />
          </Flex>
        )}
        {navigation && (
          <StyledNavButtonContainer>
            <IconButton
              Icon={Icons.ArrowLeft}
              onClick={decrement}
              size="l"
              variant="secondary"
              disabled={disablePrevious}
              aria-label={previousAriaLabel}
            />
            <IconButton
              Icon={Icons.ArrowRight}
              onClick={increment}
              size="l"
              variant="secondary"
              disabled={disableNext}
              aria-label={nextAriaLabel}
            />
          </StyledNavButtonContainer>
        )}
      </Flex>

      {baseGrid}
    </Flex>
  );
};

/**
 * An x by y grid carousel component with swipe gesture support.
 */
export const Carousel = memo(CarouselComponent);

export const CarouselNavContainerSelector = StyledNavButtonContainer.toString();
