import React from 'react';
import { withRouter } from 'react-router-dom'
import Chart from 'chart.js/auto';
import { navigateToQuery } from '../../query'
import GraphTooltip from './graph-tooltip'
import { buildDataSet, METRIC_LABELS, METRIC_FORMATTER } from './graph-util'
import dateFormatter from './date-formatter';
import FadeIn from '../../fade-in';
import classNames from 'classnames';

const calculateMaximumY = function(dataset) {
  const yAxisValues = dataset
    .flatMap((item) => item.data)
    .map((item) => item || 0)

  if (yAxisValues) {
    return Math.max(...yAxisValues)
  } else {
    return 1
  }
}

class LineGraph extends React.Component {
  constructor(props) {
    super(props);
    this.regenerateChart = this.regenerateChart.bind(this);
    this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
  }

  getGraphMetric() {
    let metric = this.props.graphData.metric

    if (metric == 'visitors' && this.props.query.filters.goal) {
      return 'conversions'
    } else {
      return metric
    }
  }

  regenerateChart() {
    const { graphData, query } = this.props
    const metric = this.getGraphMetric()
    const graphEl = document.getElementById("main-graph-canvas")
    this.ctx = graphEl.getContext('2d');
    const dataSet = buildDataSet(graphData.plot, graphData.comparison_plot, graphData.present_index, this.ctx, METRIC_LABELS[metric])

    return new Chart(this.ctx, {
      type: 'line',
      data: {
        labels: graphData.labels,
        datasets: dataSet
      },
      options: {
        animation: false,
        plugins: {
          legend: { display: false },
          tooltip: {
            enabled: false,
            mode: 'index',
            intersect: false,
            position: 'average',
            external: GraphTooltip(graphData, metric, query)
          },
        },
        responsive: true,
        maintainAspectRatio: false,
        onResize: this.updateWindowDimensions,
        elements: { line: { tension: 0 }, point: { radius: 0 } },
        onClick: this.maybeHopToHoveredPeriod.bind(this),
        scale: {
          ticks: { precision: 0, maxTicksLimit: 8 }
        },
        scales: {
          y: {
            min: 0,
            suggestedMax: calculateMaximumY(dataSet),
            ticks: {
              callback: METRIC_FORMATTER[metric],
              color: this.props.darkTheme ? 'rgb(243, 244, 246)' : undefined
            },
            grid: {
              zeroLineColor: 'transparent',
              drawBorder: false,
            }
          },
          yComparison: {
            min: 0,
            suggestedMax: calculateMaximumY(dataSet),
            display: false,
            grid: { display: false },
          },
          x: {
            grid: { display: false },
            ticks: {
              callback: function(val, _index, _ticks) {
                if (this.getLabelForValue(val) == "__blank__") return ""

                const hasMultipleYears =
                  graphData.labels
                    .filter((date) => typeof date === 'string')
                    .map(date => date.split('-')[0])
                    .filter((value, index, list) => list.indexOf(value) === index)
                    .length > 1

                if (graphData.interval === 'hour' && query.period !== 'day') {
                  const date = dateFormatter({
                    interval: "date",
                    longForm: false,
                    period: query.period,
                    shouldShowYear: hasMultipleYears,
                  })(this.getLabelForValue(val))

                  const hour = dateFormatter({
                    interval: graphData.interval,
                    longForm: false,
                    period: query.period,
                    shouldShowYear: hasMultipleYears,
                  })(this.getLabelForValue(val))

                  // Returns a combination of date and hour. This is because
                  // small intervals like hour may return multiple days
                  // depending on the query period.
                  return `${date}, ${hour}`
                }

                if (graphData.interval === 'minute' && query.period !== 'realtime') {
                  return dateFormatter({
                    interval: "hour", longForm: false, period: query.period,
                  })(this.getLabelForValue(val))
                }

                return dateFormatter({
                  interval: graphData.interval, longForm: false, period: query.period, shouldShowYear: hasMultipleYears,
                })(this.getLabelForValue(val))
              },
              color: this.props.darkTheme ? 'rgb(243, 244, 246)' : undefined
            }
          }
        },
        interaction: {
          mode: 'index',
          intersect: false,
        }
      }
    });
  }

  repositionTooltip(e) {
    const tooltipEl = document.getElementById('chartjs-tooltip');
    if (tooltipEl && window.innerWidth >= 768) {
      if (e.clientX > 0.66 * window.innerWidth) {
        tooltipEl.style.right = (window.innerWidth - e.clientX) + window.pageXOffset + 'px'
        tooltipEl.style.left = null;
      } else {
        tooltipEl.style.right = null;
        tooltipEl.style.left = e.clientX + window.pageXOffset + 'px'
      }
      tooltipEl.style.top = e.clientY + window.pageYOffset + 'px'
      tooltipEl.style.opacity = 1;
    }
  }

  componentDidMount() {
    if (this.props.graphData) {
      this.chart = this.regenerateChart();
    }
    window.addEventListener('mousemove', this.repositionTooltip);
  }

  componentDidUpdate(prevProps) {
    const { graphData, darkTheme } = this.props;
    const tooltip = document.getElementById('chartjs-tooltip');

    if (
      graphData !== prevProps.graphData ||
      darkTheme !== prevProps.darkTheme
    ) {

      if (graphData) {
        if (this.chart) {
          this.chart.destroy();
        }
        this.chart = this.regenerateChart();
        this.chart.update();
      }

      if (tooltip) {
        tooltip.style.display = 'none';
      }
    }

    if (!graphData) {
      if (this.chart) {
        this.chart.destroy();
      }

      if (tooltip) {
        tooltip.style.display = 'none';
      }
    }
  }

  componentWillUnmount() {
    // Ensure that the tooltip doesn't hang around when we are loading more data
    const tooltip = document.getElementById('chartjs-tooltip');
    if (tooltip) {
      tooltip.style.opacity = 0;
      tooltip.style.display = 'none';
    }
    window.removeEventListener('mousemove', this.repositionTooltip)
  }

  /**
   * The current ticks' limits are set to treat iPad (regular/Mini/Pro) as a regular screen.
   * @param {*} chart - The chart instance.
   * @param {*} dimensions - An object containing the new dimensions *of the chart.*
   */
  updateWindowDimensions(chart, dimensions) {
    chart.options.scales.x.ticks.maxTicksLimit = dimensions.width < 720 ? 5 : 8
  }

  maybeHopToHoveredPeriod(e) {
    const element = this.chart.getElementsAtEventForMode(e, 'index', { intersect: false })[0]
    const date = this.props.graphData.labels[element.index] || this.props.graphData.comparison_labels[element.index]

    if (this.props.graphData.interval === 'month') {
      navigateToQuery(this.props.history, this.props.query, { period: 'month', date })
    } else if (this.props.graphData.interval === 'date') {
      navigateToQuery(this.props.history, this.props.query, { period: 'day', date })
    }
  }

  render() {
    const { graphData } = this.props
    const canvasClass = classNames('mt-4 select-none', { 'cursor-pointer': !['minute', 'hour'].includes(graphData?.interval) })

    return (
      <FadeIn show={graphData}>
        <div className="relative h-96 print:h-auto print:pb-8 w-full z-0">
          <canvas id="main-graph-canvas" className={canvasClass}></canvas>
        </div>
      </FadeIn>
    )
  }
}

export default withRouter(LineGraph)