/* ============================================================================
 * 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,
  type TabItemProps,
  TabProps,
  TabsProvider,
  useScrollPositionBlocker,
  useTabsContextValue,
} from "@docusaurus/theme-common/internal";
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 useTabsContextValue>) {
  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 useTabsContextValue>) {
  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}</div>;
}
function TabsComponent(props: SchemaTabsProps): React.JSX.Element {
  const tabs = useTabsContextValue(props);
  return (
    <TabsProvider value={tabs}>
      <div className="openapi-tabs__schema-container">
        <TabList {...props} {...tabs} />
        <TabContent {...props} {...tabs} />
      </div>
    </TabsProvider>
  );
}
export default function SchemaTabs(
  props: SchemaTabsProps
): React.JSX.Element | null {
  const isBrowser = useIsBrowser();

  const children = Array.isArray(props.children)
    ? props.children.filter(Boolean)
    : props.children
      ? [props.children]
      : [];

  if (children.length === 0) {
    return null;
  }

  let sanitizedChildren;
  try {
    sanitizedChildren = sanitizeTabsChildren(children);
  } catch {
    return null;
  }

  if (
    !sanitizedChildren ||
    (Array.isArray(sanitizedChildren) && sanitizedChildren.length === 0)
  ) {
    return null;
  }

  return (
    <TabsComponent
      // Remount tabs after hydration
      // Temporary fix for https://github.com/facebook/docusaurus/issues/5653
      key={String(isBrowser)}
      {...props}
    >
      {sanitizedChildren}
    </TabsComponent>
  );
}
