/* ============================================================================
 * Copyright (c) Palo Alto Networks
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * ========================================================================== */

import React, {
  cloneElement,
  useRef,
  useEffect,
  useState,
  ReactElement,
  LegacyRef,
} from "react";

import {
  sanitizeTabsChildren,
  TabProps,
  useScrollPositionBlocker,
  useTabs,
} from "@docusaurus/theme-common/internal";
import { TabItemProps } from "@docusaurus/theme-common/lib/utils/tabsUtils";
import useIsBrowser from "@docusaurus/useIsBrowser";
import clsx from "clsx";
import flatten from "lodash/flatten";

export interface SchemaTabsProps extends TabProps {
  /**
   * Optional callback fired when the selected tab changes.
   * Receives the index of the newly selected tab.
   */
  onChange?: (index: number) => void;
}

function TabList({
  className,
  block,
  selectedValue,
  selectValue,
  tabValues,
  onChange,
}: SchemaTabsProps & ReturnType<typeof useTabs>) {
  const tabRefs: (HTMLLIElement | null)[] = [];
  const { blockElementScrollPositionUntilNextRender } =
    useScrollPositionBlocker();

  const handleTabChange = (
    event:
      | React.FocusEvent<HTMLLIElement>
      | React.MouseEvent<HTMLLIElement>
      | React.KeyboardEvent<HTMLLIElement>
  ) => {
    const newTab = event.currentTarget;
    const newTabIndex = tabRefs.indexOf(newTab);
    const newTabValue = tabValues[newTabIndex].value;

    if (newTabValue !== selectedValue) {
      blockElementScrollPositionUntilNextRender(newTab);
      selectValue(newTabValue);
      // Notify parent component of the tab change
      onChange?.(newTabIndex);
    }
  };

  const handleKeydown = (event: React.KeyboardEvent<HTMLLIElement>) => {
    let focusElement: HTMLLIElement | null = null;

    switch (event.key) {
      case "Enter": {
        handleTabChange(event);
        break;
      }
      case "ArrowRight": {
        const nextTab = tabRefs.indexOf(event.currentTarget) + 1;
        focusElement = tabRefs[nextTab] ?? tabRefs[0]!;
        break;
      }
      case "ArrowLeft": {
        const prevTab = tabRefs.indexOf(event.currentTarget) - 1;
        focusElement = tabRefs[prevTab] ?? tabRefs[tabRefs.length - 1]!;
        break;
      }
      default:
        break;
    }

    focusElement?.focus();
  };

  const tabItemListContainerRef = useRef<HTMLUListElement>(null);
  const [showTabArrows, setShowTabArrows] = useState<boolean>(false);

  useEffect(() => {
    const resizeObserver = new ResizeObserver((entries) => {
      for (let entry of entries) {
        requestAnimationFrame(() => {
          if (entry.target.clientWidth < entry.target.scrollWidth) {
            setShowTabArrows(true);
          } else {
            setShowTabArrows(false);
          }
        });
      }
    });

    resizeObserver.observe(tabItemListContainerRef.current!);

    return () => {
      resizeObserver.disconnect();
    };
  }, []);

  const handleRightClick = (e: any) => {
    e.preventDefault();
    if (tabItemListContainerRef.current) {
      tabItemListContainerRef.current.scrollLeft += 90;
    }
  };

  const handleLeftClick = (e: any) => {
    e.preventDefault();
    if (tabItemListContainerRef.current) {
      tabItemListContainerRef.current.scrollLeft -= 90;
    }
  };

  return (
    <div
      className="openapi-tabs__schema-tabs-container"
      style={{ marginBottom: "1rem" }}
    >
      {showTabArrows && (
        <button
          className="openapi-tabs__arrow left"
          onClick={handleLeftClick}
        />
      )}
      <ul
        ref={tabItemListContainerRef as LegacyRef<HTMLUListElement>}
        role="tablist"
        aria-orientation="horizontal"
        className={clsx(
          "openapi-tabs__schema-list-container",
          "tabs",
          {
            "tabs--block": block,
          },
          className
        )}
      >
        {tabValues.map(({ value, label, attributes }) => (
          <li
            // TODO extract TabListItem
            role="tab"
            tabIndex={selectedValue === value ? 0 : -1}
            aria-selected={selectedValue === value}
            key={value}
            ref={(tabControl) => {
              tabRefs.push(tabControl);
            }}
            onKeyDown={handleKeydown}
            onClick={handleTabChange}
            {...attributes}
            className={clsx(
              "tabs__item",
              "openapi-tabs__schema-item",
              attributes?.className as string,
              {
                active: selectedValue === value,
              }
            )}
          >
            <span className="openapi-tabs__schema-label">{label ?? value}</span>
          </li>
        ))}
      </ul>
      {showTabArrows && (
        <button
          className="openapi-tabs__arrow right"
          onClick={handleRightClick}
        />
      )}
    </div>
  );
}
function TabContent({
  lazy,
  children,
  selectedValue,
}: SchemaTabsProps & ReturnType<typeof useTabs>) {
  const childTabs = (Array.isArray(children) ? children : [children]).filter(
    Boolean
  ) as ReactElement<TabItemProps>[];
  const flattenedChildTabs = flatten(childTabs);
  if (lazy) {
    const selectedTabItem = flattenedChildTabs.find(
      (tabItem) => tabItem.props.value === selectedValue
    );
    if (!selectedTabItem) {
      // fail-safe or fail-fast? not sure what's best here
      return null;
    }
    return cloneElement(selectedTabItem, { className: "margin-top--md" });
  }
  return (
    <div className="margin-top--md">
      {childTabs.map((tabItem, i) =>
        cloneElement(tabItem, {
          key: i,
          hidden: tabItem.props.value !== selectedValue,
        })
      )}
    </div>
  );
}
function TabsComponent(props: SchemaTabsProps): React.JSX.Element {
  const tabs = useTabs(props);
  return (
    <div className="openapi-tabs__schema-container">
      <TabList {...props} {...tabs} />
      <TabContent {...props} {...tabs} />
    </div>
  );
}
export default function SchemaTabs(props: SchemaTabsProps): React.JSX.Element {
  const isBrowser = useIsBrowser();
  return (
    <TabsComponent
      // Remount tabs after hydration
      // Temporary fix for https://github.com/facebook/docusaurus/issues/5653
      key={String(isBrowser)}
      {...props}
    >
      {sanitizeTabsChildren(props.children)}
    </TabsComponent>
  );
}
