import {
  type CustomCell,
  type CustomRenderer,
  getMiddleCenterBias,
  GridCellKind,
  measureTextCached,
} from '@glideapps/glide-data-grid'

import { YesNo, YesNoBlank } from '@components/ui/data-table/utils/columns'

export type Link = {
  readonly label: string

  readonly isFilteredOut?: boolean
  /**
   * Will be triggered if the user clicks on the link.
   */
  readonly onClick?: () => void

  readonly url?: URL
}

type LinksCellProps = {
  readonly kind: 'links-cell'
  /**
   * Used to hand tune the position of the underline as this is not a native canvas capability,
   * it can need tweaking for different fonts.
   */
  readonly underlineOffset?: number
  readonly links: readonly Link[]
  /**
   * Will only be triggered if the user click outside of a link - or if
   * the items does not have a separate `onClick` handler.
   */
  readonly onClick?: <T>(items: T) => void

  readonly rowData: any
}

export type LinksCell = CustomCell<LinksCellProps>

export function stringToLink(item: string | Link): Link {
  if (typeof item === 'string') {
    return { label: item }
  }
  return item
}

export function booleanToLink(item: boolean | YesNo | YesNoBlank | null | undefined | Link): Link {
  if (typeof item === 'object') {
    return item as Link
  } else if (item === null || item === undefined) {
    return { label: '' }
  } else if (item === true || item === 'Yes') {
    return { label: 'Yes' }
  } else if (item === false || item === 'No') {
    return { label: 'No' }
  }
  return { label: '' }
}

export function linkToBoolean(item: boolean | string | undefined | null | Link): boolean | null {
  if (typeof item === 'boolean') {
    return item
  } else if (typeof item === 'string') {
    return item === 'Yes'
  } else if (typeof item === 'object') {
    return item?.label === 'Yes'
  } else {
    return null
  }
}

export function linkToString(item: string | Link): string {
  if (typeof item === 'string') {
    return item
  }
  return item.label
}
/**
 * Based on the mouse position, this function will return the link that
 * is currently hovered. If no link is hovered, it will return `undefined`.
 */
function getHoveredLink(e: Parameters<NonNullable<CustomRenderer<LinksCell>['onSelect']>>[0]) {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d', { alpha: false })
  if (ctx === null) {
    return
  }

  const { posX: hoverX, bounds: rect, cell, theme } = e
  const font = theme.baseFontFull
  ctx.font = font

  const { links } = cell.data

  const xPad = theme.cellHorizontalPadding

  let drawX = rect.x + xPad

  const mouseX = rect.x + hoverX

  for (const [index, link] of links.entries()) {
    const needsComma = index < links.length - 1
    const metrics = measureTextCached(link.label, ctx)
    const commaMetrics = needsComma ? measureTextCached(link.label + ',', ctx, font) : metrics

    const isHovered = drawX <= mouseX && mouseX <= drawX + metrics.width

    if (isHovered) {
      return link
    }

    drawX += commaMetrics.width + 5
  }

  return undefined
}

export const linksTableRenderer: CustomRenderer<LinksCell> = {
  kind: GridCellKind.Custom,
  needsHover: true,
  needsHoverPosition: true,
  isMatch: (c): c is LinksCell => (c.data as { kind?: string }).kind === 'links-cell',
  onSelect: (e) => {
    if (getHoveredLink(e) !== undefined) {
      e.preventDefault()
    }
  },
  onClick: (event) => {
    // We first check if the user clicked on a specific link and if we have a link-specific handler.
    const clickedItem = getHoveredLink(event)
    if (clickedItem?.onClick) {
      clickedItem.onClick()
      event.preventDefault()
      return undefined
    }

    // In case the item has a URL, we open it in a new tab
    if (clickedItem?.url) {
      window.open(clickedItem.url, '_blank')
      return undefined
    }

    // As a fallback, ie if either the user clicked in the cell but not on a link or if the
    // cell does not have a separate `onClick` handler, we check if the cell has a generic
    // `onClick` handler.
    const { onClick: genericCellOnClick } = event.cell.data
    if (genericCellOnClick) {
      genericCellOnClick(event.cell.data.rowData)
      event.preventDefault()
      return undefined
    }

    return undefined
  },

  draw: (args, cell) => {
    const { ctx, rect, theme, hoverX = -100 } = args
    const { links, underlineOffset = 5 } = cell.data

    const xPad = theme.cellHorizontalPadding

    let drawX = rect.x + xPad

    const rectHoverX = rect.x + hoverX

    const font = theme.baseFontFull

    const middleCenterBias = getMiddleCenterBias(ctx, font)
    const drawY = rect.y + rect.height / 2 + middleCenterBias

    for (const [index, l] of links.entries()) {
      const needsComma = index < links.length - 1
      const metrics = measureTextCached(l.label, ctx, font)
      const commaMetrics = needsComma ? measureTextCached(l.label + ',', ctx, font) : metrics

      const isHovered = rectHoverX > drawX && rectHoverX < drawX + metrics.width

      if (isHovered) {
        args.overrideCursor?.('pointer')
        ctx.moveTo(drawX, Math.floor(drawY + underlineOffset) + 0.5)
        ctx.lineTo(drawX + metrics.width, Math.floor(drawY + underlineOffset) + 0.5)

        ctx.lineWidth = 1
        ctx.strokeStyle = theme.textDark
        ctx.stroke()

        ctx.fillStyle = theme.linkColor
      } else {
        ctx.fillStyle = theme.textDark
      }

      if (l.isFilteredOut === true) {
        ctx.fillStyle = theme.textLight
      }

      ctx.fillText(needsComma ? l.label + ',' : l.label, drawX, drawY)

      drawX += commaMetrics.width + 4
    }

    return true
  },
}
