import { type BaseGridCell, type CellClickedEventArgs, GridCellKind, type GridColumn } from '@glideapps/glide-data-grid'

import type { ContextMenuAction } from '../context-menu-simple'
import { type DataTableColumn } from '../data-table'
import { booleanToLink, type Link, linkToBoolean, linkToString, stringToLink } from '../renderers/links-cell-renderer'

export type YesNo = 'Yes' | 'No'
export type YesNoBlank = 'Yes' | 'No' | 'Blank'

export type ColumnDefinition<Row extends object, ColumnId extends string> = GridColumn & {
  id: ColumnId
  tooltip?: string
  onClick?: (row: Row, event: CellClickedEventArgs) => void
  themeOverride?: (row: Row) => BaseGridCell['themeOverride']
  onContextMenu?: (row: Row, event: CellClickedEventArgs) => ContextMenuAction[]
  isSelected?: (row: Row) => boolean
  sortComparator?: (row1: Row, row2: Row, direction: 'asc' | 'desc' | null) => number
}

// STRING ----------------------------------------------------------------------
export function stringColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => Link | string,
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: definition.title.length * 10,
    sortComparator: (row1, row2, direction) => {
      const value1 = linkToString(lookup(row1))
      const value2 = linkToString(lookup(row2))
      return compare(value1, value2, direction)
    },

    ...definition,
    // We omit the `onClick` property because the custom renderer has its own handler for it.
    onClick: undefined,
    themeOverride: undefined,
    toCell: (item) => ({
      kind: GridCellKind.Custom,
      allowOverlay: true,
      copyData: linkToString(lookup(item)),
      data: {
        kind: 'links-cell',
        underlineOffset: 6,
        links: [stringToLink(lookup(item))],
        onClick: definition.onClick,
        rowData: item,
      },
      themeOverride: definition.themeOverride?.(item),
    }),
  }
}

// STRING ARRAY ----------------------------------------------------------------
export function stringArrayColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => Link[] | string[],
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: definition.title.length * 10,
    sortComparator: (row1, row2, direction) => {
      const value1 = linkToString(lookup(row1)[0]) ?? ''
      const value2 = linkToString(lookup(row2)[0]) ?? ''
      return compare(value1, value2, direction)
    },
    ...definition,
    // We omit the `onClick` property because the custom renderer has its own handler for it.
    onClick: undefined,
    toCell: (item) => ({
      kind: GridCellKind.Custom,
      allowOverlay: true,
      copyData: lookup(item).map(linkToString).join(', '),
      data: {
        kind: 'links-cell',
        underlineOffset: 6,
        links: lookup(item).map(stringToLink),
        onClick: definition.onClick,
        rowData: item,
      },
      themeOverride: definition.themeOverride?.(item),
    }),
  }
}

// NUMBER ----------------------------------------------------------------------

export function numberColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => number,
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: definition.title.length * 10,
    sortComparator: (row1, row2, direction) => compare(lookup(row1), lookup(row2), direction),
    ...definition,
    toCell: (item) => ({
      contentAlign: 'right',
      kind: GridCellKind.Text,
      data: lookup(item).toLocaleString(),
      allowOverlay: false,
      displayData: lookup(item).toLocaleString(),
      themeOverride: { cellHorizontalPadding: 0, ...definition.themeOverride?.(item) },
    }),
  }
}

export function optionalNumberColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => number | undefined | null,
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: definition.title.length * 10,
    sortComparator: (row1, row2, direction) => compare(lookup(row1), lookup(row2), direction),
    ...definition,
    toCell: (item) => ({
      contentAlign: 'right',
      kind: GridCellKind.Text,
      data: lookup(item)?.toLocaleString() ?? '',
      allowOverlay: false,
      displayData: lookup(item)?.toLocaleString() ?? '',
      themeOverride: { cellHorizontalPadding: 0, ...definition.themeOverride?.(item) },
    }),
  }
}

// PERCENTAGE ------------------------------------------------------------------
export function percentageColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => string,
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: definition.title.length * 10,
    sortComparator: (row1, row2, direction) => compare(lookup(row1), lookup(row2), direction),
    ...definition,
    themeOverride: undefined,
    toCell: (item) => ({
      kind: GridCellKind.Text,
      data: lookup(item),
      allowOverlay: false,
      displayData: lookup(item),

      contentAlign: 'right',
      themeOverride: definition.themeOverride?.(item),
    }),
  }
}

//BOOLEAN ----------------------------------------------------------------------
export function booleanColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => Link | YesNo | YesNoBlank | boolean | null | undefined,
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: definition.title.length * 10,
    sortComparator: (row1, row2, direction) =>
      compare(linkToBoolean(lookup(row1)) ? 1 : 0, linkToBoolean(lookup(row2)) ? 1 : 0, direction),

    ...definition,
    // We omit the `onClick` property because the custom renderer has its own handler for it.
    onClick: undefined,
    toCell: (item) => ({
      kind: GridCellKind.Custom,
      allowOverlay: true,
      copyData: linkToBoolean(lookup(item)) ? 'true' : 'false',
      data: {
        kind: 'links-cell',
        underlineOffset: 6,
        links: [booleanToLink(lookup(item))],
        onClick: definition.onClick,
        rowData: item,
      },
      themeOverride: definition.themeOverride?.(item),
    }),
  }
}

export function toYesNo(value: boolean | null): 'Yes' | 'No' | '-' {
  if (value === null) {
    return '-'
  }
  return value ? 'Yes' : 'No'
}

export function toYesNoBlank(value: YesNoBlank): 'Yes' | 'No' | '-' {
  if (value === 'Yes') {
    return 'Yes'
  }
  if (value === 'No') {
    return 'No'
  }
  return '-'
}

// COUNTRY ---------------------------------------------------------------------
export function countryColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => { name: string; code: string },
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: Math.max(definition.title.length * 10, 200),
    sortComparator: (row1, row2, direction) => compare(lookup(row1).name, lookup(row2).name, direction),
    ...definition,
    toCell: (item) => ({
      kind: GridCellKind.Custom,
      data: { kind: 'country', ...lookup(item) },
      copyData: lookup(item).name,
      displayData: lookup(item).name,
      allowOverlay: false,
      themeOverride: definition.themeOverride?.(item),
    }),
  }
}

// COUNTRY ARRAY ----------------------------------------------------------------
export function countryArrayColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => { code: string; label: string; hoverLabel?: string }[],
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: Math.max(definition.title.length * 10, 200),
    sortComparator: (row1, row2, direction) =>
      compare(lookup(row1)[0]?.label ?? '', lookup(row2)[0]?.label ?? '', direction),
    ...definition,
    toCell: (item) => {
      const values = lookup(item)
      return {
        kind: GridCellKind.Custom,
        allowOverlay: true,
        copyData: values.map((v) => v.label).join(', '),
        data: {
          kind: 'country-array-cell',
          countries: values.map((v) => ({ id: v.code, label: v.label, hoverLabel: v.hoverLabel })),
          rowData: item,
        },
        themeOverride: definition.themeOverride?.(item),
      }
    },
  }
}

// INLINE BAR ------------------------------------------------------------------
export function inlineBarChartColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => number[],
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    sortComparator: () => 0, // No sorting for inline bar charts
    ...definition,
    toCell: (item: Row) => ({
      kind: GridCellKind.Custom,
      data: { kind: 'inline-bar-chart', datapoints: lookup(item) },
      copyData: lookup(item).join(', '),
      allowOverlay: false,
      themeOverride: definition.themeOverride?.(item),
    }),
  }
}

// FAVORITE -------------------------------------------------------------------
export function favoriteColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => boolean,
  definition: ColumnDefinition<Row, ColumnId>,
): DataTableColumn<Row, ColumnId> {
  return {
    width: definition.title.length * 10,
    sortComparator: () => 0, // No sorting for favorites
    ...definition,
    toCell: (item) => ({
      kind: GridCellKind.Custom,
      allowOverlay: false,
      data: { kind: 'favorite', isFavorite: lookup(item), rowData: item, isSelected: definition.isSelected?.(item) },
      copyData: lookup(item) ? 'true' : 'false',
      onClick: definition.onClick,
      themeOverride: definition.themeOverride?.(item),
    }),
  }
}

export function compare(
  value1: string | number | null | undefined,
  value2: string | number | null | undefined,
  direction: 'asc' | 'desc' | null,
): number {
  if (direction === null) {
    return 0
  }
  if (value1 === null || value1 === undefined) {
    return direction === 'asc' ? -1 : 1
  }
  if (value2 === null || value2 === undefined) {
    return direction === 'asc' ? 1 : -1
  }
  if (value1 < value2) {
    return direction === 'asc' ? -1 : 1
  }
  if (value1 > value2) {
    return direction === 'asc' ? 1 : -1
  }
  return 0
}

export type FrontendSort<Row extends object, Fieldname extends string> =
  | { order: null; field: null }
  | {
      order: 'asc' | 'desc'
      sortComparator: (row1: Row, row2: Row, direction: 'asc' | 'desc' | null) => number
      field: Fieldname
    }
export function sortData<Row extends object, Fieldname extends string>(
  sortConfig: FrontendSort<Row, Fieldname>,
  data: Row[],
): Row[] {
  if (!sortConfig.order) {
    return data
  }
  return data.toSorted((a, b) => {
    return sortConfig.sortComparator(a, b, sortConfig.order)
  })
}

// DATE ------------------------------------------------------------------------
type FallbackValue = {
  /**
   * Default value to use when the number is null or undefined.
   * Defaults to an empty string.
   * @default ''
   * @example '-', '0.00', 'N/A'
   */
  fallback: string
}

export function dateColumn<Row extends object, ColumnId extends string>(
  lookup: (item: Row) => Date | null | undefined,
  definition: ColumnDefinition<Row, ColumnId>,
  format: Intl.DateTimeFormatOptions & FallbackValue = {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    fallback: '',
  },
): DataTableColumn<Row, ColumnId> {
  return {
    width: definition.title.length * 10,
    sortComparator: () => 0, // TODO: implement sorting for dates later
    ...definition,
    toCell: (item) => {
      const date = lookup(item)
      const formattedDate = date ? date.toLocaleDateString(undefined, format) : format.fallback

      return { kind: GridCellKind.Text, data: formattedDate, allowOverlay: false, displayData: formattedDate }
    },
  }
}
