/**
 * 柱状图和条形图绘制函数
 *
 * 从 u-charts 核心库搬迁的柱状图和条形图绘制相关函数
 */

// @ts-nocheck - 由于从 u-charts 搬迁，类型系统不兼容，暂时禁用类型检查

import type { ChartOptions, UChartsConfig, SeriesItem, ColumnOptions, MountOptions } from '../data-processing/series-calculator';
import {
  getColumnDataPoints,
  getStackDataPoints,
  getDataPoints,
  getBarDataPoints,
  getBarStackDataPoints,
  getMountDataPoints,
} from '../charts-data/basic-charts';
import {
  fixColumeData,
  fixColumeStackData,
  fixColumeMeterData,
  fixBarData,
} from '../helper-functions/data-fixers';
import { fillCustomColor } from '../data-processing/series-calculator';
import { drawToolTipSplitArea, drawBarToolTipSplitArea } from './common-renderer';
import { assign } from '../config';
import { hexToRgb } from '../utils/color';

export type CanvasContext = any;

export interface Point {
  x: number;
  y: number;
}

export interface DataPoint extends Point {
  width?: number;
  height?: number;
  x0?: number;
  y0?: number;
  color?: string;
  zeroPoints?: number;
  value?: number;
}

export interface ColumnOption {
  type?: 'group' | 'stack' | 'meter';
  width?: number;
  meterBorder?: number;
  meterFillColor?: string;
  barBorderCircle?: boolean;
  barBorderRadius?: number[];
  seriesGap?: number;
  linearType?: string;
  linearOpacity?: number;
  customColor?: string[];
  colorStop?: number;
  labelPosition?: string;
  borderWidth?: number;
  widthRatio?: number;
}

export interface ColumnResult {
  xAxisPoints?: number[];
  yAxisPoints?: number[];
  calPoints: any[];
  eachSpacing: number;
}

// 声明全局变量
declare const process: number;
declare const chartProcess: any;

/**
 * 绘制柱状图数据点
 * @param series 系列数据
 * @param opts 图表配置
 * @param config UCharts配置
 * @param context Canvas上下文
 * @returns 计算结果
 */
export function drawColumnDataPoints(
  series: SeriesItem[],
  opts: ChartOptions,
  config: UChartsConfig,
  context: CanvasContext
): ColumnResult {
  const xAxisData = opts.chartData?.xAxisData;
  if (!xAxisData) {
    return { xAxisPoints: [], calPoints: [], eachSpacing: 0 };
  }

  const xAxisPoints = xAxisData.xAxisPoints;
  const eachSpacing = xAxisData.eachSpacing;

  const columnOption = assign(
    {},
    {
      type: 'group',
      width: eachSpacing / 2,
      meterBorder: 4,
      meterFillColor: '#FFFFFF',
      barBorderCircle: false,
      barBorderRadius: [] as number[],
      seriesGap: 2,
      linearType: 'none',
      linearOpacity: 1,
      customColor: [] as string[],
      colorStop: 0,
      labelPosition: 'outside',
    },
    opts.extra?.column || {}
  ) as ColumnOption;

  const calPoints: any[] = [];

  context.save();
  let leftNum = -2;
  let rightNum = xAxisPoints.length + 2;

  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
    context.translate(opts._scrollDistance_, 0);
    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
    rightNum = leftNum + (opts.xAxis?.itemCount || 0) + 4;
  }

  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
    drawToolTipSplitArea(opts.tooltip.offset.x, opts, config, context, eachSpacing);
  }

  columnOption.customColor = fillCustomColor(
    columnOption.linearType || 'none',
    columnOption.customColor || [],
    series,
    config
  );

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

    // 计算0轴坐标
    const spacingValid = opts.height! - opts.area![0] - opts.area![2];
    const zeroHeight = (spacingValid * (0 - (minRange || 0))) / ((maxRange || 0) - (minRange || 0));
    const zeroPoints = opts.height! - Math.round(zeroHeight) - opts.area![2];
    (eachSeries as any).zeroPoints = zeroPoints;

    const data = eachSeries.data;

    switch ((columnOption.type as any)) {
      case 'group':
        let points = getColumnDataPoints(
          data as number[],
          minRange!,
          maxRange!,
          xAxisPoints,
          eachSpacing,
          opts,
          config,
          zeroPoints,
          chartProcess
        );
        const tooltipPoints = getStackDataPoints(
          data as number[],
          minRange!,
          maxRange!,
          xAxisPoints,
          eachSpacing,
          opts,
          config,
          seriesIndex,
          series,
          chartProcess
        );
        calPoints.push(tooltipPoints);
        points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);

        for (let i = 0; i < points.length; i++) {
          const item = points[i];
          if (item !== null && i > leftNum && i < rightNum) {
            const startX = item.x - item.width || 0 / 2;
            const height = opts.height! - item.y - opts.area![2];
            context.beginPath();
            let fillColor = item.color || eachSeries.color;
            const strokeColor = item.color || eachSeries.color;

            if (columnOption.linearType !== 'none') {
              const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
              // 透明渐变
              if (columnOption.linearType == 'opacity') {
                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity || 1));
                grd.addColorStop(1, hexToRgb(fillColor, 1));
              } else {
                grd.addColorStop(
                  0,
                  hexToRgb(
                    (columnOption.customColor || [])[eachSeries.linearIndex || 0],
                    columnOption.linearOpacity || 1
                  )
                );
                grd.addColorStop(
                  columnOption.colorStop || 0,
                  hexToRgb(
                    (columnOption.customColor || [])[eachSeries.linearIndex || 0],
                    columnOption.linearOpacity || 1
                  )
                );
                grd.addColorStop(1, hexToRgb(fillColor, 1));
              }
              fillColor = grd;
            }

            // 圆角边框
            if (
              (columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) ||
              columnOption.barBorderCircle === true
            ) {
              const left = startX;
              const top = item.y > zeroPoints ? zeroPoints : item.y;
              const width = item.width || 0;
              const height = Math.abs(zeroPoints - item.y);

              if (columnOption.barBorderCircle) {
                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
              }
              if (item.y > zeroPoints) {
                columnOption.barBorderRadius = [0, 0, width / 2, width / 2];
              }

              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
              const minRadius = Math.min(width / 2, height / 2);
              r0 = r0 > minRadius ? minRadius : r0;
              r1 = r1 > minRadius ? minRadius : r1;
              r2 = r2 > minRadius ? minRadius : r2;
              r3 = r3 > minRadius ? minRadius : r3;
              r0 = r0 < 0 ? 0 : r0;
              r1 = r1 < 0 ? 0 : r1;
              r2 = r2 < 0 ? 0 : r2;
              r3 = r3 < 0 ? 0 : r3;

              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
            } else {
              context.moveTo(startX, item.y);
              context.lineTo(startX + item.width || 0, item.y);
              context.lineTo(startX + item.width || 0, zeroPoints);
              context.lineTo(startX, zeroPoints);
              context.lineTo(startX, item.y);
              context.setLineWidth(1);
              context.setStrokeStyle(strokeColor);
            }

            context.setFillStyle(fillColor);
            context.closePath();
            context.fill();
          }
        }
        break;

      case 'stack':
        // 绘制堆叠数据图
        points = getStackDataPoints(
          data as number[],
          minRange,
          maxRange,
          xAxisPoints,
          eachSpacing,
          opts,
          config,
          seriesIndex,
          series,
          chartProcess
        );
        calPoints.push(points);
        points = fixColumeStackData(points, eachSpacing, series.length, seriesIndex, config, opts, series);

        for (let i = 0; i < points.length; i++) {
          const item = points[i];
          if (item !== null && i > leftNum && i < rightNum) {
            context.beginPath();
            const fillColor = item.color || eachSeries.color;
            const startX = item.x - item.width || 0 / 2 + 1;
            let height = opts.height! - item.y - opts.area![2];
            const height0 = opts.height! - item.y0! - opts.area![2];

            if (seriesIndex > 0) {
              height -= height0;
            }

            context.setFillStyle(fillColor);
            context.moveTo(startX, item.y);
            context.fillRect(startX, item.y, item.width || 0, height);
            context.closePath();
            context.fill();
          }
        }
        break;

      case 'meter':
        // 绘制温度计数据图
        points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
        calPoints.push(points);
        points = fixColumeMeterData(
          points,
          eachSpacing,
          series.length,
          seriesIndex,
          config,
          opts,
          columnOption.meterBorder || 4
        );

        for (let i = 0; i < points.length; i++) {
          const item = points[i];
          if (item !== null && i > leftNum && i < rightNum) {
            // 画背景颜色
            context.beginPath();

            if (seriesIndex == 0 && (columnOption.meterBorder || 0) > 0) {
              context.setStrokeStyle(eachSeries.color || '');
              context.setLineWidth((columnOption.meterBorder || 0) * opts.pix);
            }

            if (seriesIndex == 0) {
              context.setFillStyle(columnOption.meterFillColor || '#FFFFFF');
            } else {
              context.setFillStyle(item.color || eachSeries.color || '');
            }

            const startX = item.x - item.width || 0 / 2;
            const height = opts.height! - item.y - opts.area![2];

            if (
              (columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) ||
              columnOption.barBorderCircle === true
            ) {
              const left = startX;
              const top = item.y;
              const width = item.width || 0;
              const height = zeroPoints - item.y;

              if (columnOption.barBorderCircle) {
                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
              }

              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
              const minRadius = Math.min(width / 2, height / 2);
              r0 = r0 > minRadius ? minRadius : r0;
              r1 = r1 > minRadius ? minRadius : r1;
              r2 = r2 > minRadius ? minRadius : r2;
              r3 = r3 > minRadius ? minRadius : r3;
              r0 = r0 < 0 ? 0 : r0;
              r1 = r1 < 0 ? 0 : r1;
              r2 = r2 < 0 ? 0 : r2;
              r3 = r3 < 0 ? 0 : r3;

              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
              context.fill();
            } else {
              context.moveTo(startX, item.y);
              context.lineTo(startX + item.width || 0, item.y);
              context.lineTo(startX + item.width || 0, zeroPoints);
              context.lineTo(startX, zeroPoints);
              context.lineTo(startX, item.y);
              context.fill();
            }

            if (seriesIndex == 0 && (columnOption.meterBorder || 0) > 0) {
              context.closePath();
              context.stroke();
            }
          }
        }
        break;
    }
  });

  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) as number])
        : [];
      const minRange = ranges.pop();
      const maxRange = ranges.shift();
      const data = eachSeries.data;

      switch (columnOption.type) {
        case 'group': {
          const points = getColumnDataPoints(
            data as number[],
            minRange,
            maxRange,
            xAxisPoints,
            eachSpacing,
            opts,
            config,
            zeroPoints,
            chartProcess
          );
          const fixedPoints = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);
          drawColumePointText(fixedPoints, eachSeries, config, context, opts);
          break;
        }

        case 'stack': {
          const points = getStackDataPoints(
            data as number[],
            minRange,
            maxRange,
            xAxisPoints,
            eachSpacing,
            opts,
            config,
            seriesIndex,
            series,
            chartProcess
          );
          drawColumePointText(points, eachSeries, config, context, opts);
          break;
        }

        case 'meter': {
          const points = getDataPoints(data as number[], minRange, maxRange, xAxisPoints, eachSpacing, opts, config);
          drawColumePointText(points, eachSeries, config, context, opts);
          break;
        }
      }
    });
  }

  context.restore();

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

/**
 * 绘制堆叠柱状图/山峰图数据点
 * @param series 系列数据
 * @param opts 图表配置
 * @param config UCharts配置
 * @param context Canvas上下文
 * @returns 计算结果
 */
export function drawMountDataPoints(
  series: SeriesItem[],
  opts: ChartOptions,
  config: UChartsConfig,
  context: CanvasContext
): ColumnResult {
  const xAxisData = opts.chartData?.xAxisData;
  if (!xAxisData) {
    return { xAxisPoints: [], calPoints: [], eachSpacing: 0 };
  }

  const xAxisPoints = xAxisData.xAxisPoints;
  const eachSpacing = xAxisData.eachSpacing;

  const mountOption = assign(
    {},
    {
      type: 'mount',
      widthRatio: 1,
      borderWidth: 1,
      barBorderCircle: false,
      barBorderRadius: [],
      linearType: 'none',
      linearOpacity: 1,
      customColor: [],
      colorStop: 0,
    },
    opts.extra?.mount
  );

  (mountOption as any).widthRatio = (mountOption.widthRatio || 0 || 0) <= 0 ? 0 : mountOption.widthRatio || 0;
  (mountOption as any).widthRatio = (mountOption as any).widthRatio >= 2 ? 2 : (mountOption as any).widthRatio;

  const calPoints: any[] = [];

  context.save();
  let leftNum = -2;
  let rightNum = xAxisPoints.length + 2;

  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {
    context.translate(opts._scrollDistance_, 0);
    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;
    rightNum = leftNum + (opts.xAxis?.itemCount || 0) + 4;
  }

  (mountOption as any).customColor = fillCustomColor(
    mountOption.linearType || 'none',
    mountOption.customColor || [],
    series,
    config
  );

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

  // 计算0轴坐标
  const spacingValid = opts.height - opts.area![0] - opts.area![2];
  const zeroHeight = (spacingValid * (0 - minRange)) / (maxRange - minRange);
  const zeroPoints = opts.height - Math.round(zeroHeight) - opts.area![2];

  let points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints);

  switch (mountOption.type) {
    case 'bar':
      for (let i = 0; i < points.length; i++) {
        const item = points[i];
        if (item !== null && i > leftNum && i < rightNum) {
          const startX = item.x - (eachSpacing * (mountOption.widthRatio || 0 || 0)) / 2;
          const height = opts.height! - item.y - opts.area![2];
          context.beginPath();
          let fillColor = item.color || series[i].color;
          const strokeColor = item.color || series[i].color;

          if (mountOption.linearType !== 'none') {
            const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
            // 透明渐变
            if (mountOption.linearType == 'opacity') {
              grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity || 1));
              grd.addColorStop(1, hexToRgb(fillColor, 1));
            } else {
              grd.addColorStop(
                0,
                hexToRgb((mountOption.customColor || [])[series[i].linearIndex || 0], mountOption.linearOpacity || 1)
              );
              grd.addColorStop(
                mountOption.colorStop || 0,
                hexToRgb(
                  (mountOption.customColor || [])[series[i].linearIndex || 0],
                  mountOption.linearOpacity || 1
                )
              );
              grd.addColorStop(1, hexToRgb(fillColor, 1));
            }
            fillColor = grd;
          }

          // 圆角边框
          if (
            (mountOption.barBorderRadius && mountOption.barBorderRadius.length === 4) ||
            mountOption.barBorderCircle === true
          ) {
            const left = startX;
            const top = item.y > zeroPoints ? zeroPoints : item.y;
            const width = item.width || 0;
            const height = Math.abs(zeroPoints - item.y);

            if (mountOption.barBorderCircle) {
              mountOption.barBorderRadius = [width / 2, width / 2, 0, 0];
            }
            if (item.y > zeroPoints) {
              mountOption.barBorderRadius = [0, 0, width / 2, width / 2];
            }

            let [r0, r1, r2, r3] = mountOption.barBorderRadius;
            const minRadius = Math.min(width / 2, height / 2);
            r0 = r0 > minRadius ? minRadius : r0;
            r1 = r1 > minRadius ? minRadius : r1;
            r2 = r2 > minRadius ? minRadius : r2;
            r3 = r3 > minRadius ? minRadius : r3;
            r0 = r0 < 0 ? 0 : r0;
            r1 = r1 < 0 ? 0 : r1;
            r2 = r2 < 0 ? 0 : r2;
            r3 = r3 < 0 ? 0 : r3;

            context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);
            context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);
            context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);
            context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);
          } else {
            context.moveTo(startX, item.y);
            context.lineTo(startX + item.width || 0, item.y);
            context.lineTo(startX + item.width || 0, zeroPoints);
            context.lineTo(startX, zeroPoints);
            context.lineTo(startX, item.y);
          }

          context.setStrokeStyle(strokeColor);
          context.setFillStyle(fillColor);

          if ((mountOption.borderWidth || 0) > 0) {
            context.setLineWidth((mountOption.borderWidth || 0) * opts.pix);
            context.closePath();
            context.stroke();
          }
          context.fill();
        }
      }
      break;

    case 'triangle':
      for (let i = 0; i < points.length; i++) {
        const item = points[i];
        if (item !== null && i > leftNum && i < rightNum) {
          const startX = item.x - (eachSpacing * (mountOption.widthRatio || 0 || 0)) / 2;
          const height = opts.height! - item.y - opts.area![2];
          context.beginPath();
          let fillColor = item.color || series[i].color;
          const strokeColor = item.color || series[i].color;

          if (mountOption.linearType !== 'none') {
            const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
            // 透明渐变
            if (mountOption.linearType == 'opacity') {
              grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity || 1));
              grd.addColorStop(1, hexToRgb(fillColor, 1));
            } else {
              grd.addColorStop(
                0,
                hexToRgb((mountOption.customColor || [])[series[i].linearIndex || 0], mountOption.linearOpacity || 1)
              );
              grd.addColorStop(
                mountOption.colorStop || 0,
                hexToRgb(
                  (mountOption.customColor || [])[series[i].linearIndex || 0],
                  mountOption.linearOpacity || 1
                )
              );
              grd.addColorStop(1, hexToRgb(fillColor, 1));
            }
            fillColor = grd;
          }

          context.moveTo(startX, zeroPoints);
          context.lineTo(item.x, item.y);
          context.lineTo(startX + item.width || 0, zeroPoints);
          context.setStrokeStyle(strokeColor);
          context.setFillStyle(fillColor);

          if ((mountOption.borderWidth || 0) > 0) {
            context.setLineWidth((mountOption.borderWidth || 0) * opts.pix);
            context.stroke();
          }
          context.fill();
        }
      }
      break;

    case 'mount':
      for (let i = 0; i < points.length; i++) {
        const item = points[i];
        if (item !== null && i > leftNum && i < rightNum) {
          const startX = item.x - (eachSpacing * (mountOption.widthRatio || 0 || 0)) / 2;
          const height = opts.height! - item.y - opts.area![2];
          context.beginPath();
          let fillColor = item.color || series[i].color;
          const strokeColor = item.color || series[i].color;

          if (mountOption.linearType !== 'none') {
            const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
            // 透明渐变
            if (mountOption.linearType == 'opacity') {
              grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity || 1));
              grd.addColorStop(1, hexToRgb(fillColor, 1));
            } else {
              grd.addColorStop(
                0,
                hexToRgb((mountOption.customColor || [])[series[i].linearIndex || 0], mountOption.linearOpacity || 1)
              );
              grd.addColorStop(
                mountOption.colorStop || 0,
                hexToRgb(
                  (mountOption.customColor || [])[series[i].linearIndex || 0],
                  mountOption.linearOpacity || 1
                )
              );
              grd.addColorStop(1, hexToRgb(fillColor, 1));
            }
            fillColor = grd;
          }

          context.moveTo(startX, zeroPoints);
          context.bezierCurveTo(item.x - item.width || 0 / 4, zeroPoints, item.x - item.width || 0 / 4, item.y, item.x, item.y);
          context.bezierCurveTo(
            item.x + item.width || 0 / 4,
            item.y,
            item.x + item.width || 0 / 4,
            zeroPoints,
            startX + item.width || 0,
            zeroPoints
          );
          context.setStrokeStyle(strokeColor);
          context.setFillStyle(fillColor);

          if ((mountOption.borderWidth || 0) > 0) {
            context.setLineWidth((mountOption.borderWidth || 0) * opts.pix);
            context.stroke();
          }
          context.fill();
        }
      }
      break;

    case 'sharp':
      for (let i = 0; i < points.length; i++) {
        const item = points[i];
        if (item !== null && i > leftNum && i < rightNum) {
          const startX = item.x - (eachSpacing * (mountOption.widthRatio || 0 || 0)) / 2;
          const height = opts.height! - item.y - opts.area![2];
          context.beginPath();
          let fillColor = item.color || series[i].color;
          const strokeColor = item.color || series[i].color;

          if (mountOption.linearType !== 'none') {
            const grd = context.createLinearGradient(startX, item.y, startX, zeroPoints);
            // 透明渐变
            if (mountOption.linearType == 'opacity') {
              grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity || 1));
              grd.addColorStop(1, hexToRgb(fillColor, 1));
            } else {
              grd.addColorStop(
                0,
                hexToRgb((mountOption.customColor || [])[series[i].linearIndex || 0], mountOption.linearOpacity || 1)
              );
              grd.addColorStop(
                mountOption.colorStop || 0,
                hexToRgb(
                  (mountOption.customColor || [])[series[i].linearIndex || 0],
                  mountOption.linearOpacity || 1
                )
              );
              grd.addColorStop(1, hexToRgb(fillColor, 1));
            }
            fillColor = grd;
          }

          context.moveTo(startX, zeroPoints);
          context.quadraticCurveTo(item.x - 0, zeroPoints - height / 4, item.x, item.y);
          context.quadraticCurveTo(item.x + 0, zeroPoints - height / 4, startX + item.width || 0, zeroPoints);
          context.setStrokeStyle(strokeColor);
          context.setFillStyle(fillColor);

          if ((mountOption.borderWidth || 0) > 0) {
            context.setLineWidth((mountOption.borderWidth || 0) * opts.pix);
            context.stroke();
          }
          context.fill();
        }
      }
      break;
  }

  if (opts.dataLabel !== false && process === 1) {
    const ranges = opts.chartData?.yAxisData?.ranges ? [].concat(opts.chartData.yAxisData.ranges[0]) : [];
    const minRange = ranges.pop();
    const maxRange = ranges.shift();
    const points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, zeroPoints);
    drawMountPointText(points, series, config, context, opts, zeroPoints);
  }

  context.restore();

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

/**
 * 绘制条形图数据点
 * @param series 系列数据
 * @param opts 图表配置
 * @param config UCharts配置
 * @param context Canvas上下文
 * @returns 计算结果
 */
export function drawBarDataPoints(
  series: SeriesItem[],
  opts: ChartOptions,
  config: UChartsConfig,
  context: CanvasContext
): ColumnResult {
  const yAxisPoints: number[] = [];
  const eachSpacing = (opts.height - opts.area![0] - opts.area![2]) / (opts.categories?.length || 1);

  for (let i = 0; i < (opts.categories?.length || 0); i++) {
    yAxisPoints.push(opts.area![0] + eachSpacing / 2 + eachSpacing * i);
  }

  const columnOption = assign(
    {},
    {
      type: 'group',
      width: eachSpacing / 2,
      meterBorder: 4,
      meterFillColor: '#FFFFFF',
      barBorderCircle: false,
      barBorderRadius: [],
      seriesGap: 2,
      linearType: 'none',
      linearOpacity: 1,
      customColor: [],
      colorStop: 0,
    },
    opts.extra?.bar
  );

  const calPoints: any[] = [];

  context.save();
  let leftNum = -2;
  let rightNum = yAxisPoints.length + 2;

  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {
    drawBarToolTipSplitArea(opts.tooltip.offset.y, opts, config, context, eachSpacing);
  }

  columnOption.customColor = fillCustomColor(
    columnOption.linearType || 'none',
    columnOption.customColor || [],
    series,
    config
  );

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

    switch (columnOption.type) {
      case 'group':
        let points = getBarDataPoints(data as number[], minRange, maxRange, yAxisPoints, eachSpacing, opts, config);
        const tooltipPoints = getBarStackDataPoints(
          data as number[],
          minRange,
          maxRange,
          yAxisPoints,
          eachSpacing,
          opts,
          config,
          seriesIndex,
          series
        );
        calPoints.push(tooltipPoints);
        points = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);

        for (let i = 0; i < points.length; i++) {
          const item = points[i];
          if (item !== null && i > leftNum && i < rightNum) {
            const startX = opts.area![3];
            const startY = item.y - item.width || 0 / 2;
            const height = item.height;
            context.beginPath();
            let fillColor = item.color || eachSeries.color;
            const strokeColor = item.color || eachSeries.color;

            if (columnOption.linearType !== 'none') {
              const grd = context.createLinearGradient(startX, item.y, item.x, item.y);
              // 透明渐变
              if (columnOption.linearType == 'opacity') {
                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity || 1));
                grd.addColorStop(1, hexToRgb(fillColor, 1));
              } else {
                grd.addColorStop(
                  0,
                  hexToRgb((columnOption.customColor || [])[eachSeries.linearIndex || 0], columnOption.linearOpacity || 1)
                );
                grd.addColorStop(
                  columnOption.colorStop || 0,
                  hexToRgb(
                    (columnOption.customColor || [])[eachSeries.linearIndex || 0],
                    columnOption.linearOpacity || 1
                  )
                );
                grd.addColorStop(1, hexToRgb(fillColor, 1));
              }
              fillColor = grd;
            }

            // 圆角边框
            if (
              (columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) ||
              columnOption.barBorderCircle === true
            ) {
              const left = startX;
              const width = item.width || 0;
              const top = item.y - item.width || 0 / 2;
              const height = item.height;

              if (columnOption.barBorderCircle) {
                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];
              }

              let [r0, r1, r2, r3] = columnOption.barBorderRadius;
              const minRadius = Math.min(width / 2, height / 2);
              r0 = r0 > minRadius ? minRadius : r0;
              r1 = r1 > minRadius ? minRadius : r1;
              r2 = r2 > minRadius ? minRadius : r2;
              r3 = r3 > minRadius ? minRadius : r3;
              r0 = r0 < 0 ? 0 : r0;
              r1 = r1 < 0 ? 0 : r1;
              r2 = r2 < 0 ? 0 : r2;
              r3 = r3 < 0 ? 0 : r3;

              context.arc(left + r3, top + r3, r3, -Math.PI, -Math.PI / 2);
              context.arc(item.x - r0, top + r0, r0, -Math.PI / 2, 0);
              context.arc(item.x - r1, top + width - r1, r1, 0, Math.PI / 2);
              context.arc(left + r2, top + width - r2, r2, Math.PI / 2, Math.PI);
            } else {
              context.moveTo(startX, startY);
              context.lineTo(item.x, startY);
              context.lineTo(item.x, startY + item.width || 0);
              context.lineTo(startX, startY + item.width || 0);
              context.lineTo(startX, startY);
            }

            context.setFillStyle(fillColor);
            context.closePath();
            context.fill();
          }
        }
        break;
    }
  });

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

      const points = getBarDataPoints(data as number[], minRange, maxRange, yAxisPoints, eachSpacing, opts, config);
      const fixedPoints = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);
      drawBarPointText(fixedPoints, eachSeries, config, context, opts);
    });
  }

  context.restore();

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

/**
 * 绘制柱状图数据点文本
 * @param points 数据点数组
 * @param series 系列数据
 * @param config UCharts配置
 * @param context Canvas上下文
 * @param opts 图表配置
 */
export function drawColumePointText(
  points: (Point | null)[],
  series: any,
  config: UChartsConfig,
  context: CanvasContext,
  opts: ChartOptions
): void {
  const data = series.data;
  const textOffset = series.textOffset || 0;
  const Position = opts.extra?.column?.labelPosition || 'outside';

  points.forEach(function (item: any, index: number) {
    if (item !== null) {
      context.beginPath();
      const fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
      context.setFontSize(fontSize);
      context.setFillStyle(series.textColor || opts.fontColor);

      let value = data[index];
      if (typeof data[index] === 'object' && data[index] !== null) {
        if (Array.isArray(data[index])) {
          value = data[index][1];
        } else {
          value = data[index].value;
        }
      }

      const formatVal = series.formatter ? series.formatter(value, index, series, opts) : value;
      context.setTextAlign('center');
      let startY = item.y - 4 * opts.pix + textOffset * opts.pix;

      if (item.y > series.zeroPoints) {
        startY = item.y + textOffset * opts.pix + fontSize;
      }

      if (Position == 'insideTop') {
        startY = item.y + fontSize + textOffset * opts.pix;
        if (item.y > series.zeroPoints) {
          startY = item.y - textOffset * opts.pix - 4 * opts.pix;
        }
      }

      if (Position == 'center') {
        startY = item.y + textOffset * opts.pix + (opts.height! - opts.area![2] - item.y + fontSize) / 2;
        if (series.zeroPoints < opts.height! - opts.area![2]) {
          startY = item.y + textOffset * opts.pix + (series.zeroPoints - item.y + fontSize) / 2;
        }
        if (item.y > series.zeroPoints) {
          startY = item.y - textOffset * opts.pix - (item.y - series.zeroPoints - fontSize) / 2;
        }
        if (opts.extra?.column?.type == 'stack') {
          startY = item.y + textOffset * opts.pix + (item.y0 - item.y + fontSize) / 2;
        }
      }

      if (Position == 'bottom') {
        startY = opts.height! - opts.area![2] + textOffset * opts.pix - 4 * opts.pix;
        if (series.zeroPoints < opts.height! - opts.area![2]) {
          startY = series.zeroPoints + textOffset * opts.pix - 4 * opts.pix;
        }
        if (item.y > series.zeroPoints) {
          startY = series.zeroPoints - textOffset * opts.pix + fontSize + 2 * opts.pix;
        }
        if (opts.extra?.column?.type == 'stack') {
          startY = item.y0 + textOffset * opts.pix - 4 * opts.pix;
        }
      }

      context.fillText(String(formatVal), item.x, startY);
      context.closePath();
      context.stroke();
      context.setTextAlign('left');
    }
  });
}

/**
 * 绘制堆叠柱状图/山峰图数据点文本
 * @param points 数据点数组
 * @param series 系列数据
 * @param config UCharts配置
 * @param context Canvas上下文
 * @param opts 图表配置
 * @param zeroPoints 零点坐标
 */
export function drawMountPointText(
  points: any[],
  series: SeriesItem[],
  config: UChartsConfig,
  context: CanvasContext,
  opts: ChartOptions,
  zeroPoints: number
): void {
  points.forEach(function (item: any, index: number) {
    if (item !== null) {
      context.beginPath();
      const fontSize = series[index].textSize ? series[index].textSize * opts.pix : config.fontSize;
      context.setFontSize(fontSize);
      context.setFillStyle(series[index].textColor || opts.fontColor);

      let value = item.value;
      const formatVal = series[index].formatter
        ? series[index].formatter(value, index, series, opts)
        : value;

      context.setTextAlign('center');
      let startY = item.y - 4 * opts.pix + (series[index].textOffset || 0) * opts.pix;

      if (item.y > zeroPoints) {
        startY = item.y + (series[index].textOffset || 0) * opts.pix + fontSize;
      }

      context.fillText(String(formatVal), item.x, startY);
      context.closePath();
      context.stroke();
      context.setTextAlign('left');
    }
  });
}

/**
 * 绘制条形图数据点文本
 * @param points 数据点数组
 * @param series 系列数据
 * @param config UCharts配置
 * @param context Canvas上下文
 * @param opts 图表配置
 */
export function drawBarPointText(
  points: (Point | null)[],
  series: any,
  config: UChartsConfig,
  context: CanvasContext,
  opts: ChartOptions
): void {
  const data = series.data;
  const textOffset = series.textOffset || 0;

  points.forEach(function (item: any, index: number) {
    if (item !== null) {
      context.beginPath();
      const fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;
      context.setFontSize(fontSize);
      context.setFillStyle(series.textColor || opts.fontColor);

      let value = data[index];
      if (typeof data[index] === 'object' && data[index] !== null) {
        value = data[index].value;
      }

      const formatVal = series.formatter ? series.formatter(value, index, series, opts) : value;
      context.setTextAlign('left');
      let startX = item.x + 4 * opts.pix + textOffset * opts.pix;

      if (item.x < series.zeroPoints) {
        startX = item.x - textOffset * opts.pix - fontSize;
        context.setTextAlign('right');
      }

      context.fillText(String(formatVal), startX, item.y + fontSize / 2 - 3);
      context.closePath();
      context.stroke();
    }
  });
}
