class LisioImgToBlob {
  private _base64s: Map<string, string> = new Map<string, string>();
  private _canvas: HTMLCanvasElement;
  private _ctx: CanvasRenderingContext2D;
  private _svgFiles = new Map<string, string>();

  constructor() {
    this._canvas = document.createElement("canvas");
    this._ctx = this._canvas.getContext("2d") as CanvasRenderingContext2D;
  }

  public get base64s(): Map<string, string> {
    return this._base64s;
  }

  public async imgToBlob(image: HTMLImageElement) {
    const fileName = image.src.split("/").pop();
    if (!this._base64s.has(image.src) && fileName != undefined) {
      const ext = fileName.split(".").pop();
      const isSVG = ext === "svg";
      this._canvas.width = isSVG
        ? image.width
        : image.naturalWidth || image.width;
      this._canvas.height = isSVG
        ? image.height
        : image.naturalHeight || image.height;

      this._ctx.drawImage(image, 0, 0);

      try {
        const blob: Blob | undefined = await new Promise((res) =>
          this._canvas.toBlob(
            (blob) => res(blob as Blob | undefined),
            `image/${isSVG ? "svg+xml" : ext}`,
          ),
        );
        if (blob == undefined) {
          throw new Error("Blob is not defined");
        }
        const uri = window.URL.createObjectURL(blob);
        this._base64s.set(image.src, uri);
        return uri;
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
      } catch (error) {
        this._base64s.set(image.src, image.src);
        return image.src;
      } finally {
        this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
      }
    } else {
      return undefined;
    }
  }

  public async loadSVG(svg: SVGElement) {
    const parser = new DOMParser();

    const clonedSVG = svg.cloneNode(true) as SVGElement;
    const hash = await this.generateSHA256Hash(clonedSVG.innerHTML);
    const useElements = clonedSVG.querySelectorAll("use");
    clonedSVG.setAttribute("xmlns", "http://www.w3.org/2000/svg");
    const styles = window.getComputedStyle(svg);
    clonedSVG.style.color = styles.color;
    for (const use of useElements) {
      const href = use.getAttribute("href") || use.getAttribute("xlink:href");
      if (href && href.includes(".svg")) {
        const [file, id] = href.split("#");
        if (!this._svgFiles.has(file)) {
          const response = await fetch(file);
          const externalSvg = await response.text();
          this._svgFiles.set(file, externalSvg);
        }
        const svgFile = this._svgFiles.get(file);
        if (svgFile != undefined) {
          const externalDoc = parser.parseFromString(svgFile, "image/svg+xml");
          const referencedElement = externalDoc.querySelector(`#${id}`);

          if (referencedElement) {
            const clone = referencedElement.cloneNode(true);
            for (const attr of use.attributes) {
              if (
                !["href", "xlink:href"].includes(attr.name) &&
                clone instanceof HTMLElement
              ) {
                clone.setAttribute(attr.name, attr.value);
              }
            }
            clonedSVG.insertBefore(clone, use);
            use.setAttribute("href", `#${id}`);
          }
        }
      }
    }
    if (!this._base64s.has(hash)) {
      const blob = new Blob([clonedSVG.outerHTML], { type: "image/svg+xml" });
      const blobUrl = URL.createObjectURL(blob);
      this._base64s.set(hash, blobUrl);
    }

    return hash;
  }

  private async generateSHA256Hash(input: string) {
    const encoder = new TextEncoder();
    const data = encoder.encode(input);
    const hashBuffer = await crypto.subtle.digest("SHA-256", data);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
  }
}

export default LisioImgToBlob;
