/**
 * 折线图和面积图绘制函数
 *
 * 从 u-charts 核心库搬迁的折线图和面积图绘制相关函数
 */

// @ts-nocheck - 由于从 u-charts 搬迁，类型系统不兼容，暂时禁用类型检查
import type { ChartOptions, UChartsConfig, SeriesItem } from '../data-processing/series-calculator';
import { getLineDataPoints, getDataPoints } from '../charts-data/basic-charts';
import { splitPoints, createCurveControlPoints } from '../utils/misc';
import { drawPointShape, drawActivePoint, drawPointText } from './common-renderer';
import { assign } from '../config';
import { hexToRgb } from '../utils/color';

export type CanvasContext = any;

export interface Point {
  x: number;
  y: number;
  r?: number;
  t?: number;
}

export interface LineOption {
  type?: 'straight' | 'curve' | 'step';
  width?: number;
  activeType?: string;
  linearType?: string;
  onShadow?: boolean;
  animation?: string;
}

export interface AreaOption extends LineOption {
  opacity?: number;
  addLine?: boolean;
  gradient?: boolean;
}

export interface LineResult {
  xAxisPoints: number[];
  calPoints: any[][];
  eachSpacing: number;
}

/**
 * 绘制面积图数据点
 * @param series 系列数据
 * @param opts 图表配置
 * @param config UCharts配置
 * @param context Canvas上下文
 * @param process 动画进度（默认为1）
 * @returns 计算结果
 */
export function drawAreaDataPoints(
  series: SeriesItem[],
  opts: ChartOptions,
  config: UChartsConfig,
  context: CanvasContext,
  process = 1
): LineResult {
  const areaOption = assign(
    {},
    {
      type: 'straight',
      opacity: 0.2,
      addLine: false,
      width: 2,
      gradient: false,
      activeType: 'none',
    },
    opts.extra?.area
  );

  const xAxisData = opts.chartData?.xAxisData;
  if (!xAxisData) {
    return { xAxisPoints: [], calPoints: [], eachSpacing: 0 };
  }

  const xAxisPoints = xAxisData.xAxisPoints;
  const eachSpacing = xAxisData.eachSpacing;
  const endY = opts.height - opts.area![2];
  const calPoints: any[][] = [];

  context.save();
  let leftSpace = 0;
  let rightSpace = opts.width + eachSpacing;

  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
    context.translate(opts._scrollDistance_, 0);
    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area![3];
    rightSpace = leftSpace + (opts.xAxis?.itemCount || 0 + 4) * eachSpacing;
  }

  series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
    const ranges = opts.chartData?.yAxisData?.ranges
      ? [].concat(opts.chartData.yAxisData.ranges[eachSeries.index || 0])
      : [];
    const minRange = ranges.pop();
    const maxRange = ranges.shift();
    const data = eachSeries.data;

    const points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
    calPoints.push(points);
    const splitPointList = splitPoints(points, eachSeries);

    for (let i = 0; i < splitPointList.length; i++) {
      const points = splitPointList[i];

      // 绘制区域
      context.beginPath();
      context.setStrokeStyle(hexToRgb(eachSeries.color || '', areaOption.opacity || 0.2));

      if (areaOption.gradient) {
        const gradient = context.createLinearGradient(0, opts.area![0], 0, opts.height - opts.area![2]);
        gradient.addColorStop('0', hexToRgb(eachSeries.color || '', areaOption.opacity || 0.2));
        gradient.addColorStop('1.0', hexToRgb('#FFFFFF', 0.1));
        context.setFillStyle(gradient);
      } else {
        context.setFillStyle(hexToRgb(eachSeries.color || '', areaOption.opacity || 0.2));
      }

      context.setLineWidth((areaOption.width || 2) * opts.pix);

      if (points.length > 1) {
        const firstPoint = points[0];
        const lastPoint = points[points.length - 1];
        context.moveTo(firstPoint.x, firstPoint.y);
        let startPoint = 0;

        if (areaOption.type === 'curve') {
          for (let j = 0; j < points.length; j++) {
            const item = points[j];
            if (startPoint == 0 && item.x > leftSpace) {
              context.moveTo(item.x, item.y);
              startPoint = 1;
            }
            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
              const ctrlPoint = createCurveControlPoints(points, j - 1);
              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
            }
          }
        }

        if (areaOption.type === 'straight') {
          for (let j = 0; j < points.length; j++) {
            const item = points[j];
            if (startPoint == 0 && item.x > leftSpace) {
              context.moveTo(item.x, item.y);
              startPoint = 1;
            }
            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
              context.lineTo(item.x, item.y);
            }
          }
        }

        if (areaOption.type === 'step') {
          for (let j = 0; j < points.length; j++) {
            const item = points[j];
            if (startPoint == 0 && item.x > leftSpace) {
              context.moveTo(item.x, item.y);
              startPoint = 1;
            }
            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
              context.lineTo(item.x, points[j - 1].y);
              context.lineTo(item.x, item.y);
            }
          }
        }

        context.lineTo(lastPoint.x, endY);
        context.lineTo(firstPoint.x, endY);
        context.lineTo(firstPoint.x, firstPoint.y);
      } else {
        const item = points[0];
        context.moveTo(item.x - eachSpacing / 2, item.y);
      }

      context.closePath();
      context.fill();

      // 画连线
      if (areaOption.addLine) {
        if (eachSeries.lineType == 'dash') {
          const dashLength = (eachSeries as any).dashLength ? (eachSeries as any).dashLength : 8;
          context.setLineDash([dashLength * opts.pix, dashLength * opts.pix]);
        }

        context.beginPath();
        context.setStrokeStyle(eachSeries.color || '');
        context.setLineWidth((areaOption.width || 2) * opts.pix);

        if (points.length === 1) {
          context.moveTo(points[0].x, points[0].y);
        } else {
          context.moveTo(points[0].x, points[0].y);
          let startPoint = 0;

          if (areaOption.type === 'curve') {
            for (let j = 0; j < points.length; j++) {
              const item = points[j];
              if (startPoint == 0 && item.x > leftSpace) {
                context.moveTo(item.x, item.y);
                startPoint = 1;
              }
              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
                const ctrlPoint = createCurveControlPoints(points, j - 1);
                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
              }
            }
          }

          if (areaOption.type === 'straight') {
            for (let j = 0; j < points.length; j++) {
              const item = points[j];
              if (startPoint == 0 && item.x > leftSpace) {
                context.moveTo(item.x, item.y);
                startPoint = 1;
              }
              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
                context.lineTo(item.x, item.y);
              }
            }
          }

          if (areaOption.type === 'step') {
            for (let j = 0; j < points.length; j++) {
              const item = points[j];
              if (startPoint == 0 && item.x > leftSpace) {
                context.moveTo(item.x, item.y);
                startPoint = 1;
              }
              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
                context.lineTo(item.x, points[j - 1].y);
                context.lineTo(item.x, item.y);
              }
            }
          }

          context.moveTo(points[0].x, points[0].y);
        }

        context.stroke();
        context.setLineDash([]);
      }
    }

    // 画点
    if (opts.dataPointShape !== false) {
      drawPointShape(points, eachSeries.color || '', eachSeries.pointShape, context, opts);
    }

    drawActivePoint(points, eachSeries.color || '', eachSeries.pointShape, context, opts, areaOption, seriesIndex);
  });

  if (opts.dataLabel !== false && process === 1) {
    series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
      const ranges = opts.chartData?.yAxisData?.ranges
        ? [].concat(opts.chartData.yAxisData.ranges[eachSeries.index || 0])
        : [];
      const minRange = ranges.pop();
      const maxRange = ranges.shift();
      const data = eachSeries.data;

      const points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
      drawPointText(points, eachSeries, config, context, opts);
    });
  }

  context.restore();

  return {
    xAxisPoints: xAxisPoints,
    calPoints: calPoints,
    eachSpacing: eachSpacing,
  };
}

/**
 * 绘制折线图数据点
 * @param series 系列数据
 * @param opts 图表配置
 * @param config UCharts配置
 * @param context Canvas上下文
 * @param process 动画进度（默认为1）
 * @returns 计算结果
 */
export function drawLineDataPoints(
  series: SeriesItem[],
  opts: ChartOptions,
  config: UChartsConfig,
  context: CanvasContext,
  process = 1
): LineResult {
  const lineOption = assign(
    {},
    {
      type: 'straight',
      width: 2,
      activeType: 'none',
      linearType: 'none',
      onShadow: false,
      animation: 'vertical',
    },
    opts.extra?.line
  );

  (lineOption as any).width *= opts.pix;

  const xAxisData = opts.chartData?.xAxisData;
  if (!xAxisData) {
    return { xAxisPoints: [], calPoints: [], eachSpacing: 0 };
  }

  const xAxisPoints = xAxisData.xAxisPoints;
  const eachSpacing = xAxisData.eachSpacing;
  const calPoints: any[][] = [];

  context.save();
  let leftSpace = 0;
  let rightSpace = opts.width + eachSpacing;

  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
    context.translate(opts._scrollDistance_, 0);
    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area![3];
    rightSpace = leftSpace + (opts.xAxis?.itemCount || 0 + 4) * eachSpacing;
  }

  series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
    // 这段很神奇的代码用于解决ios16的setStrokeStyle失效的bug
    context.beginPath();
    context.setStrokeStyle(eachSeries.color || '');
    context.moveTo(-10000, -10000);
    context.lineTo(-10001, -10001);
    context.stroke();

    const ranges = opts.chartData?.yAxisData?.ranges
      ? [].concat(opts.chartData.yAxisData.ranges[eachSeries.index || 0])
      : [];
    const minRange = ranges.pop();
    const maxRange = ranges.shift();
    const data = eachSeries.data;

    const points = getLineDataPoints(
      data as number[],
      minRange,
      maxRange,
      xAxisPoints,
      eachSpacing,
      opts,
      config,
      lineOption,
      process
    );
    calPoints.push(points);
    const splitPointList = splitPoints(points, eachSeries);

    if (eachSeries.lineType == 'dash') {
      const dashLength = (eachSeries as any).dashLength ? (eachSeries as any).dashLength : 8;
      context.setLineDash([dashLength * opts.pix, dashLength * opts.pix]);
    }

    context.beginPath();
    let strokeColor = eachSeries.color || '';

    if (
      lineOption.linearType !== 'none' &&
      (eachSeries as any).linearColor &&
      (eachSeries as any).linearColor.length > 0
    ) {
      const grd = context.createLinearGradient(
        xAxisData.startX,
        opts.height / 2,
        xAxisData.endX,
        opts.height / 2
      );
      for (let i = 0; i < (eachSeries as any).linearColor.length; i++) {
        grd.addColorStop((eachSeries as any).linearColor[i][0], hexToRgb((eachSeries as any).linearColor[i][1], 1));
      }
      strokeColor = grd;
    }

    context.setStrokeStyle(strokeColor);

    if (lineOption.onShadow == true && (eachSeries as any).setShadow && (eachSeries as any).setShadow.length > 0) {
      context.setShadow(
        (eachSeries as any).setShadow[0],
        (eachSeries as any).setShadow[1],
        (eachSeries as any).setShadow[2],
        (eachSeries as any).setShadow[3]
      );
    } else {
      context.setShadow(0, 0, 0, 'rgba(0,0,0,0)');
    }

    context.setLineWidth(lineOption.width || 2);

    splitPointList.forEach(function (points: any[], index: number) {
      if (points.length === 1) {
        context.moveTo(points[0].x, points[0].y);
      } else {
        context.moveTo(points[0].x, points[0].y);
        let startPoint = 0;

        if (lineOption.type === 'curve') {
          for (let j = 0; j < points.length; j++) {
            const item = points[j];
            if (startPoint == 0 && item.x > leftSpace) {
              context.moveTo(item.x, item.y);
              startPoint = 1;
            }
            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
              const ctrlPoint = createCurveControlPoints(points, j - 1);
              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);
            }
          }
        }

        if (lineOption.type === 'straight') {
          for (let j = 0; j < points.length; j++) {
            const item = points[j];
            if (startPoint == 0 && item.x > leftSpace) {
              context.moveTo(item.x, item.y);
              startPoint = 1;
            }
            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
              context.lineTo(item.x, item.y);
            }
          }
        }

        if (lineOption.type === 'step') {
          for (let j = 0; j < points.length; j++) {
            const item = points[j];
            if (startPoint == 0 && item.x > leftSpace) {
              context.moveTo(item.x, item.y);
              startPoint = 1;
            }
            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {
              context.lineTo(item.x, points[j - 1].y);
              context.lineTo(item.x, item.y);
            }
          }
        }

        context.moveTo(points[0].x, points[0].y);
      }
    });

    context.stroke();
    context.setLineDash([]);

    if (opts.dataPointShape !== false) {
      drawPointShape(points, eachSeries.color || '', eachSeries.pointShape, context, opts);
    }

    drawActivePoint(points, eachSeries.color || '', eachSeries.pointShape, context, opts, lineOption);
  });

  if (opts.dataLabel !== false && process === 1) {
    series.forEach(function (eachSeries: SeriesItem, seriesIndex: number) {
      const ranges = opts.chartData?.yAxisData?.ranges
        ? [].concat(opts.chartData.yAxisData.ranges[eachSeries.index || 0])
        : [];
      const minRange = ranges.pop();
      const maxRange = ranges.shift();
      const data = eachSeries.data;

      const points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
      drawPointText(points, eachSeries, config, context, opts);
    });
  }

  context.restore();

  return {
    xAxisPoints: xAxisPoints,
    calPoints: calPoints,
    eachSpacing: eachSpacing,
  };
}
