import type { ImageOptions, PswpInterface } from '../../types';
import type { EditorInterface, NodeInterface } from '@aomao/engine';
import { $, File, isEngine, escape, random, getExtensionName, sanitizeUrl, Tooltip, isMobile, Resizer, CardType } from '@aomao/engine';
import 'cropperjs/dist/cropper.css';
import Cropper from 'cropperjs';
import PhotoSwipe from 'photoswipe';
import { ImageValue } from '..';
import Pswp from '../pswp';
import './index.css';

export type Status = 'uploading' | 'done' | 'error';

export type Size = {
  width: number;
  height: number;
  naturalWidth: number;
  naturalHeight: number;
};

export type Options = {
  /**
   * 卡片根节点
   */
  root: NodeInterface;
  /**
   * 容器
   */
  container: NodeInterface;
  /**
   * 状态
   * uploading 上传中
   * done 上传完成
   * error 错误
   */
  status: Status;
  /**
   * 图标链接
   */
  src: string;
  /**
   * 标题
   */
  alt?: string;
  /**
   * 链接
   */
  link?: {
    href: string;
    target?: string;
  };
  display?: CardType;
  /**
   * 错误消息
   */
  message?: string;
  /**
   * 样式名称，多个以空格隔空
   */
  className?: string;
  /**
   * 图片大小
   */
  size?: Size;
  /**
   * 上传进度
   */
  percent?: number;
  /**
   * 图片渲染前调用
   * @param status 状态 
   * @param src 图片地址
   * @returns 图片地址
   */
  onBeforeRender?: (status: 'uploading' | 'done', src: string, editor: EditorInterface) => string;
  onChangeSrc?: (url?: string, loaded?: boolean) => void;
  onChange?: (size?: Size, loaded?: boolean) => void;
  onError?: () => void;
  onLoad?: () => void;
  enableResizer?: boolean;
  maxHeight?: number | undefined;
};

export const winPixelRatio = window.devicePixelRatio;
let pswp: PswpInterface | undefined = undefined;
class Image {
  private editor: EditorInterface;
  options: Options;
  root: NodeInterface;
  private progress: NodeInterface;
  private image: NodeInterface;
  private detail: NodeInterface;
  private meta: NodeInterface;
  private maximize: NodeInterface;
  private bg: NodeInterface;
  resizer?: Resizer;
  private pswp: PswpInterface;
  src: string;
  status: Status;
  size: Size;
  maxWidth: number;
  maxHeight: number | undefined;
  rate = 1;
  isLoad = false;
  message: string | undefined;
  cropper: Cropper | undefined;

  constructor(editor: EditorInterface, options: Options) {
    this.editor = editor;
    this.options = options;
    this.src = this.options.src;
    this.size = this.options.size || {
      width: 0,
      height: 0,
      naturalHeight: 0,
      naturalWidth: 0,
    };
    this.maxHeight = this.options.maxHeight;
    this.status = this.options.status;
    this.root = $(this.renderTemplate());
    this.progress = this.root.find('.data-image-progress');
    this.image = this.root.find('img');
    this.detail = this.root.find('.data-image-detail');
    this.meta = this.root.find('.data-image-meta');
    this.maximize = this.root.find('.data-image-maximize');
    this.bg = this.root.find('.data-image-bg');
    this.maxWidth = this.getMaxWidth();
    this.pswp = pswp || new Pswp(editor);
    this.message = this.options.message;

    pswp = this.pswp;
  }

  renderTemplate(message?: string) {
    const { link, percent, className, onBeforeRender } = this.options;

    if (this.status === 'error') {
      return `<span class="data-image-error" style="max-width:${this.maxWidth}px">
				<span class="data-icon data-icon-error"></span>
				${message || this.options.message}
				<span class="data-icon data-icon-copy"></span>
			</span>`;
    }
    const src = onBeforeRender ? onBeforeRender(this.status, this.options.src, this.editor) : this.options.src;
    const progress = `<span class="data-image-progress">
												<i class="data-anticon">
													<svg viewBox="0 0 1024 1024" class="data-anticon-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true">
														<path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path>
													</svg>
												</i>
												<span class="percent">${percent || 0}%</span>
											</span>`;

    const alt = escape(this.options.alt || '');
    const attr = alt ? ` alt="${alt}" title="${alt}" ` : '';
    //加上 data-drag-image 样式可以拖动图片
    let img = `<img src="${sanitizeUrl(src)}" class="${className || ''} data-drag-image" ${attr}/>`;
    //只读渲染加载链接
    if (link && !isEngine(this.editor)) {
      const target = link.target || '_blank';
      img = `<a href="${sanitizeUrl(link.href)}" target="${target}">${img}</a>`;
    }
    //全屏图标
    const maximize = `<span class="data-image-maximize" style="display: none;"><span class="data-icon data-icon-maximize"></span></span>`;

    return `
			<span class="data-image">
				<span class="data-image-content data-image-loading">
					<span class="data-image-detail">
						<span class="data-image-meta">
							${img}
							${progress}
							<span class="data-image-bg"></span>
							${maximize}
						</span>
					</span>
				</span>
			</span>`;
  }

  bindErrorEvent(node: NodeInterface) {
    const editor = this.editor;
    const copyNode = node.find('.data-icon-copy');
    copyNode.on('mouseenter', () => {
      Tooltip.show(copyNode, editor.language.get('image', 'errorMessageCopy').toString());
    });
    copyNode.on('mouseleave', () => {
      Tooltip.hide();
    });
    copyNode.on('click', (event: MouseEvent) => {
      event.stopPropagation();
      event.preventDefault();
      Tooltip.hide();
      editor.clipboard.copy(this.message || this.options.message || 'Error message');
      editor.messageSuccess('copy', editor.language.get('copy', 'success').toString());
    });
  }

  setProgressPercent(percent: number) {
    this.progress.find('.percent').html(`${percent}%`);
  }

  imageLoadCallback() {
    const editor = this.editor;
    const root = editor.card.closest(this.root);
    if (!root || this.status === 'uploading') {
      return;
    }

    if (this.status === 'done') {
      const contentNode = this.root.find('.data-image-content');
      contentNode.addClass('data-image-loaded');
      contentNode.removeClass('data-image-loading');
    }

    const img = this.image.get<HTMLImageElement>();
    if (!img) return;
    const { naturalWidth, naturalHeight } = img;
    this.rate = naturalHeight / naturalWidth;

    this.size.naturalWidth = naturalWidth;
    this.size.naturalHeight = naturalHeight;

    if (!this.size.width) this.size.width = naturalWidth;
    if (!this.size.height) this.size.height = naturalHeight;

    this.resetSize();

    this.image.css('visibility', 'visible');
    this.detail.css('height', '');
    this.detail.css('width', '');
    const { onChange } = this.options;
    if (isEngine(editor) && onChange) {
      onChange(this.size, true);
    }
    window.removeEventListener('resize', this.onWindowResize);
    window.addEventListener('resize', this.onWindowResize);
    editor.off('editor:resize', this.onWindowResize);
    editor.on('editor:resize', this.onWindowResize);
    // 重新调整拖动层尺寸
    if (this.resizer) {
      this.resizer.setSize(img.clientWidth, img.clientHeight);
    }
    this.isLoad = true;
    if (this.options.onLoad) {
      this.options.onLoad();
    }
  }

  onWindowResize = () => {
    if (!isEngine(this.editor)) return;
    this.maxWidth = this.getMaxWidth();
    this.resetSize();
    const image = this.image.get<HTMLElement>();
    if (!image) return;
    const { clientWidth, clientHeight } = image;
    if (this.resizer) {
      this.resizer.maxWidth = this.maxWidth;
      this.resizer.setSize(clientWidth, clientHeight);
    }
  };

  imageLoadError() {
    if (this.status === 'uploading') return;
    this.status = 'error';
    const { container } = this.options;
    container.empty();
    container.append(this.renderTemplate(this.editor.language.get('image', 'loadError').toString()));
    this.detail.css('width', '');
    this.detail.css('height', '');
    this.bindErrorEvent(container);
    const { onError } = this.options;
    if (onError) onError();
    this.isLoad = true;
  }

  getMaxWidth(node: NodeInterface = this.options.root) {
    const block = this.editor.block.closest(node).get<HTMLElement>();
    if (!block) return 0;
    return block.clientWidth - 6;
  }

  /**
   * 重置大小
   */
  resetSize() {
    this.meta.css({
      'background-color': '',
      width: '',
      //height: "",
    });

    this.image.css({
      width: '',
      //height: "",
    });

    const img = this.image.get<HTMLImageElement>();
    if (!img) return;

    let { width, height } = this.size;

    if (!height) {
      height = Math.round(this.rate * width);
    } else if (!width) {
      width = Math.round(height / this.rate);
    } else if (width && height) {
      // 修正非正常的比例
      height = Math.round(this.rate * width);
      this.size.height = height;
    } else {
      const { clientWidth, clientHeight } = img;
      width = clientWidth;
      height = clientHeight;
      const { naturalWidth, naturalHeight } = this.size;
      // fix：svg 图片宽度 300px 问题
      if (this.isSvg() && naturalWidth && naturalHeight) {
        width = naturalWidth;
        height = naturalHeight;
      }
    }

    if (width > this.maxWidth) {
      width = this.maxWidth;
      height = Math.round(width * this.rate);
    }
    if (this.options.enableResizer === false) {
      this.image.css('width', '');
    } else {
      this.image.css('width', `${width}px`);
      //this.image.css("height", `${height}px`);
    }
  }

  changeSize(width: number, height: number) {
    if (width < 24) {
      width = 24;
      height = width * this.rate;
    }

    if (width > this.maxWidth) {
      width = this.maxWidth;
      height = width * this.rate;
    }

    if (height < 24) {
      height = 24;
      width = height / this.rate;
    }

    width = Math.round(width);
    height = Math.round(height);
    this.size.width = width;
    this.size.height = height;
    this.image.css({
      width: `${width}px`,
      //height: `${height}px`,
    });
    const { onChange } = this.options;
    if (onChange) onChange(this.size);

    this.destroyEditor();
    this.renderEditor();
  }

  changeUrl(url: string) {
    if (this.src !== url) {
      this.src = url;
      this.isLoad = false;
      this.image.attributes('src', this.getSrc());
    }
  }

  getSrc = () => {
    const { onBeforeRender } = this.options;
    return onBeforeRender && this.status !== 'error' ? onBeforeRender(this.status, this.src, this.editor) : this.src;
  };

  isSvg() {
    return this.src.split('?')[0].endsWith('.svg') || this.src.startsWith('data:image/svg+xml');
  }

  openZoom = (event: MouseEvent | TouchEvent) => {
    event.preventDefault();
    event.stopPropagation();
    const editor = this.editor;
    const imageArray: PhotoSwipe.Item[] = [];
    const cardRoot = editor.card.closest(this.root);
    let rootIndex = 0;

    editor.container
      .find(`[data-card-key="image"]`)
      .toArray()
      .filter(image => {
        return image.find('img').length > 0;
      })
      .forEach((imageNode, index) => {
        const card = editor.card.find<ImageValue>(imageNode);
        const value = card?.getValue();
        if (!card || !value) return;
        const image = card.getCenter().find('img');
        const imageWidth = parseInt(image.css('width'));
        const imageHeight = parseInt(image.css('height'));
        const size = value.size;
        const naturalWidth = size ? size.naturalWidth || this.size.naturalWidth : imageWidth * winPixelRatio;
        const naturalHeight = size ? size.naturalHeight || this.size.naturalHeight : imageHeight * winPixelRatio;
        let src = value['src'];
        const { onBeforeRender } = this.options;
        if (onBeforeRender) src = onBeforeRender('done', src, this.editor);
        const msrc = image.attributes('src');
        imageArray.push({
          src,
          msrc,
          w: naturalWidth,
          h: naturalHeight,
        });
        if (cardRoot?.equal(imageNode)) {
          rootIndex = index;
        }
      });
    this.pswp.open(imageArray, rootIndex);
  };

  closeZoom() {
    this.pswp?.close();
  }

  cropImage() {
    if (this.status === 'done') {
      this.destroyEditor();
    }
    const img = this.image.get<HTMLElement>();
    this.cropper = new Cropper(img as HTMLImageElement, { autoCropArea: 1 });
  }

  async cropImageSave() {
    if (this.cropper) {
      const canvas = this.cropper.getCroppedCanvas();
      const base64url = canvas?.toDataURL();
      /* 直接使用 base64赋值
        this.changeUrl(base64url);
        onChangeSrc && onChangeSrc(base64url);
      */
      // 重新上传用后台返回的url 展示
      const fileBlob = this.dataURIToFile(base64url);
      const ext = getExtensionName(fileBlob);
      const name = ext ? 'image.'.concat(ext) : 'image';
      const file: File = new globalThis.File([fileBlob], name, { type: 'image/jpeg' });
      file.uid = new Date().getTime() + '-' + random();
      const src = await this.uploadImage([file]);
      const { onChangeSrc } = this.options;
      this.changeUrl(src);
      onChangeSrc && onChangeSrc(src);
      this.size.naturalWidth = canvas?.width;
      this.size.naturalHeight = canvas?.height;
      const { width, height } = this.cropper?.getCropBoxData();
      this.changeSize(width, height);
      this.cropper.destroy();
      this.cropper = undefined;
    }
  }

  async rotateImage() {
    console.log("rotateImage", this.editor)
    const canvas = document.createElement('canvas');
    // 默认旋转90° 将图片的初始宽高交换赋值给画布的宽高
    canvas.width = this.size.naturalHeight;
    canvas.height = this.size.naturalWidth;
    const ctx = canvas.getContext('2d');
    ctx?.save();
    // 从坐标 0,0 渲染canvas.width, canvas.height的画布
    ctx?.fillRect(0, 0, canvas.width, canvas.height);
    // 旋转90°
    ctx?.rotate(90 * Math.PI / 180);
    // 将图片渲染到画布
    const img = this.image.get<HTMLElement>();
    ctx?.drawImage(img as HTMLImageElement, 0, -canvas.width);
    // 生成base64url
    const base64url = canvas.toDataURL('image/png');
    /* 直接使用 base64赋值
      this.changeUrl(base64url);
      onChangeSrc && onChangeSrc(base64url);
    */
    // 重新上传用后台返回的url 展示
    const fileBlob = this.dataURIToFile(base64url);
    const ext = getExtensionName(fileBlob);
    const name = ext ? 'image.'.concat(ext) : 'image';
    const file: File = new globalThis.File([fileBlob], name, { type: 'image/jpeg' });
    file.uid = new Date().getTime() + '-' + random();
    const src = await this.uploadImage([file]);
    // 修改图片地址
    const { onChangeSrc } = this.options;
    this.changeUrl(src);
    onChangeSrc && onChangeSrc(src);
    // 计算旋转后的宽高
    const size = {
      width: this.size.height,
      height: this.size.width,
      naturalWidth: this.size.naturalHeight,
      naturalHeight: this.size.naturalWidth
    };
    this.size.naturalWidth = size.naturalWidth;
    this.size.naturalHeight = size.naturalHeight;
    // 修改图片宽高
    this.changeSize(size.width, size.height);
  }

  dataURIToFile(dataURI: string) {
    let byteString;

    if (dataURI.split(',')[0].indexOf('base64') >= 0) {
      byteString = atob(dataURI.split(',')[1]);
    } else {
      byteString = unescape(dataURI.split(',')[1]);
    }
    // separate out the mime component
    const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to a typed array

    const ia = new Uint8Array(byteString.length);

    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }

    return new Blob([ia], {
      type: mimeString,
    });
  }

  uploadImage(files: Array<File>): Promise<string> {
    const { request, card } = this.editor;
    const imageUploaderPlugin = this.editor.plugin.findPlugin<ImageOptions>('image-uploader');
    const { action, crossOrigin, headers } = imageUploaderPlugin?.options.file;
    const parse = imageUploaderPlugin?.options.parse;
    return new Promise((resolve, reject) => {
      return request.upload(
        {
          url: action,
          crossOrigin,
          headers,
          onSuccess: (response, file) => {
            const result = parse(response);
            return resolve(result.data);
          },
          onError: (error, file) => {
            return reject(error);
          },
        },
        files,
        'file'
      );
    })
  }

  renderEditor() {
    const img = this.image.get<HTMLElement>();
    if (!img) return;
    const { clientWidth, clientHeight } = img;

    if (!clientWidth || !clientHeight) {
      return;
    }
    const editor = this.editor;
    this.maxWidth = this.getMaxWidth();
    this.rate = clientHeight / clientWidth;
    if (isMobile || !isEngine(editor) || editor.readonly) return;
    if (this.options.enableResizer === false) {
      return;
    }
    // 拖动调整图片大小
    const resizer = new Resizer({
      imgUrl: this.getSrc(),
      width: clientWidth,
      height: clientHeight,
      rate: this.rate,
      maxWidth: this.maxWidth,
      onChange: ({ width, height }) => this.changeSize(width, height),
    });
    const resizerNode = resizer.render();
    this.root.find('.data-image-detail').append(resizerNode);
    this.resizer = resizer;
    this.resizer.on('dblclick', this.openZoom);
  }

  destroyEditor() {
    this.resizer?.off('dblclick', this.openZoom);
    this.resizer?.destroy();
  }

  destroy() {
    window.removeEventListener('resize', this.onWindowResize);
    this.editor.off('editor:resize', this.onWindowResize);
    this.destroyEditor();
    this.image.off('click', this.openZoom);
    this.image.off('dblclick', this.openZoom);
    this.maximize.off('click', this.openZoom);
  }

  focus = () => {
    if (!isEngine(this.editor)) {
      return;
    }
    this.root.addClass('data-image-active');
    if (this.status === 'done') {
      this.destroyEditor();
      this.renderEditor();
    }
  };

  blur = () => {
    if (!isEngine(this.editor)) {
      return;
    }
    this.root.removeClass('data-image-active');
    if (this.status === 'done') {
      this.destroyEditor();
      this.cropImageSave();
    }
  };

  render(loadingBg?: string) {
    // 阅读模式不展示错误
    const { container, display, enableResizer } = this.options;
    if (display === CardType.BLOCK) {
      this.root.addClass('data-image-blcok');
    }
    const editor = this.editor;
    if (enableResizer === false) {
      this.root.addClass('data-image-disable-resize');
    }
    if (this.status === 'error' && isEngine(editor)) {
      this.root = $(this.renderTemplate(this.message || editor.language.get<string>('image', 'uploadError')));
      this.bindErrorEvent(this.root);
      container.empty().append(this.root);
      this.progress.remove();
      return;
    }
    if (this.status === 'uploading') {
      this.progress.show();
      container.empty().append(this.root);
    } else {
      this.progress.remove();
    }
    if (this.status === 'done' && this.isLoad) {
      const contentNode = this.root.find('.data-image-content');
      contentNode.addClass('data-image-loaded');
      contentNode.removeClass('data-image-loading');
    }
    if (this.status === 'done' && !this.isLoad) {
      if (!this.root.inEditor()) container.empty().append(this.root);
    }
    this.maxWidth = this.getMaxWidth();
    let { width, height } = this.size;
    if ((width && height) || !this.src) {
      if (width > this.maxWidth) {
        width = this.maxWidth;
        height = Math.round((width * height) / this.size.width);
      } else if (!this.src && !width && !height) {
        width = this.maxWidth;
        height = this.maxWidth / 2;
      }
      if (this.src) {
        if (this.options.enableResizer === false) {
          this.image.css({
            width: '100%',
          });
        } else {
          this.image.css({
            width: width + 'px',
            //height: height + "px",
          });
        }

        const { onChange } = this.options;
        if (width > 0 && height > 0) {
          this.size = { ...this.size, width, height };
          if (onChange) onChange(this.size);
        }
      }
      if (this.options.enableResizer === false) {
        this.bg.css({
          width: '100%',
        });
      } else {
        this.bg.css({
          width: width + 'px',
          height: height + 'px',
        });
      }

      if (loadingBg) {
        this.bg.css('background-image', `url(${loadingBg})`);
      }
    }

    this.image.on('load', () => this.imageLoadCallback());
    this.image.on('error', () => this.imageLoadError());
    if (!isMobile) {
      this.root.on('mouseenter', () => {
        this.maximize.show();
      });
      this.root.on('mouseleave', () => {
        this.maximize.hide();
      });
    }

    if (!isEngine(editor) || editor.readonly) {
      const link = this.image.closest('a');
      // 无链接
      if (link.length === 0) {
        this.image.on('click', this.openZoom);
      }
    }
    this.maximize.on('click', this.openZoom);
    if (isEngine(editor) || !this.root.inEditor()) {
      this.image.on('dblclick', this.openZoom);
    }
  }
}

export default Image;
