import Highcharts, {
  PointOptionsObject,
  SeriesOptions,
  SeriesPieOptions,
  TooltipFormatterContextObject,
} from 'highcharts'
import HighchartsAccessibility from 'highcharts/modules/accessibility'
import HighchartsNoDataToDisplay from 'highcharts/modules/no-data-to-display'
import create from './dom/create'
import findAll from './dom/findAll'
import { htmlEncode } from 'js-htmlencode'

HighchartsAccessibility(Highcharts)
HighchartsNoDataToDisplay(Highcharts)

interface DefaultConfig {
  Default: Highcharts.Options
  AreaFilled: Highcharts.Options
  AreaStacked: Highcharts.Options
  BarHorizontal: Highcharts.Options
  BarGrouped: Highcharts.Options
  BarStacked: Highcharts.Options
  Line: Highcharts.Options
  LineZoomable: Highcharts.Options
  Pie: Highcharts.Options
  Scatter: Highcharts.Options
}

interface CustomSeries extends SeriesOptions {
  data?: number[]
}

const defaultColor = '#7cb5ec' // A fallback color in case the highcharts default colors are not string
const defaultRgba = 'rgba(124,181,236,0)' // A fallback rgba string in case the highcharts default color is not string
let firstColor: string
let rgba: string

const highchartsFirstColor = Highcharts.getOptions().colors?.[0]

if (typeof highchartsFirstColor === 'string') {
  firstColor = highchartsFirstColor
} else {
  firstColor = defaultColor
}

const highchartsRgba = Highcharts.color(firstColor).setOpacity(0).get('rgba')

if (typeof highchartsRgba === 'string') {
  rgba = highchartsRgba
} else {
  rgba = defaultRgba
}

const defaultTooltip = {
  shared: true,
  formatter: function (
    this: TooltipFormatterContextObject,
  ): string | undefined {
    const getTooltipFromPointData = (
      point: Highcharts.Point | TooltipFormatterContextObject,
    ) => {
      return (
        '<br><span style="color:' +
        htmlEncode(
          typeof point.series.color === 'string' ? point.series.color : '',
        ) +
        '">\u25CF</span> ' +
        htmlEncode(point.series.name) +
        ': <b>' +
        point.y +
        '</b>'
      )
    }

    let tooltipText = this.x

    if (this.points) {
      for (let i = 0; i < this.points.length; i++) {
        tooltipText += getTooltipFromPointData(this.points[i])
      }
    } else if (this.point) {
      tooltipText += getTooltipFromPointData(this.point)
    }
    return tooltipText?.toString()
  },
}

const Config: DefaultConfig = {
  Default: {
    credits: { enabled: false },
    legend: { accessibility: { enabled: false } },
    title: undefined,
    xAxis: {
      tickLength: 0,
    },
    yAxis: {
      opposite: true,
      title: undefined,
    },
  },
  AreaFilled: {
    chart: { type: 'area' },
    tooltip: defaultTooltip,
  },
  AreaStacked: {
    chart: { type: 'area' },
    plotOptions: {
      area: {
        stacking: 'normal',
        lineColor: '#666666',
        lineWidth: 1,
        marker: {
          lineWidth: 1,
          lineColor: '#666666',
        },
      },
    },
    tooltip: defaultTooltip,
  },
  BarHorizontal: {
    chart: { type: 'bar' },
    plotOptions: {
      bar: {
        dataLabels: {
          enabled: true,
        },
      },
    },
    tooltip: defaultTooltip,
  },
  BarGrouped: {
    chart: { type: 'bar' },
    plotOptions: {
      bar: {
        dataLabels: {
          enabled: true,
          formatter: function (this: Highcharts.PointLabelObject): string {
            return this.y?.toString() || ''
          },
        },
      },
    },
    tooltip: defaultTooltip,
  },
  BarStacked: {
    chart: { type: 'bar' },
    plotOptions: {
      series: {
        stacking: 'normal',
      },
    },
    tooltip: defaultTooltip,
  },
  Line: {
    chart: { type: 'line' },
    tooltip: defaultTooltip,
  },
  LineZoomable: {
    chart: { zooming: { type: 'x' }, type: 'area' },
    plotOptions: {
      area: {
        fillColor: {
          linearGradient: {
            x1: 0,
            y1: 0,
            x2: 0,
            y2: 1,
          },
          stops: [
            [0, firstColor],
            [1, rgba],
          ],
        },
        marker: {
          radius: 2,
        },
        lineWidth: 1,
        states: {
          hover: {
            lineWidth: 1,
          },
        },
      },
    },
    tooltip: defaultTooltip,
  },
  Pie: {
    chart: { type: 'pie' },
    tooltip: {
      pointFormat:
        'Total: <b>{point.y}</b><br>Percentage: <b>{point.percentage:.1f}%</b>',
    },
    accessibility: {
      point: {
        valueSuffix: '%',
      },
    },
    plotOptions: {
      pie: {
        allowPointSelect: true,
        cursor: 'pointer',
        dataLabels: {
          enabled: true,
          format: '<b>{point.name}</b>: {point.percentage:.1f} %',
        },
      },
    },
  },
  Scatter: {
    chart: { type: 'scatter', zooming: { type: 'xy' } },
    plotOptions: {
      scatter: {
        marker: {
          radius: 3,
          states: {
            hover: {
              enabled: true,
            },
          },
        },
        states: {
          hover: {
            enabled: false,
          },
        },
      },
    },
    tooltip: defaultTooltip,
  },
}

Highcharts.theme = {
  chart: {
    height: 300,
    style: {
      fontFamily: 'inherit',
      fontSize: '100%',
    },
  },
  colors: [
    'var(--color-blue6)',
    'var(--color-gray6)',
    'var(--color-red6)',
    'var(--color-yellow6)',
    'var(--color-green6)',
  ],
}

Highcharts.setOptions(Highcharts.theme)
Highcharts.setOptions({ lang: { noData: 'No Data to Display' } })

const createLabels = (table: HTMLTableElement): string[] => {
  const labels = []
  for (const row of findAll(table, 'tbody > tr') as HTMLTableRowElement[]) {
    const cells = findAll(row, 'th, td')
    labels.push(cells[0].textContent || '')
  }
  return labels
}

const getSeriesData = (
  table: HTMLTableElement,
  type: string,
  isPie = false,
): CustomSeries[] | SeriesPieOptions[] => {
  const series: CustomSeries[] = []
  const pieData: PointOptionsObject[] = []
  findAll(table, 'thead > tr:first-child > th').forEach((th, index) => {
    if (index > 0) {
      series.push({
        name: th.textContent || undefined,
        data: [],
        type,
      })
    }
  })

  for (const row of findAll(table, 'tbody > tr')) {
    const cells = findAll(row as HTMLElement, 'th, td')

    for (let i = 1; i < cells.length; ++i) {
      series[i - 1].data?.push(parseFloat(cells[i].textContent || '0'))
    }
  }

  if (isPie) {
    series.forEach((s) => {
      const d = s.data
      let count = 0
      if (!d) return
      for (let i = d.length; i--; ) {
        count += d[i]
      }
      pieData.push({
        name: s.name,
        y: count,
      })
    })
  }

  return isPie ? [{ type: 'pie', data: pieData }] : series
}

export default class ChartCompat {
  constructor(table: HTMLTableElement) {
    let config: Highcharts.Options
    const chart = create('div', { className: 'Chart' })
    table.parentNode?.insertBefore(chart, table.nextSibling)
    table.style.display = 'none'

    const labelsArr: string[] = createLabels(table)

    const labels = {
      formatter: function (this: Highcharts.AxisLabelsFormatterContextObject) {
        return labelsArr[parseInt(this.value.toString())]
      },
    }
    const xAxis = {
      categories: labelsArr,
    }

    // Valid chart type?
    switch (table.getAttribute('data-chart-type')) {
      case 'AREA_FILLED':
        config = Object.assign({}, Config.AreaFilled, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.AreaFilled.chart?.type || ''),
        })
        break

      case 'AREA_STACKED':
        config = Object.assign({}, Config.AreaStacked, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.AreaStacked.chart?.type || ''),
        })
        break

      case 'BAR_HORIZONTAL':
        config = Object.assign({}, Config.BarHorizontal, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.BarHorizontal.chart?.type || ''),
        })
        break

      case 'BAR_GROUPED':
        config = Object.assign({}, Config.BarGrouped, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.BarGrouped.chart?.type || ''),
        })
        break

      case 'BAR_STACKED':
        config = Object.assign({}, Config.BarStacked, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.BarStacked.chart?.type || ''),
        })
        break

      case 'LINE':
        config = Object.assign({}, Config.Line, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.Line.chart?.type || ''),
        })
        break

      case 'LINE_ZOOMABLE':
        config = Object.assign({}, Config.LineZoomable, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.LineZoomable.chart?.type || ''),
        })
        break

      case 'PIE':
        config = Object.assign({}, Config.Pie, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.Pie.chart?.type || '', true),
        })
        break

      case 'SCATTER':
        config = Object.assign({}, Config.Scatter, Config.Default, {
          labels,
          xAxis,
          series: getSeriesData(table, Config.Scatter.chart?.type || ''),
        })
        break

      default:
        config = {}
    }

    if (!config) {
      return
    }

    // eslint-disable-next-line no-new
    new Highcharts.Chart(chart, config, () => {
      chart.dispatchEvent(
        new CustomEvent('highchart-loaded', {
          bubbles: true,
        }),
      )
    })
  }
}
