import { Tabs, TabsProps } from "@mui/material";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { useGlobalState } from "providers";
import React, { useEffect, useState, useRef, ReactNode } from "react";
// import AnimatedTabs from "./AnimatedTabs";

interface DraggableTabsProps extends TabsProps {
  children: React.ReactNode;
  startNode?: React.ReactNode;
  endNode?: React.ReactNode;
}

export default function DraggableTabs(props: DraggableTabsProps) {
  const { startNode, endNode, children, ...tabsProps } = props;
  const [selectedValue, setSelectedValue] = useState<any>(tabsProps.value);
  const initialChildren = React.Children.toArray(children);
  const [hoveredIndex, setHoveredIndex] = useState<number>(-1); // Track hovered tab
  const tabRefs = useRef<(string | number | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal)[]>([]); // Refs for each tab
  const [{ as, user }] = useGlobalState()
  
  // cache key should be unique for the context and user
  let cacheKey = "tab-order-"
  const types = getContextTypes()
  getContextKeys().forEach((key, index) => {
    cacheKey = cacheKey + types[index] + "-" + key + "-"
  })
  cacheKey = cacheKey + (as ? as : user.id())

  // Get tab order from cache
  const [tabOrder, setTabOrder] = useState<number[]>(() => {
    const storedValue = localStorage.getItem(cacheKey);
    return storedValue !== null ? JSON.parse(storedValue) : [];
  });

  const sanitize = (order: any[]) => {
    let newOrder = [...order]
    // if there's something less than 0, increase everything by one
    while (newOrder.some(num => num < 0)) {
      // Increase all numbers by 1
      newOrder = newOrder.map(num => num + 1);
    }

    // find a skipped index and flatten
    while (true) {
      // Find min and max values
      const min = Math.min(...newOrder);
      const max = Math.max(...newOrder);
      
      // Create a set of all numbers that should be present
      const shouldHave = new Set();
      for (let i = min; i <= max; i++) {
        shouldHave.add(i);
      }
      
      // Create a set of actual numbers
      const actual = new Set(newOrder);
      
      // Find first missing number
      let missing = null;
      for (let num of shouldHave) {
        if (!actual.has(num)) {
          missing = num;
          break;
        }
      }
      
      // If no numbers are missing, we're done
      if (missing === null || missing === undefined) break;
      
      // Reduce all numbers greater than the missing number by 1
      newOrder = newOrder.map(num => num > missing ? num - 1 : num);
  }
    return newOrder
  }

  // save tab order to cache when it changes
  useEffect(() => {
    localStorage.setItem(cacheKey, JSON.stringify(sanitize(tabOrder)));
  }, [tabOrder]);

  const getKey = (node: ReactNode): string => {
    return (React.isValidElement(node) && 'key' in node && typeof node.key === 'string') ? node.key : "";
  }

  function removeAndReduceFirst(arr: any[], numToRemove: number) {
    let found = false;
    return arr.map(num => {
        if (num === numToRemove && !found) {
            found = true;
            return null;  // We'll filter this out later
        }
        return num > numToRemove ? num - 1 : num;
    }).filter(num => num !== null);
}
  
  useEffect(() => {
    if (tabOrder.length !== initialChildren.length && initialChildren.length > 0) {
      if (tabOrder.length - initialChildren.length === 1) {
        // Extract keys from the nodes
        const oldKeys = tabRefs.current.map(node => getKey(node));
        const newKeys = initialChildren.map(node => getKey(node));

        // Find the missing key
        const removedIndex = oldKeys.findIndex(key => !newKeys.includes(key));
        setTabOrder(removeAndReduceFirst(sanitize(tabOrder), removedIndex))
        
      } else if (tabOrder.length - initialChildren.length === -1) {
        let newTabOrder = [...tabOrder]
        newTabOrder.push(tabOrder.length)
        setTabOrder(newTabOrder)
      } else {
        setTabOrder(initialChildren.map((_, index) => index))
      }
    }
    tabRefs.current = initialChildren; // Initialize refs
  }, [initialChildren.length]);

  // Sync selectedValue with propValue if controlled
  useEffect(() => {
    if (tabsProps.value !== undefined) {
      setSelectedValue(tabsProps.value); // Keep in sync with parent if controlled
    }
  }, [tabsProps.value]);

  const flattenNodes = (nodes: React.ReactNode, prefix: string) =>
    React.Children.toArray(nodes).flatMap((node, nodeIndex) => {
      if (React.isValidElement(node) && (node.type as any) === React.Fragment) {
        return React.Children.toArray(node.props.children).map((child, childIndex) =>
          React.isValidElement(child)
            ? React.cloneElement(child, { key: `${prefix}-${nodeIndex}-${childIndex}` })
            : child
        );
      }
      return React.isValidElement(node)
        ? React.cloneElement(node, { key: `${prefix}-${nodeIndex}` })
        : node;
    });

  const startTabs = flattenNodes(startNode, "start");
  const endTabs = flattenNodes(endNode, "end");

  const handleDragStart = (e: React.DragEvent<HTMLDivElement>, index: number) => {
    e.dataTransfer.setData("text/plain", index.toString());
    e.dataTransfer.effectAllowed = "move";
  };

  const handleDragOver = (e: React.DragEvent<HTMLDivElement>, index: number) => {
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    setHoveredIndex(index); // Update hovered index
  };

  const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
    e.preventDefault();
    const draggedIndex = Number(e.dataTransfer.getData("text/plain"));
    const dropIndex = hoveredIndex; // Use the last hovered index

    if (dropIndex === -1) {
      console.log("Drop skipped: no valid drop target");
      return;
    }

    if (draggedIndex !== dropIndex && draggedIndex >= 0 && dropIndex >= 0 && draggedIndex < tabOrder.length && dropIndex < tabOrder.length) {
      const newOrder = [...tabOrder];
      const [draggedItem] = newOrder.splice(draggedIndex, 1);
      newOrder.splice(dropIndex, 0, draggedItem);
      setTabOrder(newOrder);

      const selectedIndex = selectedValue-startTabs.length
      if (draggedIndex === selectedIndex)
        setSelectedValue(dropIndex+startTabs.length)
      else if (draggedIndex > selectedIndex && dropIndex <= selectedIndex) 
        setSelectedValue(selectedValue+1)
      else if (draggedIndex < selectedIndex && dropIndex >= selectedIndex)
        setSelectedValue(selectedValue-1)

    } else {
      console.log("Drop skipped: invalid indices or same position");
    }
    setHoveredIndex(-1); // Reset hovered index
  };

  const enhancedChildren = React.Children.map(initialChildren, (child, originalIndex) => {
    if (React.isValidElement(child)) {
      const currentIndex = tabOrder.indexOf(originalIndex);
      return React.cloneElement(child, {
        ...child.props,
        draggable: true,
        onDragStart: (e: React.DragEvent<HTMLDivElement>) => handleDragStart(e, currentIndex),
        onDragOver: (e: React.DragEvent<HTMLDivElement>) => handleDragOver(e, currentIndex),
        onDrop: handleDrop,
        style: { 
          ...(hoveredIndex === currentIndex && {
            background: "rgba(150, 150, 150, 0.1)",
            border: "1px double blue",
            borderStyle: "hidden hidden hidden dotted",
          }),
        },
      });
    }
    return child;
  }) || [];

  const orderedChildren = tabOrder.map((index) => enhancedChildren[index]);
  const allTabs = [...startTabs, ...orderedChildren, ...endTabs];

  // return <AnimatedTabs {...tabsProps} value={selectedValue}>{allTabs}</AnimatedTabs>;
  return <Tabs {...tabsProps} value={selectedValue}>{allTabs}</Tabs>;
}