import React, {
  useEffect,
  useState,
  createContext,
  useContext,
  useMemo,
  forwardRef,
} from 'react';
import type { VariantProps } from '@gluestack-ui/nativewind-utils';
import { View, Dimensions, Platform, ViewProps } from 'react-native';
import { gridStyle, gridItemStyle } from './styles';
import { cssInterop } from 'nativewind';
import {
  useBreakpointValue,
  getBreakPointValue,
} from '@/components/ui/utils/use-break-point-value';

const { width: DEVICE_WIDTH } = Dimensions.get('window');

const GridContext = createContext<any>({});

function arrangeChildrenIntoRows({
  childrenArray,
  colSpanArr,
  numColumns,
}: {
  childrenArray: React.ReactNode[];
  colSpanArr: number[];
  numColumns: number;
}) {
  let currentRow = 1;
  let currentRowTotalColSpan = 0;

  // store how many items in each row
  const rowItemsCount: {
    [key: number]: number[];
  } = {};

  for (let i = 0; i < childrenArray.length; i++) {
    const colSpan = colSpanArr[i];

    // if current row is full, go to next row
    if (currentRowTotalColSpan + colSpan > numColumns) {
      currentRow++;
      currentRowTotalColSpan = colSpan;
    } else {
      // if current row is not full, add colSpan to current row
      currentRowTotalColSpan += colSpan;
    }

    rowItemsCount[currentRow] = rowItemsCount[currentRow]
      ? [...rowItemsCount[currentRow], i]
      : [i];
  }

  return rowItemsCount;
}

function generateResponsiveNumColumns({ gridClass }: { gridClass: string }) {
  const gridClassNamePattern = /\b(?:\w+:)?grid-cols-?\d+\b/g;
  const numColumns = gridClass?.match(gridClassNamePattern);

  if (!numColumns) {
    return 12;
  }

  const regex = /^(?:(\w+):)?grid-cols-?(\d+)$/;
  const result: any = {};

  numColumns.forEach((classname) => {
    const match = classname.match(regex);
    if (match) {
      const prefix = match[1] || 'default';
      const value = parseInt(match[2], 10);
      result[prefix] = value;
    }
  });

  return result;
}

function generateResponsiveColSpans({
  gridItemClassName,
}: {
  gridItemClassName: string;
}) {
  const gridClassNamePattern = /\b(?:\w+:)?col-span-?\d+\b/g;

  const colSpan: any = gridItemClassName?.match(gridClassNamePattern);

  if (!colSpan) {
    return 1;
  }

  const regex = /^(?:(\w+):)?col-span-?(\d+)$/;
  const result: any = {};

  colSpan.forEach((classname: any) => {
    const match = classname.match(regex);
    if (match) {
      const prefix = match[1] || 'default';
      const value = parseInt(match[2], 10);
      result[prefix] = value;
    }
  });

  return result;
}

type IGridProps = ViewProps &
  VariantProps<typeof gridStyle> & {
    gap?: number;
    rowGap?: number;
    columnGap?: number;
    flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
    padding?: number;
    paddingLeft?: number;
    paddingRight?: number;
    paddingStart?: number;
    paddingEnd?: number;
    borderWidth?: number;
    borderLeftWidth?: number;
    borderRightWidth?: number;
    _extra: {
      className: string;
    };
  };

const Grid = forwardRef<React.ComponentRef<typeof View>, IGridProps>(
  function Grid({ className, _extra, children, ...props }, ref) {
    const [calculatedWidth, setCalculatedWidth] = useState<number | null>(null);

    const gridClass = _extra?.className;
    const obj = generateResponsiveNumColumns({ gridClass });
    const responsiveNumColumns: any = useBreakpointValue(obj);

    const itemsPerRow = useMemo(() => {
      // get the colSpan of each child
      const colSpanArr = React.Children.map(children, (child: any) => {
        const gridItemClassName = child?.props?._extra?.className;

        const colSpan2 = getBreakPointValue(
          generateResponsiveColSpans({ gridItemClassName }),
          DEVICE_WIDTH
        );
        const colSpan = colSpan2 ? colSpan2 : 1;

        if (colSpan > responsiveNumColumns) {
          return responsiveNumColumns;
        }

        return colSpan;
      });

      const childrenArray = React.Children.toArray(children);

      const rowItemsCount = arrangeChildrenIntoRows({
        childrenArray,
        colSpanArr,
        numColumns: responsiveNumColumns,
      });

      return rowItemsCount;
    }, [responsiveNumColumns, children]);

    const childrenWithProps = React.Children.map(children, (child, index) => {
      if (React.isValidElement(child)) {
        return React.cloneElement(child, { key: index });
      }

      return child;
    });

    const gridClassMerged = `${Platform.select({
      web: gridClass ?? '',
    })}`;

    const contextValue = useMemo(() => {
      return {
        calculatedWidth,
        numColumns: responsiveNumColumns,
        itemsPerRow,
        flexDirection: props?.flexDirection || 'row',
        gap: props?.gap || 0,
        columnGap: props?.columnGap || 0,
      };
    }, [calculatedWidth, itemsPerRow, responsiveNumColumns, props]);

    const borderLeftWidth = props?.borderLeftWidth || props?.borderWidth || 0;
    const borderRightWidth = props?.borderRightWidth || props?.borderWidth || 0;
    const borderWidthToSubtract = borderLeftWidth + borderRightWidth;

    return (
      <GridContext.Provider value={contextValue}>
        <View
          ref={ref}
          className={gridStyle({
            class: className + ' ' + gridClassMerged,
          })}
          onLayout={(event) => {
            const paddingLeftToSubtract =
              props?.paddingStart || props?.paddingLeft || props?.padding || 0;

            const paddingRightToSubtract =
              props?.paddingEnd || props?.paddingRight || props?.padding || 0;

            const gridWidth =
              event.nativeEvent.layout.width -
              paddingLeftToSubtract -
              paddingRightToSubtract -
              borderWidthToSubtract;

            setCalculatedWidth(gridWidth);
          }}
          {...props}
        >
          {calculatedWidth && childrenWithProps}
        </View>
      </GridContext.Provider>
    );
  }
);

cssInterop(Grid, {
  className: {
    target: 'style',
    nativeStyleToProp: {
      gap: 'gap',
      rowGap: 'rowGap',
      columnGap: 'columnGap',
      flexDirection: 'flexDirection',
      padding: 'padding',
      paddingLeft: 'paddingLeft',
      paddingRight: 'paddingRight',
      paddingStart: 'paddingStart',
      paddingEnd: 'paddingEnd',
      borderWidth: 'borderWidth',
      borderLeftWidth: 'borderLeftWidth',
      borderRightWidth: 'borderRightWidth',
    },
  },
});

type IGridItemProps = ViewProps &
  VariantProps<typeof gridItemStyle> & {
    index?: number;
    _extra: {
      className: string;
    };
  };

const GridItem = forwardRef<React.ComponentRef<typeof View>, IGridItemProps>(
  function GridItem({ className, _extra, ...props }, ref) {
    const [flexBasisValue, setFlexBasisValue] = useState<
      number | string | null
    >('auto');

    const {
      calculatedWidth,
      numColumns,
      itemsPerRow,
      flexDirection,
      gap,
      columnGap,
    } = useContext(GridContext);

    const gridItemClass = _extra?.className;
    const responsiveColSpan = (useBreakpointValue(
      generateResponsiveColSpans({ gridItemClassName: gridItemClass })
    ) ?? 1) as number;

    useEffect(() => {
      if (
        !flexDirection?.includes('column') &&
        calculatedWidth &&
        numColumns > 0 &&
        responsiveColSpan > 0
      ) {
        // find out in which row of itemsPerRow the current item's index is
        const row = Object.keys(itemsPerRow).find((key) => {
          return itemsPerRow[key].includes(props?.index);
        });

        const rowColsCount = itemsPerRow[row as string]?.length;

        const space = columnGap || gap || 0;

        const gutterOffset =
          space *
          (rowColsCount === 1 && responsiveColSpan < numColumns
            ? 2
            : rowColsCount - 1);

        const flexBasisVal =
          Math.min(
            (((calculatedWidth - gutterOffset) * responsiveColSpan) /
              numColumns /
              calculatedWidth) *
              100,
            100
          ) + '%';

        setFlexBasisValue(flexBasisVal);
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [
      calculatedWidth,
      responsiveColSpan,
      numColumns,
      columnGap,
      gap,
      flexDirection,
    ]);

    return (
      <View
        ref={ref}
        // @ts-expect-error : internal implementation for r-19/react-native-web
        gridItemClass={gridItemClass}
        className={gridItemStyle({
          class: className,
        })}
        {...props}
        style={[
          {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            flexBasis: flexBasisValue as any,
          },
          props.style,
        ]}
      />
    );
  }
);

Grid.displayName = 'Grid';
GridItem.displayName = 'GridItem';

export { Grid, GridItem };
