/**
 * view 中缓存 scale 的类
 */
import { deepMix, each, get, isNumber, last } from '@antv/util';
import { Scale, Coordinate } from '../../dependents';
import { Data, LooseObject, ScaleOption, ViewCfg } from '../../interface';
import { createScaleByField, syncScale, getDefaultCategoryScaleRange } from '../../util/scale';

/** @ignore */
interface ScaleMeta {
  readonly key: string;
  readonly scale: Scale;
  scaleDef: ScaleOption;
  syncKey?: string;
}

/** @ignore */
export class ScalePool {
  /** 所有的 scales */
  private scales = new Map<string, ScaleMeta>();
  /** 需要同步的 scale 分组， key: scaleKeyArray */
  private syncScales = new Map<string, string[]>();

  /**
   * 创建 scale
   * @param field
   * @param data
   * @param scaleDef
   * @param key
   */
  public createScale(field: string, data: Data, scaleDef: ScaleOption, key: string): Scale {
    let finalScaleDef = scaleDef;

    const cacheScaleMeta = this.getScaleMeta(key);
    if (data.length === 0 && cacheScaleMeta) {
      // 在更新过程中数据变为空，同时 key 对应的 scale 已存在则保持 scale 同类型
      const cacheScale = cacheScaleMeta.scale;
      const cacheScaleDef: LooseObject = {
        type: cacheScale.type,
      };
      if (cacheScale.isCategory) {
        // 如果是分类类型，保持 values
        cacheScaleDef.values = cacheScale.values;
      }
      finalScaleDef = deepMix(cacheScaleDef, cacheScaleMeta.scaleDef, scaleDef);
    }

    const scale = createScaleByField(field, data, finalScaleDef);

    // 缓存起来
    this.cacheScale(scale, scaleDef, key);

    return scale;
  }

  /**
   * 同步 scale
   */
  public sync(coordinate: Coordinate, theme: ViewCfg['theme']) {
    // 对于 syncScales 中每一个 syncKey 下面的 scale 数组进行同步处理
    this.syncScales.forEach((scaleKeys: string[], syncKey: string) => {
      // min, max, values, ranges
      let min = Number.MAX_SAFE_INTEGER;
      let max = Number.MIN_SAFE_INTEGER;
      const values = [];

      // 1. 遍历求得最大最小值，values 等
      each(scaleKeys, (key: string) => {
        const scale = this.getScale(key);

        max = isNumber(scale.max) ? Math.max(max, scale.max) : max;
        min = isNumber(scale.min) ? Math.min(min, scale.min) : min;

        // 去重
        each(scale.values, (v: any) => {
          if (!values.includes(v)) {
            values.push(v);
          }
        });
      });

      // 2. 同步
      each(scaleKeys, (key: string) => {
        const scale = this.getScale(key);

        if (scale.isContinuous) {
          scale.change({
            min,
            max,
            values,
          });
        } else if (scale.isCategory) {
          let range = scale.range;
          const cacheScaleMeta = this.getScaleMeta(key);

          // 存在 value 值，且用户没有配置 range 配置 to fix https://github.com/antvis/G2/issues/2996
          if (values && !get(cacheScaleMeta, ['scaleDef', 'range'])) {
            // 更新 range
            range = getDefaultCategoryScaleRange(
              deepMix({}, scale, {
                values,
              }),
              coordinate,
              theme
            );
          }
          scale.change({
            values,
            range,
          });
        }
      });
    });
  }

  /**
   * 缓存一个 scale
   * @param scale
   * @param scaleDef
   * @param key
   */
  private cacheScale(scale: Scale, scaleDef: ScaleOption, key: string) {
    // 1. 缓存到 scales

    let sm = this.getScaleMeta(key);
    // 存在则更新，同时检测类型是否一致
    if (sm && sm.scale.type === scale.type) {
      syncScale(sm.scale, scale);
      sm.scaleDef = scaleDef;
      // 更新 scaleDef
    } else {
      sm = {
        key,
        scale,
        scaleDef,
      };

      this.scales.set(key, sm);
    }

    // 2. 缓存到 syncScales，构造 Record<sync, string[]> 数据结构
    const syncKey = this.getSyncKey(sm);
    sm.syncKey = syncKey; // 设置 sync 同步的 key

    // 因为存在更新 scale 机制，所以在缓存之前，先从原 syncScales 中去除 sync 的缓存引用
    this.removeFromSyncScales(key);

    // 存在 sync 标记才进行 sync
    if (syncKey) {
      // 不存在这个 syncKey，则创建一个空数组
      let scaleKeys = this.syncScales.get(syncKey);
      if (!scaleKeys) {
        scaleKeys = [];
        this.syncScales.set(syncKey, scaleKeys);
      }
      scaleKeys.push(key);
    }
  }

  /**
   * 通过 key 获取 scale
   * @param key
   */
  public getScale(key: string): Scale {
    let scaleMeta = this.getScaleMeta(key);
    if (!scaleMeta) {
      const field = last(key.split('-'));
      const scaleKeys = this.syncScales.get(field);
      if (scaleKeys && scaleKeys.length) {
        scaleMeta = this.getScaleMeta(scaleKeys[0]);
      }
    }
    return scaleMeta && scaleMeta.scale;
  }

  /**
   * 在 view 销毁的时候，删除 scale 实例，防止内存泄露
   * @param key
   */
  public deleteScale(key: string) {
    const scaleMeta = this.getScaleMeta(key);
    if (scaleMeta) {
      const { syncKey } = scaleMeta;

      const scaleKeys = this.syncScales.get(syncKey);

      // 移除同步的关系
      if (scaleKeys && scaleKeys.length) {
        const idx = scaleKeys.indexOf(key);

        if (idx !== -1) {
          scaleKeys.splice(idx, 1);
        }
      }
    }

    // 删除 scale 实例
    this.scales.delete(key);
  }

  /**
   * 清空
   */
  public clear() {
    this.scales.clear();
    this.syncScales.clear();
  }

  /**
   * 删除 sync scale 引用
   * @param key
   */
  private removeFromSyncScales(key: string) {
    this.syncScales.forEach((scaleKeys: string[], syncKey: string) => {
      const idx = scaleKeys.indexOf(key);

      if (idx !== -1) {
        scaleKeys.splice(idx, 1);

        // 删除空数组值
        if (scaleKeys.length === 0) {
          this.syncScales.delete(syncKey);
        }

        return false; // 跳出循环
      }
    });
  }

  /**
   * get sync key
   * @param sm
   */
  private getSyncKey(sm: ScaleMeta): string {
    const { scale, scaleDef } = sm;
    const { field } = scale;
    const sync = get(scaleDef, ['sync']);

    // 如果 sync = true，则直接使用字段名作为 syncKey
    return sync === true ? field : sync === false ? undefined : sync;
  }

  /**
   * 通过 key 获取 scale
   * @param key
   */
  private getScaleMeta(key: string): ScaleMeta {
    return this.scales.get(key);
  }
}
