import "../../DataGrid.css";
import { jsxs, jsx } from "react/jsx-runtime";
import DataEditor, { GridCellKind, getMiddleCenterBias, CompactSelection } from "@glideapps/glide-data-grid";
import * as t from "io-ts";
import _ from "lodash";
import { observable, useObservable } from "micro-observables";
import React__default, { useRef, useState, useCallback, useEffect, useMemo, useImperativeHandle } from "react";
import { useTheme } from "../Theme/UI.js";
import "../Theme/themeUtils.js";
import "../Theme/themes.js";
import useMobile from "../useMobile.js";
import { Loadable } from "../utils/loadable.js";
import { MIN_COLUMN_WIDTH } from "./columns.js";
import { ContextMenu } from "./contextMenu.js";
import { customRenderers } from "./custom-renderers/index.js";
import { getHeaderIcons } from "./icons.js";
import { HeaderMenu } from "./menu.js";
import { useTableTooltip } from "./tooltip.js";
import "@glideapps/glide-data-grid/dist/index.css";
import { LOADING_CELL } from "./custom-renderers/cells/loadingCell.js";
import { drawArrow, drawTextWithEllipsis } from "./custom-renderers/utils.js";
import { getCheckboxDimensions } from "./custom-renderers/cells/checkboxCell.js";
const base = "DataGrid_base_26f12";
const horizontalScrollDisabled = "DataGrid_horizontalScrollDisabled_26f12";
const wrapper = "DataGrid_wrapper_26f12";
const css = {
  base,
  horizontalScrollDisabled,
  wrapper
};
const directionType = t.keyof({ asc: null, desc: null });
const validSort = t.type({
  column: t.string,
  direction: directionType
});
t.partial(validSort.props);
const getTheme = (appTheme) => {
  return {
    accentLight: appTheme.float,
    bgBubble: appTheme.ixStrong,
    bgCell: appTheme.surface,
    bgHeader: appTheme.surface,
    bgHeaderHovered: appTheme.surfaceStrong,
    borderColor: appTheme.ixBorder,
    fontFamily: appTheme.fontFamily,
    headerBottomBorderColor: appTheme.ixBorder,
    headerFontStyle: "normal 12px",
    linkColor: appTheme.surfaceOn,
    textBubble: appTheme.surfaceBorderStrong,
    textDark: appTheme.surfaceOnWeak,
    textHeader: appTheme.surfaceOnWeak
  };
};
const SCROLL_SET_COUNT_NEEDED = 1;
const isLinkCell = (cell) => {
  var _a, _b;
  return !!((_b = (_a = cell.data) == null ? void 0 : _a.link) == null ? void 0 : _b.href);
};
function DataGrid({
  columns,
  data,
  getHeaderMenuItems,
  getRowAccentColor,
  hideUnpinned = false,
  height = "100%",
  onColumnResize,
  onContextMenuComplete,
  onPinnedColumnsCountChange,
  onSelectionChange,
  onColumnsOrderChange,
  infiniteScroll = false,
  page,
  pageSize,
  onPageUpdate,
  pinnedColumnsCount = 0,
  renderContextMenuComponent,
  rowHeight = 36,
  selection = {
    columns: CompactSelection.empty(),
    rows: CompactSelection.empty()
  },
  total,
  imperativeRef,
  sorts = [],
  staticColumns,
  smoothScrollX = true,
  smoothScrollY = true
}) {
  const gridRef = useRef(null);
  const clickedCellRef = useRef(null);
  const [hoveredRow, setHoveredRow] = useState();
  const [scrollPositionSetCount] = useState(observable(0));
  const onScroll = useCallback(
    ({ y, height: height2 }) => {
      if (!infiniteScroll || _.isUndefined(pageSize) || scrollPositionSetCount.get() < SCROLL_SET_COUNT_NEEDED)
        return;
      onPageUpdate == null ? void 0 : onPageUpdate(Math.floor((y + height2) / pageSize));
    },
    [scrollPositionSetCount, onPageUpdate, pageSize, infiniteScroll]
  );
  useEffect(() => {
    if (scrollPositionSetCount.get() >= SCROLL_SET_COUNT_NEEDED)
      return;
    if (gridRef.current !== null && !_.isUndefined(page) && !_.isUndefined(pageSize)) {
      const rowOffset = Math.max(page * pageSize, 0);
      const bounds = gridRef.current.getBounds(0, rowOffset);
      if (bounds && !Number.isNaN(bounds.x)) {
        gridRef.current.scrollTo(0, rowOffset);
        scrollPositionSetCount.update((x) => x + 1);
      }
    }
  });
  const [headerMenuIsOpen, setHeaderMenuIsOpen] = useState(false);
  const [headerMenuProps, setHeaderMenuProps] = useState({
    bounds: {
      height: 0,
      width: 0,
      x: 0,
      y: 0
    },
    handleClose: () => setHeaderMenuIsOpen(false)
  });
  const [contextMenuOpen] = useState(observable(false));
  const contextMenuIsOpen = useObservable(contextMenuOpen);
  const [contextMenuProps, setContextMenuProps] = useState(null);
  const {
    getThemeVar,
    themeSettings: { theme: appTheme }
  } = useTheme();
  const theme = getTheme(appTheme);
  const isMobile = useMobile();
  const headerIcons = useMemo(() => getHeaderIcons(), [appTheme]);
  const { tooltip, onItemHovered, closeTooltip } = useTableTooltip({
    columns,
    data
  });
  const scrollToTop = useCallback(() => {
    var _a;
    (_a = gridRef.current) == null ? void 0 : _a.scrollTo(0, 0);
  }, []);
  const getRowThemeOverride = React__default.useCallback(
    (row) => {
      if (row === data.length)
        return;
      if (!data[row]) {
        return { borderColor: getThemeVar("surface") };
      }
      const hoverStyle = {};
      if (row === hoveredRow) {
        hoverStyle.bgCell = getThemeVar("surfaceStrong");
        if (selection.rows.toArray().includes(hoveredRow)) {
          hoverStyle.accentLight = getThemeVar("floatStrong");
        }
      }
      const rowColorTheme = Loadable.match(data[row], {
        _: () => ({}),
        Loaded: (record) => {
          const accentColor = getRowAccentColor == null ? void 0 : getRowAccentColor(record);
          return accentColor ? { accentColor } : {};
        }
      });
      return { ...rowColorTheme, ...hoverStyle };
    },
    [getRowAccentColor, data, getThemeVar, hoveredRow, selection.rows]
  );
  const handleColumnResize = useCallback(
    (column, width) => {
      const columnId = column.id;
      if (columnId === void 0)
        return;
      onColumnResize == null ? void 0 : onColumnResize(columnId, width);
    },
    [onColumnResize]
  );
  const onHeaderClicked = React__default.useCallback(
    (col, { bounds, preventDefault }) => {
      preventDefault();
      const columnId = columns[col].id;
      const items = getHeaderMenuItems == null ? void 0 : getHeaderMenuItems(columnId, col);
      if (items == null ? void 0 : items.length) {
        setHeaderMenuProps((prev) => ({ ...prev, bounds, items, title: `${columnId} menu` }));
        setHeaderMenuIsOpen(true);
      }
    },
    [columns, getHeaderMenuItems]
  );
  const getCellContent = React__default.useCallback(
    (cell) => {
      const [col, row] = cell;
      const loadingCell = {
        allowOverlay: true,
        copyData: "",
        data: { appTheme, kind: LOADING_CELL },
        kind: GridCellKind.Custom,
        readonly: true
      };
      if (!data[row]) {
        return loadingCell;
      }
      return Loadable.match(data[row], {
        _: () => loadingCell,
        Loaded: (rowData) => {
          var _a, _b;
          let cell2 = (_b = (_a = columns[col]) == null ? void 0 : _a.renderer) == null ? void 0 : _b.call(_a, rowData, row);
          if (cell2 && (cell2.kind === GridCellKind.Text || cell2.kind === GridCellKind.Number) && !cell2.displayData) {
            cell2 = {
              ...cell2,
              displayData: "-",
              themeOverride: {
                ...cell2.themeOverride,
                textDark: getThemeVar("surfaceOnWeak")
              }
            };
          }
          return cell2 || loadingCell;
        }
        // TODO correctly handle error state
      });
    },
    [appTheme, data, columns, getThemeVar]
  );
  const onCellClicked = useCallback(
    (cell, event) => {
      const [col, row] = cell;
      Loadable.forEach(data[row], (rowData) => {
        var _a, _b;
        const cell2 = columns[col].renderer(rowData, row);
        if (isLinkCell(cell2)) {
          (_b = (_a = cell2.data).onClick) == null ? void 0 : _b.call(_a, event);
        } else {
          if (event.shiftKey) {
            if (clickedCellRef.current !== null) {
              const previousRow = clickedCellRef.current.row;
              const selectionType = selection.rows.toArray().includes(row) ? "remove" : "add";
              const range = previousRow < row ? [previousRow, row + 1] : [row, previousRow + 1];
              onSelectionChange == null ? void 0 : onSelectionChange(selectionType, range);
            }
          } else {
            if (selection.rows.hasIndex(row)) {
              onSelectionChange == null ? void 0 : onSelectionChange("remove", [row, row + 1]);
            } else {
              onSelectionChange == null ? void 0 : onSelectionChange("add", [row, row + 1]);
            }
          }
          clickedCellRef.current = { col, row };
        }
      });
    },
    [data, columns, onSelectionChange, selection]
  );
  const onCellContextMenu = useCallback(
    (cell, event) => {
      const [col, row] = cell;
      contextMenuOpen.set(false);
      Loadable.forEach(data[row], (rowData) => {
        event.preventDefault();
        setTimeout(() => {
          const cell2 = columns[col].renderer(rowData, row);
          setContextMenuProps({
            cell: cell2,
            link: isLinkCell(cell2) ? cell2.data.link.href : void 0,
            onClose: (e) => {
              if (contextMenuOpen.get())
                e == null ? void 0 : e.stopPropagation();
              contextMenuOpen.set(false);
            },
            renderContextMenuComponent,
            rowData,
            x: Math.max(0, event.bounds.x + event.localEventX - 4),
            y: Math.max(0, event.bounds.y + event.localEventY - 4)
          });
          contextMenuOpen.set(true);
        }, 50);
      });
    },
    [columns, data, renderContextMenuComponent, setContextMenuProps, contextMenuOpen]
  );
  const onColumnMoved = useCallback(
    (columnIdsStartIdx, columnIdsEndIdx) => {
      if (columnIdsStartIdx < staticColumns.length)
        return;
      const pinnedColumnEnd = staticColumns.length + pinnedColumnsCount;
      const isIntoPinned = columnIdsStartIdx >= pinnedColumnEnd && columnIdsEndIdx < pinnedColumnEnd;
      const isOutOfPinned = columnIdsStartIdx < pinnedColumnEnd && columnIdsEndIdx >= pinnedColumnEnd;
      if (isIntoPinned)
        onPinnedColumnsCountChange == null ? void 0 : onPinnedColumnsCountChange(pinnedColumnsCount + 1);
      if (isOutOfPinned)
        onPinnedColumnsCountChange == null ? void 0 : onPinnedColumnsCountChange(pinnedColumnsCount - 1);
      const columnsOrder = columns.flatMap((c) => {
        if (staticColumns.includes(c.id))
          return [];
        return [c.id];
      });
      const columnsOrderStartIdx = columnIdsStartIdx - staticColumns.length;
      const columnsOrderEndIdx = Math.max(columnIdsEndIdx - staticColumns.length, 0);
      const newCols = [...columnsOrder];
      const [toMove] = newCols.splice(columnsOrderStartIdx, 1);
      newCols.splice(columnsOrderEndIdx, 0, toMove);
      onColumnsOrderChange == null ? void 0 : onColumnsOrderChange(newCols);
    },
    [onPinnedColumnsCountChange, onColumnsOrderChange, pinnedColumnsCount, columns, staticColumns]
  );
  const onColumnHovered = useCallback(
    (args) => {
      const [, row] = args.location;
      setHoveredRow(args.kind !== "cell" ? void 0 : row);
      onItemHovered == null ? void 0 : onItemHovered(args);
    },
    [onItemHovered]
  );
  const verticalBorder = useCallback(
    (col) => !hideUnpinned && col === staticColumns.length + pinnedColumnsCount,
    [hideUnpinned, pinnedColumnsCount, staticColumns.length]
  );
  const sortMap = useMemo(() => {
    return sorts.reduce(
      (acc, sort2) => {
        if (sort2.column && sort2.direction)
          acc[sort2.column] = sort2.direction;
        return acc;
      },
      {}
    );
  }, [sorts]);
  const drawHeader = useCallback(
    ({ ctx, column, rect, theme: theme2, spriteManager }) => {
      const sortDirection = column.id && sortMap[column.id];
      if (sortDirection) {
        const arrowDirection = sortDirection === "asc" ? "up" : "down";
        ctx.strokeStyle = theme2.textLight;
        drawArrow(ctx, arrowDirection, rect.x + rect.width - 16, 12);
      }
      if (column.icon === "allSelected" || column.icon === "noneSelected" || column.icon === "someSelected") {
        const checkbox = getCheckboxDimensions(rect.x, rect.y, rect.width, rect.height);
        spriteManager.drawSprite(
          column.icon,
          "normal",
          ctx,
          checkbox.x - 0.8,
          checkbox.y,
          checkbox.size,
          theme2
        );
      } else if (column.title) {
        const xPad = theme2.cellHorizontalPadding;
        const font = `${theme2.baseFontStyle} ${theme2.fontFamily}`;
        const middleCenterBias = getMiddleCenterBias(ctx, font);
        const x = rect.x + xPad;
        const y = rect.y + rect.height / 2 + middleCenterBias;
        const maxWidth = rect.width - (sortDirection ? 12 : 0) - 2 * theme2.cellHorizontalPadding;
        ctx.fillStyle = theme2.textHeader;
        drawTextWithEllipsis(ctx, column.title, x, y, maxWidth);
      }
    },
    [sortMap]
  );
  useImperativeHandle(
    imperativeRef,
    () => {
      return {
        gridRef: gridRef.current ?? void 0,
        scrollToTop
        // using gridRef.scrollTo directly adds an offset
      };
    },
    [gridRef, scrollToTop]
  );
  return /* @__PURE__ */ jsxs(
    "div",
    {
      className: css.wrapper,
      onWheel: () => {
        contextMenuOpen.set(false);
        closeTooltip();
      },
      children: [
        tooltip,
        /* @__PURE__ */ jsx("div", { className: css.base, children: /* @__PURE__ */ jsx(
          DataEditor,
          {
            className: hideUnpinned ? css.horizontalScrollDisabled : void 0,
            columns,
            customRenderers,
            drawHeader,
            freezeColumns: isMobile ? 0 : staticColumns.length + pinnedColumnsCount,
            getCellContent,
            getCellsForSelection: true,
            getRowThemeOverride,
            gridSelection: selection,
            headerHeight: rowHeight,
            headerIcons,
            height,
            minColumnWidth: MIN_COLUMN_WIDTH,
            ref: gridRef,
            rowHeight,
            rows: infiniteScroll ? total ?? pageSize ?? data.length : data.length,
            smoothScrollX,
            smoothScrollY,
            theme,
            verticalBorder,
            width: "100%",
            onCellClicked,
            onCellContextMenu: renderContextMenuComponent ? onCellContextMenu : void 0,
            onColumnMoved: onColumnsOrderChange ? onColumnMoved : void 0,
            onColumnResize: onColumnResize ? handleColumnResize : void 0,
            onHeaderClicked,
            onHeaderContextMenu: onHeaderClicked,
            onItemHovered: onColumnHovered,
            onVisibleRegionChanged: onScroll
          }
        ) }),
        /* @__PURE__ */ jsx(HeaderMenu, { ...headerMenuProps, open: headerMenuIsOpen }),
        contextMenuProps && /* @__PURE__ */ jsx(
          ContextMenu,
          {
            ...contextMenuProps,
            open: contextMenuIsOpen,
            onComplete: onContextMenuComplete
          }
        )
      ]
    }
  );
}
export {
  DataGrid,
  DataGrid as default,
  getTheme,
  validSort
};
