import {
  DataTable as UiDataTable,
  useDataTable,
  DataTableColumnDef,
  DataTableCommand,
  DataTableEmptyStateProps,
  DataTableFilter,
  DataTableRow,
  DataTableRowSelectionState,
  Heading,
  Text,
  Button,
  DataTableFilteringState,
  DataTablePaginationState,
  DataTableSortingState,
  Tooltip,
} from "@medusajs/ui"
import React, { ReactNode, useCallback, useMemo } from "react"
import { useTranslation } from "react-i18next"
import { Link, useNavigate, useSearchParams } from "react-router-dom"

import { useQueryParams } from "../../hooks/use-query-params"
import { ActionMenu } from "../common/action-menu"
import { ViewPills } from "../table/view-selector"
import { useFeatureFlag } from "../../providers/feature-flag-provider"

// Types for column visibility and ordering
type VisibilityState = Record<string, boolean>
type ColumnOrderState = string[]

type DataTableActionProps = {
  label: string
  disabled?: boolean
  tooltip?: ReactNode
} & (
  | {
      to: string
    }
  | {
      onClick: () => void
    }
)

type DataTableActionMenuActionProps = {
  label: string
  icon: ReactNode
  disabled?: boolean
} & (
  | {
      to: string
    }
  | {
      onClick: () => void
    }
)

type DataTableActionMenuGroupProps = {
  actions: DataTableActionMenuActionProps[]
}

type DataTableActionMenuProps = {
  groups: DataTableActionMenuGroupProps[]
}

interface DataTableProps<TData> {
  data?: TData[]
  columns: DataTableColumnDef<TData, any>[]
  filters?: DataTableFilter[]
  commands?: DataTableCommand[]
  action?: DataTableActionProps
  actions?: DataTableActionProps[]
  actionMenu?: DataTableActionMenuProps
  rowCount?: number
  getRowId: (row: TData) => string
  enablePagination?: boolean
  enableSearch?: boolean
  autoFocusSearch?: boolean
  rowHref?: (row: TData) => string
  emptyState?: DataTableEmptyStateProps
  heading?: string
  headingLevel?: "h1" | "h2" | "h3"
  subHeading?: string
  prefix?: string
  pageSize?: number
  isLoading?: boolean
  rowSelection?: {
    state: DataTableRowSelectionState
    onRowSelectionChange: (value: DataTableRowSelectionState) => void
    enableRowSelection?: boolean | ((row: DataTableRow<TData>) => boolean)
  }
  layout?: "fill" | "auto"
  enableColumnVisibility?: boolean
  initialColumnVisibility?: VisibilityState
  onColumnVisibilityChange?: (visibility: VisibilityState) => void
  columnOrder?: ColumnOrderState
  onColumnOrderChange?: (order: ColumnOrderState) => void
  enableViewSelector?: boolean
  entity?: string
  currentColumns?: {
    visible: string[]
    order: string[]
  }
  filterBarContent?: React.ReactNode
}

export const DataTable = <TData,>({
  data = [],
  columns,
  filters,
  commands,
  action,
  actions,
  actionMenu,
  getRowId,
  rowCount = 0,
  enablePagination = true,
  enableSearch = true,
  autoFocusSearch = false,
  rowHref,
  heading,
  headingLevel = "h1",
  subHeading,
  prefix,
  pageSize = 10,
  emptyState,
  rowSelection,
  isLoading = false,
  layout = "auto",
  enableColumnVisibility = false,
  initialColumnVisibility = {},
  onColumnVisibilityChange,
  columnOrder,
  onColumnOrderChange,
  enableViewSelector = false,
  entity,
  currentColumns,
  filterBarContent,
}: DataTableProps<TData>) => {
  const { t } = useTranslation()
  const isViewConfigEnabled = useFeatureFlag("view_configurations")

  // If view config is disabled, don't use column visibility features
  const effectiveEnableColumnVisibility =
    isViewConfigEnabled && enableColumnVisibility
  const effectiveEnableViewSelector = isViewConfigEnabled && enableViewSelector

  const enableFiltering = filters && filters.length > 0
  const enableCommands = commands && commands.length > 0
  const enableSorting = columns.some((column) => column.enableSorting)

  // Reserve the filter bar row whenever it can gain content — active filter
  // chips, or the view-config save controls that appear on sort/column changes
  // — so toggling those doesn't shift the layout.
  const alwaysShowFilterBar = Boolean(
    enableFiltering || effectiveEnableColumnVisibility
  )

  const [columnVisibility, setColumnVisibility] =
    React.useState<VisibilityState>(initialColumnVisibility)

  // Update column visibility when initial visibility changes
  React.useEffect(() => {
    // Deep compare to check if the visibility has actually changed
    const currentKeys = Object.keys(columnVisibility).sort()
    const newKeys = Object.keys(initialColumnVisibility).sort()

    const hasChanged =
      currentKeys.length !== newKeys.length ||
      currentKeys.some((key, index) => key !== newKeys[index]) ||
      Object.entries(initialColumnVisibility).some(
        ([key, value]) => columnVisibility[key] !== value
      )

    if (hasChanged) {
      setColumnVisibility(initialColumnVisibility)
    }
  }, [initialColumnVisibility])

  // Wrapper function to handle column visibility changes
  const handleColumnVisibilityChange = React.useCallback(
    (visibility: VisibilityState) => {
      setColumnVisibility(visibility)
      onColumnVisibilityChange?.(visibility)
    },
    [onColumnVisibilityChange]
  )

  // Extract filter IDs for query param management
  const filterIds = useMemo(() => filters?.map((f) => f.id) ?? [], [filters])
  const prefixedFilterIds = filterIds.map((id) => getQueryParamKey(id, prefix))

  const { offset, order, q, ...filterParams } = useQueryParams(
    [
      ...filterIds,
      ...(enableSorting ? ["order"] : []),
      ...(enableSearch ? ["q"] : []),
      ...(enablePagination ? ["offset"] : []),
    ],
    prefix
  )
  const [_, setSearchParams] = useSearchParams()

  const search = useMemo(() => {
    return q ?? ""
  }, [q])

  const handleSearchChange = (value: string) => {
    setSearchParams((prev) => {
      if (value) {
        prev.set(getQueryParamKey("q", prefix), value)
      } else {
        prev.delete(getQueryParamKey("q", prefix))
      }

      return prev
    })
  }

  const pagination: DataTablePaginationState = useMemo(() => {
    return offset
      ? parsePaginationState(offset, pageSize)
      : { pageIndex: 0, pageSize }
  }, [offset, pageSize])

  const handlePaginationChange = (value: DataTablePaginationState) => {
    setSearchParams((prev) => {
      if (value.pageIndex === 0) {
        prev.delete(getQueryParamKey("offset", prefix))
      } else {
        prev.set(
          getQueryParamKey("offset", prefix),
          transformPaginationState(value).toString()
        )
      }
      return prev
    })
  }

  const filtering: DataTableFilteringState = useMemo(
    () => parseFilterState(filterIds, filterParams),
    [filterIds, filterParams]
  )

  const handleFilteringChange = (value: DataTableFilteringState) => {
    setSearchParams((prev) => {
      // Remove filters that are no longer in the state
      Array.from(prev.keys()).forEach((key) => {
        if (prefixedFilterIds.includes(key)) {
          // Extract the unprefixed key
          const unprefixedKey = prefix ? key.replace(`${prefix}_`, "") : key
          if (!(unprefixedKey in value)) {
            prev.delete(key)
          }
        }
      })

      // Add or update filters in the state
      Object.entries(value).forEach(([key, filter]) => {
        const prefixedKey = getQueryParamKey(key, prefix)
        if (filter !== undefined) {
          prev.set(prefixedKey, JSON.stringify(filter))
        } else {
          prev.delete(prefixedKey)
        }
      })

      return prev
    })
  }

  const sorting: DataTableSortingState | null = useMemo(() => {
    return order ? parseSortingState(order) : null
  }, [order])

  // Memoize current configuration to prevent infinite loops
  const currentConfiguration = useMemo(
    () => ({
      filters: filtering,
      sorting: sorting,
      search: search,
    }),
    [filtering, sorting, search]
  )

  const handleSortingChange = (value: DataTableSortingState) => {
    setSearchParams((prev) => {
      if (value) {
        const valueToStore = transformSortingState(value)

        prev.set(getQueryParamKey("order", prefix), valueToStore)
      } else {
        prev.delete(getQueryParamKey("order", prefix))
      }

      return prev
    })
  }

  const { pagination: paginationTranslations, toolbar: toolbarTranslations } =
    useDataTableTranslations()

  const navigate = useNavigate()

  const onRowClick = useCallback(
    (event: React.MouseEvent<HTMLTableRowElement, MouseEvent>, row: TData) => {
      if (!rowHref) {
        return
      }

      const href = rowHref(row)
      const basePath = __BASE__ || "/"
      const hrefWithBasePath = `${basePath === "/" ? "" : basePath}${href}`

      if (event.metaKey || event.ctrlKey || event.button === 1) {
        window.open(hrefWithBasePath, "_blank", "noreferrer")
        return
      }

      if (event.shiftKey) {
        window.open(hrefWithBasePath, undefined, "noreferrer")
        return
      }

      navigate(href)
    },
    [navigate, rowHref]
  )

  const instance = useDataTable({
    data,
    columns,
    filters,
    commands,
    rowCount,
    getRowId,
    onRowClick: rowHref ? onRowClick : undefined,
    pagination: enablePagination
      ? {
          state: pagination,
          onPaginationChange: handlePaginationChange,
        }
      : undefined,
    filtering: enableFiltering
      ? {
          state: filtering,
          onFilteringChange: handleFilteringChange,
        }
      : undefined,
    sorting: enableSorting
      ? {
          state: sorting,
          onSortingChange: handleSortingChange,
        }
      : undefined,
    search: enableSearch
      ? {
          state: search,
          onSearchChange: handleSearchChange,
        }
      : undefined,
    rowSelection,
    isLoading,
    columnVisibility: effectiveEnableColumnVisibility
      ? {
          state: columnVisibility,
          onColumnVisibilityChange: handleColumnVisibilityChange,
        }
      : undefined,
    columnOrder:
      effectiveEnableColumnVisibility && columnOrder && onColumnOrderChange
        ? {
            state: columnOrder,
            onColumnOrderChange: onColumnOrderChange,
          }
        : undefined,
  })

  const shouldRenderHeading = heading || subHeading

  return (
    <UiDataTable
      instance={instance}
      className={
        layout === "fill" ? "h-full [&_tr]:last-of-type:!border-b" : undefined
      }
    >
      <UiDataTable.Toolbar
        className="flex flex-col items-start justify-between gap-2 md:flex-row md:items-center"
        translations={toolbarTranslations}
        filterBarContent={filterBarContent}
        alwaysShowFilterBar={alwaysShowFilterBar}
      >
        <div className="flex w-full items-center justify-between gap-2">
          <div className="flex items-center gap-x-4">
            {shouldRenderHeading && (
              <div>
                {heading && <Heading level={headingLevel}>{heading}</Heading>}
                {subHeading && (
                  <Text size="small" className="text-ui-fg-subtle">
                    {subHeading}
                  </Text>
                )}
              </div>
            )}
            {effectiveEnableViewSelector && entity && (
              <ViewPills
                entity={entity}
                currentColumns={currentColumns}
                currentConfiguration={currentConfiguration}
              />
            )}
          </div>
          <div className="flex items-center gap-x-2">
            {enableFiltering && (
              <UiDataTable.FilterMenu tooltip={toolbarTranslations.filter} />
            )}
            {enableSorting && (
              <UiDataTable.SortingMenu tooltip={toolbarTranslations.sort} />
            )}
            {effectiveEnableColumnVisibility && (
              <UiDataTable.ColumnVisibilityMenu
                tooltip={toolbarTranslations.columns}
              />
            )}
            {enableSearch && (
              <div className="w-full md:w-auto">
                <UiDataTable.Search
                  placeholder={t("filters.searchLabel")}
                  autoFocus={autoFocusSearch}
                />
              </div>
            )}
            {actionMenu && <ActionMenu variant="primary" {...actionMenu} />}
            {actions && actions.length > 0 && (
              <DataTableActions actions={actions} />
            )}
            {!actions && action && <DataTableAction {...action} />}
          </div>
        </div>
      </UiDataTable.Toolbar>
      <UiDataTable.Table emptyState={emptyState} />
      {enablePagination && (
        <UiDataTable.Pagination translations={paginationTranslations} />
      )}
      {enableCommands && (
        <UiDataTable.CommandBar
          selectedLabel={(count) => `${count} selected`}
        />
      )}
    </UiDataTable>
  )
}

function transformSortingState(value: DataTableSortingState) {
  return value.desc ? `-${value.id}` : value.id
}

function parseSortingState(value: string) {
  return value.startsWith("-")
    ? { id: value.slice(1), desc: true }
    : { id: value, desc: false }
}

function transformPaginationState(value: DataTablePaginationState) {
  return value.pageIndex * value.pageSize
}

function parsePaginationState(value: string, pageSize: number) {
  const offset = parseInt(value)

  return {
    pageIndex: Math.floor(offset / pageSize),
    pageSize,
  }
}

function parseFilterState(
  filterIds: string[],
  value: Record<string, string | undefined>
) {
  if (!value) {
    return {}
  }

  const filters: DataTableFilteringState = {}

  for (const id of filterIds) {
    const filterValue = value[id]

    if (filterValue !== undefined) {
      filters[id] = parseFilterValue(filterValue)
    }
  }

  return filters
}

// Radio/select filters use string option values, but a URL written by hand
// (e.g. `?is_exclusive=false`) JSON-parses to a boolean and no longer matches
// those values, leaving the filter chip unhighlighted. Fall back to the raw
// string in that case so manual URLs still highlight correctly.
function parseFilterValue(raw: string): unknown {
  try {
    const parsed = JSON.parse(raw)
    return typeof parsed === "boolean" ? raw : parsed
  } catch {
    return raw
  }
}

function getQueryParamKey(key: string, prefix?: string) {
  return prefix ? `${prefix}_${key}` : key
}

const useDataTableTranslations = () => {
  const { t } = useTranslation()

  const paginationTranslations = {
    of: t("general.of"),
    results: t("general.results"),
    pages: t("general.pages"),
    prev: t("general.prev"),
    next: t("general.next"),
  }

  const toolbarTranslations = {
    clearAll: t("actions.clearAll"),
    sort: t("filters.sortLabel"),
    filter: t("filters.filterLabel"),
    columns: t("filters.columnsLabel"),
  }

  return {
    pagination: paginationTranslations,
    toolbar: toolbarTranslations,
  }
}

const DataTableAction = ({
  label,
  disabled,
  tooltip,
  ...props
}: DataTableActionProps) => {
  const buttonProps = {
    size: "small" as const,
    disabled: disabled ?? false,
    type: "button" as const,
    variant: "secondary" as const,
  }

  if ("to" in props) {
    const linkButton = disabled ? (
      <Button {...buttonProps}>{label}</Button>
    ) : (
      <Button {...buttonProps} asChild>
        <Link to={props.to}>{label}</Link>
      </Button>
    )

    return tooltip ? (
      <Tooltip content={tooltip}>{linkButton}</Tooltip>
    ) : (
      linkButton
    )
  }

  return tooltip ? (
    <Tooltip content={tooltip}>
      <Button {...buttonProps} onClick={props.onClick}>
        {label}
      </Button>
    </Tooltip>
  ) : (
    <Button {...buttonProps} onClick={props.onClick}>
      {label}
    </Button>
  )
}

const DataTableActions = ({ actions }: { actions: DataTableActionProps[] }) => {
  return (
    <div className="flex items-center gap-x-2">
      {actions.map((action, index) => (
        <DataTableAction key={index} {...action} />
      ))}
    </div>
  )
}
