// @ts-ignore

// eslint-disable-next-line import/no-unresolved
import { PAGInit, types, clearCache } from 'libpag-miniprogram';

import { createProxyPAGComposition } from './pag-composition';
import { createProxyPAGFile } from './pag-file';
import { createProxyPAGVIew } from './pag-view';

import type {
  TipPAGWebOptions,
  TipPAGWebCore,
  TipPAGWebLoadResult,
  TipPAGWebScaleInfo,
  TipPAGWebBaseLayerInfo,
  TipPAGWebLayerInfo,
  PAG,
  PAGTypes,
  PAGFile,
  PAGView,
  PAGComposition,
  PAGLayer,
  Vector,
} from './types';

/**
 * @description 二次封装，方便业务调用
 * @class PAGWeb
 */
class PAGWeb {
  private pag: PAG | null | undefined;
  private pagTypes: PAGTypes | null | undefined;
  private pagFile: PAGFile | null | undefined;
  private pagView: PAGView | null | undefined;
  private pagComposition: PAGComposition | null | undefined;

  constructor(options: TipPAGWebOptions) {
    this.pag = null;
    this.pagFile = null;
    this.pagView = null;
    this.pagComposition = null;

    if (options?.autoLoad) {
      this.load();
    }
  }

  /**
   * @description 加载依赖库
   * @returns Promise<TipPAGWebLoadResult>
   * @memberof PAGWeb
   */
  load(): Promise<TipPAGWebLoadResult> {
    return new Promise((resolve, reject) => {
      if (this.pag && this.pagTypes) {
        resolve({ pag: this.pag, pagTypes: this.pagTypes });
        return;
      }

      PAGInit({ locateFile: file => `/static/libpag/${file}` }) // 小程序包内路径
        .then((pag) => {
          this.pag = pag as any;
          this.pagTypes = types as any;
          resolve({ pag: this.pag, pagTypes: this.pagTypes as any });
        })
        .catch(err => reject(err));
    });
  }

  /**
   * @description 完成初始化并渲染
   * @param {HTMLCanvasElement} $canvas 挂载的 <canvas />
   * @param {File} file .pag格式的素材文件对象
   * @param {object} [options={}] 选项，同 PAGWebOptions
   * @returns Promise<TipPAGWebCore|undefined>
   * @memberof PAGWeb
   */
  async create(
    $canvas: HTMLCanvasElement,
    file: File,
    options: TipPAGWebOptions = {},
  ): Promise<TipPAGWebCore|undefined> {
    if (this.pagFile) this.pagFile.destroy();
    if (this.pagView) this.pagView.destroy();
    if (this.pagComposition) this.pagComposition.destroy();

    const pagCanvas = $canvas;
    const defaultOptions = {
      useScale: true,
      useCanvas2D: false,
      firstFrame: true,
    };
    const initOptions = {
      ...defaultOptions,
      ...options,
    };

    // 加载素材
    this.pagFile = await this.pag?.PAGFile?.load(file);
    if (!this.pagFile) return;

    // 初始化舞台
    this.pagView = (await this.pag?.PAGView?.init(this.pagFile, pagCanvas, initOptions));
    if (!this.pagView) return;

    this.pagView.setRepeatCount(0); // 设置初始画面

    // 挂载合成器
    this.pagComposition = this.pagView.getComposition();
    if (!this.pagComposition) return;

    // 二次封装
    const pagTypes = this.getPAGTypes();
    const pagView = this.getPAGView();
    const pagFile = this.getPAGFile();
    const pagComposition = this.getPAGComposition();

    // 返回
    return {
      pagTypes,
      pagView,
      pagFile,
      pagComposition,
    };
  }

  /**
   * @description 封装 load 和 create，方便直接加载后渲染
   * @param {HTMLCanvasElement} $canvas 挂载的 <canvas />
   * @param {File} file .pag格式的素材文件对象
   * @param {object} [options={}] 选项，同
   * @returns Promise<TipPAGWebCore|undefined>
   * @memberof PAGWeb
   */
  async init($canvas: HTMLCanvasElement, file: File, options: TipPAGWebOptions = {}): Promise<TipPAGWebCore|undefined> {
    await this.load();
    const pag = await this.create($canvas, file, options);
    return pag;
  }

  /**
   * 清空舞台渲染的图层
   * @description
   * https://bbs.pag.art/thread/883
   * PAGView 的 destroy 只是清除实例，并没有清除 Canvas 的内容或者从 DOM 树上移除 Canvas， 因为 Canvas 是由业务上创建的，控制权在业务上。如果你需要在动画结束后将画面清空，你有几个方法：
   * 1. 当你不需要 CanvasElement 时，你可以将 CanvasElement 从 DOM 树上移除。
   * 2. 当你还需要 CanvasElement 但希望它呈现透明状态时，你可以用 WebGL 命令手动将 Canvas 的内容清空；
   * 或者使用 pagView.pagSurface.clearAll() 将内容清空，不过需要注意 PAGView 实例上的 pagSurface 是一个私有对象。
   * @memberof PAGWeb
   */
  clearAll() {
    if (!this.pagView) {
      return;
    }

    // @ts-ignore
    this.pagView?.pagSurface?.clearAll();
  }

  /**
   * 清空占用的内存
   * @description 为避免PAG的缓存把用户缓存目录内存占用过高，建议在不需要使用PAG的时候，调用 clearCache() 方法进行缓存清理。
   * @memberof PAGWeb
   */
  clearCache() {
    clearCache();
  }

  /**
   * 获取内置类型
   * @returns PAGTypes|null
   * @memberof PAGWeb
   */
  getPAGTypes() {
    if (!this.pagTypes) return null;
    return this.pagTypes;
  }

  /**
   * 获取二次封装的 pagFile 对象
   * @returns PAGFile|null
   * @memberof PAGWeb
   */
  getPAGFile() {
    if (!this.pagFile) return null;
    return createProxyPAGVIew(this.pagFile);
  }

  /**
   * 获取二次封装的 pagView 对象
   * @returns PAGView|null
   * @memberof PAGWeb
   */
  getPAGView() {
    if (!this.pagView) return null;
    return createProxyPAGFile(this.pagView);
  }

  /**
   * 获取二次封装的 pagComposition 对象
   * @returns PAGComposition|null
   * @memberof PAGWeb
   */
  getPAGComposition() {
    if (!this.pagComposition) return null;
    return createProxyPAGComposition(this.pagComposition);
  }

  /**
   * 获取缩放信息
   * @returns {(TipPAGWebScaleInfo | undefined)}
   * @memberof PAGWeb
   */
  getScaleInfo(): TipPAGWebScaleInfo | undefined {
    const pagView = this.getPAGView();
    const pagTypes = this.getPAGTypes();
    if (!pagView || !pagTypes) return;

    const { useScale } = pagView.pagViewOptions;
    const dpr = useScale ? window.devicePixelRatio : 1; // 有开启 useScale
    const m = pagView.matrix();
    // @ts-ignore
    const mi = pagTypes.MatrixIndex;
    const scaleX = m.get(mi.a);
    const scaleY = m.get(mi.d);
    const tx = m.get(mi.tx);
    const ty = m.get(mi.ty);

    const info = {
      dpr,
      scaleX,
      scaleY,
      tx,
      ty,
    };

    console.log('[info]pag-web: getScaleInfo', info);
    return info;
  }

  /**
   * 获取自构造的图层信息，方便业务调用
   * @param {PAGLayer} layer 图层对象
   * @returns {TipPAGWebBaseLayerInfo}
   * @memberof PAGWeb
   */
  getLayerInfo(layer: PAGLayer): TipPAGWebBaseLayerInfo {
    const { right, bottom } = layer.getBounds();  // 算得是内部四个点的位置
    const width = right;
    const height = bottom;
    const uniqueID = layer.uniqueID();
    const layerType = layer.layerType();
    const layerName = layer.layerName();
    const alpha = layer.alpha();
    const visible = layer.visible();
    const editableIndex = layer.editableIndex();
    const duration = layer.duration();
    const frameRate = layer.frameRate();
    const localStartTime = layer.startTime();
    const startTime = layer.localTimeToGlobal(localStartTime);

    return {
      uniqueID,
      layerType,
      layerName,
      width,
      height,
      alpha,
      visible,
      editableIndex,
      frameRate,
      startTime,
      duration,
    };
  }

  /**
   * 获取可编辑图层集合
   * @param {types.LayerType} layerTypes 过滤指定图层类型，若传入 LayerType.Image 代表只返回图像类型的图层
   * @returns {TipPAGWebLayerInfo[]} 图层信息集合
   * @memberof PAGWeb
   */
  getEditableLayers(layerTypes: number[]): TipPAGWebLayerInfo[] {
    const editableLayers: TipPAGWebLayerInfo[] = [];
    const pagTypes = this.getPAGTypes();
    const pagFile = this.getPAGFile();
    if (!pagTypes || !pagFile) return [];

    // 为空时，默认取图片类型
    let layerTypeList = layerTypes;
    if (!layerTypes || layerTypes.length === 0) {
      layerTypeList = [pagTypes.LayerType.Image];
    }

    // 根据图层类型过滤获取
    layerTypeList.forEach((layerType: number) => {
      const indices = pagFile.getEditableIndices(layerType);
      indices.forEach((index: number) => {
        const imageLayers: Vector<PAGLayer> = pagFile.getLayersByEditableIndex(index, layerType);
        for (let j = 0; j < imageLayers.size(); j++) {
          const layer: PAGLayer = imageLayers.get(j);
          const layerInfo = this.getLayerInfo(layer);
          editableLayers.push({
            layer,
            ...layerInfo,
          });
        }
      });
    });

    return editableLayers;
  }

  /**
   * 通过图层名字获取图层，并返回匹配集合
   * @param {string} layerName 图层名称
   * @returns {TipPAGWebLayerInfo[]}
   * @memberof PAGWeb
   */
  getLayersByName(layerName: string): TipPAGWebLayerInfo[] {
    const pagComposition = this.getPAGComposition();
    if (!pagComposition) return [];

    const pagLayerList = pagComposition.getLayersByName(layerName);
    const layerList = pagLayerList.map((layer: PAGLayer) => {
      console.log(`test getLayersByName: layerName: ${layer.layerName()}`);
      const layerInfo = this.getLayerInfo(layer);
      return {
        layer,
        ...layerInfo,
      };
    });

    return layerList;
  }

  /**
   * 获取事件响应时坐标位置所在的图层集合
   * @param {Event} { event } 事件对象
   * @returns {TipPAGWebLayerInfo[]}
   * @memberof PAGWeb
   */
  getLayersUnderPoint({ event }: { event: MouseEvent }): TipPAGWebLayerInfo[] {
    const pagComposition = this.getPAGComposition();
    if (!pagComposition) return [];

    const scaleInfo = this.getScaleInfo();
    if (!scaleInfo) return [];

    const { dpr, scaleX, scaleY, tx, ty } = scaleInfo;
    const localX = (event.offsetX * dpr) / scaleX + (tx * -1) / scaleX;
    const localY = (event.offsetY * dpr) / scaleY + (ty * -1) / scaleY;
    const pagLayers = pagComposition.getLayersUnderPoint(localX, localY);

    // console.log('[info]点击位置', event.offsetX, event.offsetY);
    console.log('[info]计算后的画布点击位置', localX, localY);
    // console.log('[info]scaleInfo', scaleInfo);

    // 添加常用的图层信息
    const paglayerList = pagLayers.map((layer: PAGLayer) => {
      const layerInfo = this.getLayerInfo(layer);
      return {
        layer,
        ...layerInfo,
      };
    });

    // 打印日志
    paglayerList.forEach((layer: PAGLayer) => {
      console.log(`
      舞台的点击响应位置${localX}, ${localY}
      点击中了图层: ${layer.layerName}
      图层的唯一ID: ${layer.uniqueID}
      图层的可编辑ID（同类型公用一个ID）: ${layer.editableIndex}
      `, layer);
    });

    return paglayerList;
  }

  /**
   * 替换图像
   * @param {number} layerIndex 图层的可编辑下标
   * @param {File} file 待替换的图像文件对象
   * @memberof PAGWeb
   */
  async replaceImage(layerIndex: number, file: File) {
    const pagView = this.getPAGView();
    const pagComposition = this.getPAGComposition();
    if (!pagView || !pagComposition) return;

    const pagImage = await this.pag?.PAGImage?.fromFile(file);
    if (!pagImage) return;

    pagComposition.replaceImage(layerIndex, pagImage);
    await pagView.flush();
    pagImage.destroy();
  }

  /**
   * 替换文本
   * @param {number} editableTextIndex 图层的可编辑下标
   * @param {object} texture 文本属性对象，支持属性：https://pag.art/apis/web/classes/types.TextDocument.html
   * @memberof PAGWeb
   */
  async replaceText(editableTextIndex: number, texture: Record<string, any>) {
    const pagFile = this.getPAGFile();
    const pagView = this.getPAGView();
    if (!pagView || !pagFile) return;

    const textDocument = pagFile.getTextData(editableTextIndex);
    console.log('[info]pag-web-api: 当前文本', textDocument);

    // 设置文本属性：https://pag.art/apis/web/classes/types.TextDocument.html
    Object.keys(texture).forEach((key) => {
      const value = texture[key];
      textDocument[key] = value;
    });

    // 更新文本
    pagFile.replaceText(editableTextIndex, textDocument);
    await pagView.flush();
  }

  /**
   * 对当前舞台进行截图
   * @returns {dataUrl｜undefined} base64格式的图像数据
   * @memberof PAGWeb
   */
  async makeSnapshot() {
    const pagView = this.getPAGView();
    if (!pagView) return;

    const bitmap = await pagView.makeSnapshot();
    if (bitmap) {
      const snapshotCanvas = document.createElement('canvas');
      snapshotCanvas.width = bitmap.width;
      snapshotCanvas.height = bitmap.height;
      const ctx = snapshotCanvas.getContext('2d');
      if (ctx) {
        ctx.drawImage(bitmap, 0, 0);
        const dataUrl = snapshotCanvas.toDataURL();
        return dataUrl;
      }

      return;
    }

    return;
  }
}

export {
  PAGWeb,
};
