import { setupCanvas, clearCanvas } from './canvas.js';
import { BALLOON_OFFSET, X_AXIS_HEIGHT, MAX_TOOLTIP_ITEMS } from './constants.js';
import { getPieRadius } from './formulas.js';
import { formatInteger, getLabelDate, getLabelTime, statsFormatDayHourFull } from './format.js';
import { getCssColor, isColorCloseToBackground } from './skin.js';
import { throttle, throttleWithRaf } from './utils.js';
import { addEventListener, createElement } from './minifiers.js';
import { toPixels } from './Projection.js';

export function createTooltip(container, data, plotSize, colors, onZoom, onFocus) {
  let _state;
  let _points;
  let _projection;
  let _secondaryPoints;
  let _secondaryProjection;

  let _element;
  let _canvas;
  let _context;
  let _balloon;

  let _offsetX;
  let _offsetY;
  let _clickedOnLabel = null;

  let _isZoomed = false;
  let _isZooming = false;

  const _selectLabelOnRaf = throttleWithRaf(_selectLabel);
  const _throttledUpdateContent = throttle(_updateContent, 100, true, true);

  _setupLayout();

  function update(state, points, projection, secondaryPoints, secondaryProjection) {
    _state = state;
    _points = points;
    _projection = projection;
    _secondaryPoints = secondaryPoints;
    _secondaryProjection = secondaryProjection;
    _selectLabel(true);
  }

  function toggleLoading(isLoading) {
    _balloon.classList.toggle('lovely-chart--state-loading', isLoading);

    if (!isLoading) {
      _clear();
    }
  }

  function toggleIsZoomed(isZoomed) {
    if (isZoomed !== _isZoomed) {
      _isZooming = true;
    }
    _isZoomed = isZoomed;
    _balloon.classList.toggle('lovely-chart--state-inactive', isZoomed);
  }

  function _setupLayout() {
    _element = createElement();
    _element.className = `lovely-chart--tooltip`;

    _setupCanvas();
    _setupBalloon();

    if ('ontouchstart' in window) {
      addEventListener(_element, 'touchmove', _onMouseMove);
      addEventListener(_element, 'touchstart', _onMouseMove);
      addEventListener(document, 'touchstart', _onDocumentMove);
    } else {
      addEventListener(_element, 'mousemove', _onMouseMove);
      addEventListener(_element, 'click', _onClick);
      addEventListener(document, 'mousemove', _onDocumentMove);
    }

    container.appendChild(_element);
  }

  function _setupCanvas() {
    const { canvas, context } = setupCanvas(_element, plotSize);

    _canvas = canvas;
    _context = context;
  }

  function _setupBalloon() {
    _balloon = createElement();
    _balloon.className = `lovely-chart--tooltip-balloon${!data.isZoomable ? ' lovely-chart--state-inactive' : ''}`;
    _balloon.innerHTML = '<div class="lovely-chart--tooltip-title"></div><div class="lovely-chart--tooltip-legend"></div><div class="lovely-chart--spinner"></div>';

    if ('ontouchstart' in window && data.isZoomable) {
      addEventListener(_balloon, 'click', _onBalloonClick);
    }

    _element.appendChild(_balloon);
  }

  function _onMouseMove(e) {
    if (e.target === _balloon || _balloon.contains(e.target) || _clickedOnLabel) {
      return;
    }

    _isZooming = false;

    const pageOffset = _getPageOffset(_element);
    _offsetX = (e.touches ? e.touches[0].clientX : e.clientX) - pageOffset.left;
    _offsetY = (e.touches ? e.touches[0].clientY : e.clientY) - pageOffset.top;

    _selectLabelOnRaf();
  }

  function _onDocumentMove(e) {
    if (_offsetX !== null && e.target !== _element && !_element.contains(e.target)) {
      _clear();
    }
  }

  function _onClick(e) {
    if (_isZooming) {
      return;
    }

    if (data.isZoomable) {
      const oldLabelIndex = _clickedOnLabel;

      _clickedOnLabel = null;
      _onMouseMove(e, true);

      const newLabelIndex = _getLabelIndex();
      if (newLabelIndex !== oldLabelIndex) {
        _clickedOnLabel = newLabelIndex;
      }

      onZoom(newLabelIndex);
    }
  }

  function _onBalloonClick() {
    if (_balloon.classList.contains('lovely-chart--state-inactive')) {
      return;
    }

    const labelIndex = _projection.findClosestLabelIndex(_offsetX);
    onZoom(labelIndex);
  }

  function _clear(isExternal) {
    _offsetX = null;
    _clickedOnLabel = null;
    clearCanvas(_canvas, _context);
    _hideBalloon();

    if (!isExternal && onFocus) {
      onFocus(null);
    }
  }

  function _getLabelIndex() {
    const labelIndex = _projection.findClosestLabelIndex(_offsetX);
    return labelIndex < _state.labelFromIndex || labelIndex > _state.labelToIndex ? null : labelIndex;
  }

  function _selectLabel(isExternal) {
    if (!_offsetX || !_state || _isZooming) {
      return;
    }

    const labelIndex = _getLabelIndex();
    if (labelIndex === null) {
      _clear(isExternal);
      return;
    }

    const pointerVector = getPointerVector();
    const shouldShowBalloon = data.isPie ? pointerVector.distance <= getPieRadius(_projection) : true;

    if (!isExternal && onFocus) {
      if (data.isPie) {
        onFocus(pointerVector);
      } else {
        onFocus(labelIndex);
      }
    }

    function getValue(values, labelIndex) {
      if (data.isPie) {
        return values.slice(_state.labelFromIndex, _state.labelToIndex + 1).reduce((a, x) => a + x, 0);
      }

      return values[labelIndex];
    }

    const [xPx] = toPixels(_projection, labelIndex, 0);
    const statistics = data.datasets
      .map(({ key, name, values, hasOwnYAxis }, i) => ({
        key,
        name,
        value: getValue(values, labelIndex),
        hasOwnYAxis,
        originalIndex: i,
      }))
      .filter(({ key }) => _state.filter[key]);

    if (statistics.length && shouldShowBalloon) {
      _updateBalloon(statistics, labelIndex);
    } else {
      _hideBalloon();
    }

    clearCanvas(_canvas, _context);
    if (data.isLines || data.isAreas) {
      if (data.isLines) {
        _drawCircles(statistics, labelIndex);
      }

      _drawTail(xPx, plotSize.height - X_AXIS_HEIGHT, getCssColor(colors, 'grid-lines'));
    }
  }

  function _drawCircles(statistics, labelIndex) {
    statistics.forEach(({ value, key, hasOwnYAxis, originalIndex }) => {
      if (value == null) return;

      const pointIndex = labelIndex - _state.labelFromIndex;
      const point = hasOwnYAxis ? _secondaryPoints[pointIndex] : _points[originalIndex][pointIndex];

      if (!point) {
        return;
      }

      const [x, y] = hasOwnYAxis
        ? toPixels(_secondaryProjection, labelIndex, point.stackValue)
        : toPixels(_projection, labelIndex, point.stackValue);

      // TODO animate
      _drawCircle(
        [x, y],
        getCssColor(colors, `dataset#${key}`),
        getCssColor(colors, 'background'),
      );
    });
  }

  function _drawCircle([xPx, yPx], strokeColor, fillColor) {
    _context.strokeStyle = strokeColor;
    _context.fillStyle = fillColor;
    _context.lineWidth = 2;

    _context.beginPath();
    _context.arc(xPx, yPx, 4, 0, 2 * Math.PI);
    _context.fill();
    _context.stroke();
  }

  function _drawTail(xPx, height, color) {
    _context.strokeStyle = color;
    _context.lineWidth = 1;

    _context.beginPath();
    _context.moveTo(xPx, 0);
    _context.lineTo(xPx, height);
    _context.stroke();
  }

  function _getBalloonLeftOffset(labelIndex) {
    const meanLabel = (_state.labelFromIndex + _state.labelToIndex) / 2;
    const { angle } = getPointerVector();

    const shouldPlaceRight = data.isPie ? angle > Math.PI / 2 : labelIndex < meanLabel;

    const leftOffset = shouldPlaceRight ? _offsetX + BALLOON_OFFSET : _offsetX - (_balloon.offsetWidth + BALLOON_OFFSET);

    return Math.min(Math.max(0, leftOffset), container.offsetWidth - _balloon.offsetWidth);
  }

  function _getBalloonTopOffset() {
    return data.isPie ? `${_offsetY}px` : 0;
  }

  function _updateBalloon(statistics, labelIndex) {
    _balloon.style.transform = `translate3D(${_getBalloonLeftOffset(labelIndex)}px, ${_getBalloonTopOffset()}, 0)`;
    _balloon.classList.add('lovely-chart--state-shown');

    if (data.isPie) {
      _updateContent(null, statistics);
    } else {
      _throttledUpdateContent(_getTitle(data, labelIndex), statistics);
    }
  }

  function _getTitle(data, labelIndex) {
    switch (data.tooltipFormatter) {
      case 'statsFormatDayHourFull':
        return statsFormatDayHourFull(data.xLabels[labelIndex].value);
      case 'statsTooltipFormat(\'day\')':
        return getLabelDate(data.xLabels[labelIndex]);
      case 'statsTooltipFormat(\'hour\')':
      case 'statsTooltipFormat(\'5min\')':
        return getLabelTime(data.xLabels[labelIndex]);
      default:
        return data.xLabels[labelIndex].text;
    }
  }

  function _isPieSectorSelected(statistics, value, totalValue, index, pointerVector) {
    const offset = index > 0 ? statistics.slice(0, index).reduce((a, x) => a + x.value, 0) : 0;
    const beginAngle = offset / totalValue * Math.PI * 2 - Math.PI / 2;
    const endAngle = (offset + value) / totalValue * Math.PI * 2 - Math.PI / 2;

    return pointerVector &&
      beginAngle <= pointerVector.angle &&
      pointerVector.angle < endAngle &&
      pointerVector.distance <= getPieRadius(_projection);
  }

  function _updateTitle(title) {
    const titleContainer = _balloon.children[0];

    if (data.isPie) {
      if (titleContainer) {
        titleContainer.style.display = 'none';
      }
    } else {
      if (titleContainer.style.display === 'none') {
        titleContainer.style.display = '';
      }
      const currentTitle = titleContainer.querySelector(':not(.lovely-chart--state-hidden)');

      if (!titleContainer.textContent || !currentTitle) {
        titleContainer.textContent = '';

        const newTitle = createElement('span');
        newTitle.textContent = title;
        titleContainer.appendChild(newTitle);
      } else {
        currentTitle.textContent = title;
      }
    }
  }

  function _insertNewDataSet(dataSetContainer, { name, key, value }, totalValue) {
    const colorHex = data.colors[key];
    const colorClass = isColorCloseToBackground(colors, colorHex) ? '' : ` lovely-chart--color-${colorHex.slice(1)}`;
    const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right${colorClass}`;
    const newDataSet = createElement();
    newDataSet.className = 'lovely-chart--tooltip-dataset';
    newDataSet.setAttribute('data-present', 'true');
    newDataSet.setAttribute('data-name', name);
    const titleElement = createElement('span');
    titleElement.className = 'lovely-chart--dataset-title';
    titleElement.textContent = name;
    newDataSet.appendChild(titleElement);

    const valueElement = createElement('span');
    valueElement.className = className;
    valueElement.textContent = _formatValue(value);
    newDataSet.appendChild(valueElement);

    _renderPercentageValue(newDataSet, value, totalValue);

    dataSetContainer.appendChild(newDataSet);
  }

  function _updateDataSet(currentDataSet, { key, value } = {}, totalValue) {
    currentDataSet.setAttribute('data-present', 'true');

    const valueElement = currentDataSet.querySelector(`.lovely-chart--tooltip-dataset-value`);

    if (valueElement) {
      valueElement.textContent = _formatValue(value);
    }

    _renderPercentageValue(currentDataSet, value, totalValue);
  }

  function _formatValue(value) {
    return `${data.valuePrefix || ''}${formatInteger(value)}${data.valueSuffix || ''}`;
  }

  function _renderPercentageValue(dataSet, value, totalValue) {
    if (!data.isPercentage) {
      return;
    }

    if (data.isPie) {
      Array.from(dataSet.querySelectorAll(`.lovely-chart--percentage-title`)).forEach(e => e.remove());
      return;
    }

    const percentageValue = totalValue ? Math.round(value / totalValue * 100) : 0;
    const percentageElement = dataSet.querySelector(`.lovely-chart--percentage-title:not(.lovely-chart--state-hidden)`);

    if (!percentageElement) {
      const newPercentageTitle = createElement('span');
      newPercentageTitle.className = 'lovely-chart--percentage-title lovely-chart--position-left';
      newPercentageTitle.textContent = `${percentageValue}%`;
      dataSet.prepend(newPercentageTitle);
    } else {
      percentageElement.textContent = `${percentageValue}%`;
    }
  }

  function _updateDataSets(statistics) {
    const dataSetContainer = _balloon.children[1];
    if (data.isPie) {
      dataSetContainer.classList.add('lovely-chart--tooltip-legend-pie');
    }

    Array.from(dataSetContainer.children).forEach((dataSet) => {
      if (!data.isPie && dataSetContainer.classList.contains('lovely-chart--tooltip-legend-pie')) {
        dataSet.remove();
      } else {
        dataSet.setAttribute('data-present', 'false');
      }
    });

    const totalValue = statistics.reduce((a, x) => a + x.value, 0);
    const pointerVector = getPointerVector();
    const filteredStatistics = statistics.filter(({ value }) => value !== 0 && value != null);
    const sortedStatistics = filteredStatistics.sort((a, b) => b.value - a.value);
    const limitedStatistics = sortedStatistics.slice(0, MAX_TOOLTIP_ITEMS);
    const finalStatistics = data.isPie ? limitedStatistics.filter(({ value }, index) => _isPieSectorSelected(statistics, value, totalValue, index, pointerVector)) : limitedStatistics;

    finalStatistics.forEach((statItem) => {
      const currentDataSet = Array.from(dataSetContainer.children)
        .find((element) => element.dataset.name === statItem.name);

      if (!currentDataSet) {
        _insertNewDataSet(dataSetContainer, statItem, totalValue);
      } else {
        _updateDataSet(currentDataSet, statItem, totalValue);
        dataSetContainer.appendChild(currentDataSet);
      }
    });

    if ((data.isBars || data.isSteps || data.isAreas) && data.isStacked) {
      _renderTotal(dataSetContainer, _formatValue(totalValue));
    }

    if (data.secondaryYAxis) {
      _renderSecondaryTotal(dataSetContainer, totalValue);
    }

    // Re-append total rows to keep them at the bottom after sort reordering
    Array.from(dataSetContainer.querySelectorAll('[data-total="true"]'))
      .forEach((el) => dataSetContainer.appendChild(el));

    Array.from(dataSetContainer.querySelectorAll('[data-present="false"]'))
      .forEach((dataSet) => {
        dataSet.remove();
      });
  }

  function _updateContent(title, statistics) {
    _updateTitle(title);
    _updateDataSets(statistics);
  }

  function _renderTotal(dataSetContainer, totalValue) {
    const totalText = dataSetContainer.querySelector(`[data-total="true"]`);
    const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right`;
    if (!totalText) {
      const newTotalText = createElement();
      newTotalText.className = 'lovely-chart--tooltip-dataset lovely-chart--tooltip-dataset-total';
      newTotalText.setAttribute('data-present', 'true');
      newTotalText.setAttribute('data-total', 'true');
      const titleElement = createElement('span');
      titleElement.textContent = 'Total';
      newTotalText.appendChild(titleElement);

      const valueElement = createElement('span');
      valueElement.className = className;
      valueElement.textContent = totalValue;
      newTotalText.appendChild(valueElement);

      dataSetContainer.appendChild(newTotalText);
    } else {
      totalText.setAttribute('data-present', 'true');

      const valueElement = totalText.querySelector(`.lovely-chart--tooltip-dataset-value:not(.lovely-chart--state-hidden)`);
      valueElement.textContent = totalValue;
    }
  }

  function _renderSecondaryTotal(dataSetContainer, totalValue) {
    const { label, multiplier, prefix = '', suffix = '' } = data.secondaryYAxis;
    const totalText = dataSetContainer.querySelector(`[data-total="true"]`);
    const className = `lovely-chart--tooltip-dataset-value lovely-chart--position-right`;

    const secondaryValue = (totalValue * multiplier).toFixed(2);

    if (!totalText) {
      const newTotalText = createElement();
      newTotalText.className = 'lovely-chart--tooltip-dataset lovely-chart--tooltip-dataset-total';
      newTotalText.setAttribute('data-present', 'true');
      newTotalText.setAttribute('data-total', 'true');
      const titleElement = createElement('span');
      titleElement.textContent = label;
      newTotalText.appendChild(titleElement);

      const valueElement = createElement('span');
      valueElement.className = className;
      valueElement.textContent = `${prefix}${secondaryValue}${suffix}`;
      newTotalText.appendChild(valueElement);

      dataSetContainer.appendChild(newTotalText);
    } else {
      totalText.setAttribute('data-present', 'true');

      const valueElement = totalText.querySelector(`.lovely-chart--tooltip-dataset-value:not(.lovely-chart--state-hidden)`);
      valueElement.textContent = `${prefix}${secondaryValue}${suffix}`;
    }
  }


  function _hideBalloon() {
    _balloon.classList.remove('lovely-chart--state-shown');
  }

  function getPointerVector() {
    const { width, height } = _element.getBoundingClientRect();

    const center = [width / 2, height / 2];
    const angle = Math.atan2(_offsetY - center[1], _offsetX - center[0]);
    const distance = Math.sqrt((_offsetX - center[0]) ** 2 + (_offsetY - center[1]) ** 2);

    return {
      angle: angle >= -Math.PI / 2 ? angle : 2 * Math.PI + angle,
      distance,
    };
  }

  function _getPageOffset(el) {
    return el.getBoundingClientRect();
  }

  return { update, toggleLoading, toggleIsZoomed };
}

