import {
  contains,
  filter,
  find,
  isArray,
  isEmpty,
  isFunction,
  isNil,
  isNumberEqual,
  isObject,
  memoize,
  get,
  values,
} from '@antv/util';
import { View } from '../chart';
import { FIELD_ORIGIN, GROUP_ATTRS } from '../constant';
import { Attribute, Scale } from '../dependents';
import Geometry from '../geometry/base';
import { Data, Datum, MappingDatum, Point, TooltipCfg, TooltipTitle } from '../interface';
import { getName, inferScaleType } from './scale';

function snapEqual(v1: any, v2: any, scale: Scale) {
  const value1 = scale.translate(v1);
  const value2 = scale.translate(v2);

  return isNumberEqual(value1, value2);
}

function getXValueByPoint(point: Point, geometry: Geometry): number {
  const coordinate = geometry.coordinate;
  const xScale = geometry.getXScale();
  const range = xScale.range;
  const rangeMax = range[range.length - 1];
  const rangeMin = range[0];

  const invertPoint = coordinate.invert(point);

  let xValue = invertPoint.x;
  if (coordinate.isPolar && xValue > (1 + rangeMax) / 2) {
    xValue = rangeMin; // 极坐标下，scale 的 range 被做过特殊处理
  }
  return xScale.translate(xScale.invert(xValue));
}

function filterYValue(data: Data, point: Point, geometry: Geometry) {
  const coordinate = geometry.coordinate;
  const yScale = geometry.getYScale();
  const yField = yScale.field;
  const invertPoint = coordinate.invert(point);
  const yValue = yScale.invert(invertPoint.y);

  const result = find(data, (obj: Datum) => {
    const originData = obj[FIELD_ORIGIN];
    return originData[yField][0] <= yValue && originData[yField][1] >= yValue;
  });
  return result || data[data.length - 1];
}

const getXDistance = memoize((scale: Scale) => {
  if (scale.isCategory) {
    return 1;
  }
  const scaleValues = scale.values; // values 是无序的
  const length = scaleValues.length;
  let min = scale.translate(scaleValues[0]);
  let max = min;

  for (let index = 0; index < length; index++) {
    const value = scaleValues[index];
    // 时间类型需要 translate
    const numericValue = scale.translate(value);
    if (numericValue < min) {
      min = numericValue;
    }
    if (numericValue > max) {
      max = numericValue;
    }
  }
  return (max - min) / (length - 1);
});

/**
 * 获得 tooltip 的 title
 * @param originData
 * @param geometry
 * @param title
 */
function getTooltipTitle(originData: Datum, geometry: Geometry, title: TooltipTitle): string {
  const positionAttr = geometry.getAttribute('position');
  const fields = positionAttr.getFields();
  const scales = geometry.scales;

  const titleField = isFunction(title) || !title ? fields[0] : title;
  const titleScale = scales[titleField];

  // 如果创建了该字段对应的 scale，则通过 scale.getText() 方式取值，因为用户可能对数据进行了格式化
  // 如果没有对应的 scale，则从原始数据中取值，如果原始数据中仍不存在，则直接放回 title 值
  const tooltipTitle = titleScale ? titleScale.getText(originData[titleField]) : originData[titleField] || titleField;

  return isFunction(title) ? title(tooltipTitle, originData) : tooltipTitle;
}

function getAttributesForLegend(geometry: Geometry) {
  const attributes = values(geometry.attributes);
  return filter(attributes, (attribute: Attribute) => contains(GROUP_ATTRS, attribute.type));
}

function getTooltipValueScale(geometry: Geometry) {
  const attributes = getAttributesForLegend(geometry);
  let scale;
  for (const attribute of attributes) {
    const tmpScale = attribute.getScale(attribute.type);
    if (tmpScale && tmpScale.isLinear) {
      const tmpScaleDef = get(geometry.scaleDefs, tmpScale.field);
      const inferedScaleType = inferScaleType(tmpScale, tmpScaleDef, attribute.type, geometry.type);
      if (inferedScaleType !== 'cat') {
        // 如果指定字段是非 position 的，同时是连续的
        scale = tmpScale;
        break;
      }
    }
  }

  const xScale = geometry.getXScale();
  const yScale = geometry.getYScale();

  return scale || yScale || xScale;
}

function getTooltipValue(originData: Datum, valueScale: Scale) {
  const field = valueScale.field;
  const value = originData[field];

  if (isArray(value)) {
    const texts = value.map((eachValue) => {
      return valueScale.getText(eachValue);
    });
    return texts.join('-');
  }
  return valueScale.getText(value);
}

// 根据原始数据获取 tooltip item 中 name 值
function getTooltipName(originData: Datum, geometry: Geometry) {
  let nameScale: Scale;
  const groupScales = geometry.getGroupScales();
  if (groupScales.length) {
    // 如果存在分组类型，取第一个分组类型
    nameScale = groupScales[0];
  }
  if (nameScale) {
    const field = nameScale.field;
    return nameScale.getText(originData[field]);
  }

  const valueScale = getTooltipValueScale(geometry);
  return getName(valueScale);
}

/**
 * @ignore
 * Finds data from geometry by point
 * @param point canvas point
 * @param data an item of geometry.dataArray
 * @param geometry
 * @returns
 */
export function findDataByPoint(point: Point, data: MappingDatum[], geometry: Geometry) {
  if (data.length === 0) {
    return null;
  }

  const geometryType = geometry.type;
  const xScale = geometry.getXScale();
  const yScale = geometry.getYScale();

  const xField = xScale.field;
  const yField = yScale.field;

  let rst = null;

  // 热力图采用最小逼近策略查找 point 击中的数据
  if (geometryType === 'heatmap' || geometryType === 'point') {
    // 将 point 画布坐标转换为原始数据值
    const coordinate = geometry.coordinate;
    const invertPoint = coordinate.invert(point); // 转换成归一化的数据
    const x = xScale.invert(invertPoint.x); // 转换为原始值
    const y = yScale.invert(invertPoint.y); // 转换为原始值

    let min = Infinity;
    for (let index = 0; index < data.length; index++) {
      const obj = data[index];
      const originData = obj[FIELD_ORIGIN];
      const range = (originData[xField] - x) ** 2 + (originData[yField] - y) ** 2;
      if (range < min) {
        min = range;
        rst = obj;
      }
    }

    return rst;
  }

  // 其他 Geometry 类型按照 x 字段数据进行查找
  const first = data[0];
  let last = data[data.length - 1];
  const xValue = getXValueByPoint(point, geometry);
  const firstXValue = first[FIELD_ORIGIN][xField];
  const firstYValue = first[FIELD_ORIGIN][yField];
  const lastXValue = last[FIELD_ORIGIN][xField];
  const isYArray = yScale.isLinear && isArray(firstYValue); // 考虑 x 维度相同，y 是数组区间的情况

  // 如果 x 的值是数组
  if (isArray(firstXValue)) {
    for (let index = 0; index < data.length; index++) {
      const record = data[index];
      const originData = record[FIELD_ORIGIN];
      // xValue 在 originData[xField] 的数值区间内
      if (xScale.translate(originData[xField][0]) <= xValue && xScale.translate(originData[xField][1]) >= xValue) {
        if (isYArray) {
          // 层叠直方图场景，x 和 y 都是数组区间
          if (!isArray(rst)) {
            rst = [];
          }
          rst.push(record);
        } else {
          rst = record;
          break;
        }
      }
    }
    if (isArray(rst)) {
      rst = filterYValue(rst, point, geometry);
    }
  } else {
    let next;
    if (!xScale.isLinear && xScale.type !== 'timeCat') {
      // x 轴对应的数据为非线性以及非时间类型的数据采用遍历查找
      for (let index = 0; index < data.length; index++) {
        const record = data[index];
        const originData = record[FIELD_ORIGIN];
        if (snapEqual(originData[xField], xValue, xScale)) {
          if (isYArray) {
            if (!isArray(rst)) {
              rst = [];
            }
            rst.push(record);
          } else {
            rst = record;
            break;
          }
        } else if (xScale.translate(originData[xField]) <= xValue) {
          last = record;
          next = data[index + 1];
        }
      }

      if (isArray(rst)) {
        rst = filterYValue(rst, point, geometry);
      }
    } else {
      // x 轴对应的数据为线性以及时间类型，进行二分查找，性能更好
      if (
        (xValue > xScale.translate(lastXValue) || xValue < xScale.translate(firstXValue)) &&
        (xValue > xScale.max || xValue < xScale.min)
      ) {
        // 不在数据范围内
        return null;
      }

      let firstIdx = 0;
      let lastIdx = data.length - 1;
      let middleIdx;
      while (firstIdx <= lastIdx) {
        middleIdx = Math.floor((firstIdx + lastIdx) / 2);
        const item = data[middleIdx][FIELD_ORIGIN][xField];
        if (snapEqual(item, xValue, xScale)) {
          return data[middleIdx];
        }

        if (xScale.translate(item) <= xScale.translate(xValue)) {
          firstIdx = middleIdx + 1;
          last = data[middleIdx];
          next = data[middleIdx + 1];
        } else {
          if (lastIdx === 0) {
            last = data[0];
          }
          lastIdx = middleIdx - 1;
        }
      }
    }

    if (last && next) {
      // 计算最逼近的
      if (
        Math.abs(xScale.translate(last[FIELD_ORIGIN][xField]) - xValue) >
        Math.abs(xScale.translate(next[FIELD_ORIGIN][xField]) - xValue)
      ) {
        last = next;
      }
    }
  }

  const distance = getXDistance(geometry.getXScale()); // 每个分类间的平均间距
  if (!rst && Math.abs(xScale.translate(last[FIELD_ORIGIN][xField]) - xValue) <= distance / 2) {
    rst = last;
  }

  return rst;
}

/**
 * @ignore
 * Gets tooltip items
 * @param data
 * @param geometry
 * @param [title]
 * @returns
 */
export function getTooltipItems(
  data: MappingDatum,
  geometry: Geometry,
  title: TooltipTitle = '',
  showNil: boolean = false
) {
  const originData = data[FIELD_ORIGIN];
  const tooltipTitle = getTooltipTitle(originData, geometry, title);
  const tooltipOption = geometry.tooltipOption;
  const { defaultColor } = geometry.theme;
  const items = [];
  let name;
  let value;

  function addItem(itemName, itemValue) {
    if (showNil || (!isNil(itemValue) && itemValue !== '')) {
      // 值为 null的时候，忽视
      const item = {
        title: tooltipTitle,
        data: originData, // 原始数据
        mappingData: data, // 映射后的数据
        name: itemName,
        value: itemValue,
        color: data.color || defaultColor,
        marker: true,
      };

      items.push(item);
    }
  }

  if (isObject(tooltipOption)) {
    const { fields, callback } = tooltipOption;
    if (callback) {
      // 用户定义了回调函数
      const callbackParams = fields.map((field: string) => {
        return data[FIELD_ORIGIN][field];
      });
      const cfg = callback(...callbackParams);
      const itemCfg = {
        data: data[FIELD_ORIGIN], // 原始数据
        mappingData: data, // 映射后的数据
        title: tooltipTitle,
        color: data.color || defaultColor,
        marker: true, // 默认展示 marker
        ...cfg,
      };

      items.push(itemCfg);
    } else {
      const scales = geometry.scales;
      for (const field of fields) {
        if (!isNil(originData[field])) {
          // 字段数据为null, undefined 时不显示
          const scale = scales[field];
          name = getName(scale);
          value = scale.getText(originData[field]);
          addItem(name, value);
        }
      }
    }
  } else {
    const valueScale = getTooltipValueScale(geometry);
    // 字段数据为null ,undefined时不显示
    value = getTooltipValue(originData, valueScale);
    name = getTooltipName(originData, geometry);
    addItem(name, value);
  }
  return items;
}

function getTooltipItemsByFindData(geometry: Geometry, point, title, tooltipCfg: TooltipCfg) {
  const { showNil } = tooltipCfg;
  const result = [];
  const dataArray = geometry.dataArray;
  if (!isEmpty(dataArray)) {
    geometry.sort(dataArray); // 先进行排序，便于 tooltip 查找
    for (const data of dataArray) {
      const record = findDataByPoint(point, data, geometry);
      if (record) {
        const elementId = geometry.getElementId(record);
        const element = geometry.elementsMap[elementId];
        if (geometry.type === 'heatmap' || element.visible) {
          // Heatmap 没有 Element
          // 如果图形元素隐藏了，怎不再 tooltip 上展示相关数据
          const items = getTooltipItems(record, geometry, title, showNil);
          if (items.length) {
            result.push(items);
          }
        }
      }
    }
  }

  return result;
}

function getTooltipItemsByHitShape(geometry, point, title, tooltipCfg: TooltipCfg) {
  const { showNil } = tooltipCfg;
  const result = [];
  const container = geometry.container;
  const shape = container.getShape(point.x, point.y);
  if (shape && shape.get('visible') && shape.get('origin')) {
    const mappingData = shape.get('origin').mappingData;
    const items = getTooltipItems(mappingData, geometry, title, showNil);
    if (items.length) {
      result.push(items);
    }
  }

  return result;
}

/**
 * 不进行递归查找
 */
export function findItemsFromView(view: View, point: Point, tooltipCfg: TooltipCfg) {
  const result = [];
  // 先从 view 本身查找
  const geometries = view.geometries;
  const { shared, title, reversed } = tooltipCfg;
  for (const geometry of geometries) {
    if (geometry.visible && geometry.tooltipOption !== false) {
      // geometry 可见同时未关闭 tooltip
      const geometryType = geometry.type;
      let tooltipItems;
      if (['point', 'edge', 'polygon'].includes(geometryType)) {
        // 始终通过图形拾取
        tooltipItems = getTooltipItemsByHitShape(geometry, point, title, tooltipCfg);
      } else if (['area', 'line', 'path', 'heatmap'].includes(geometryType)) {
        // 如果是 'area', 'line', 'path'，始终通过数据查找方法查找 tooltip
        tooltipItems = getTooltipItemsByFindData(geometry, point, title, tooltipCfg);
      } else {
        if (shared !== false) {
          tooltipItems = getTooltipItemsByFindData(geometry, point, title, tooltipCfg);
        } else {
          tooltipItems = getTooltipItemsByHitShape(geometry, point, title, tooltipCfg);
        }
      }
      if (tooltipItems.length) {
        if (reversed) {
          tooltipItems.reverse();
        }
        // geometry 有可能会有多个 item，因为用户可以设置 geometry.tooltip('x*y*z')
        result.push(tooltipItems);
      }
    }
  }

  return result;
}

export function findItemsFromViewRecurisive(view: View, point: Point, tooltipCfg: TooltipCfg) {
  let result = findItemsFromView(view, point, tooltipCfg);

  // 递归查找，并合并结果
  for (const childView of view.views) {
    result = result.concat(findItemsFromView(childView, point, tooltipCfg));
  }

  return result;
}
