function bom(blob: any, opts: any) {
  if (typeof opts === "undefined") opts = { autoBom: false };
  else if (typeof opts !== "object") {
    console.warn("Deprecated: Expected third argument to be a object");
    opts = { autoBom: !opts };
  }

  if (
    opts.autoBom &&
    /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(
      blob.type
    )
  ) {
    return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type });
  }
  return blob;
}

function download(url: string, name: string, opts: any): Promise<void> {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.responseType = "blob";
    xhr.onload = function () {
      saveAs(xhr.response, name, opts).then(resolve);
    };
    xhr.onerror = function () {
      reject("could not download file");
    };
    xhr.send();
  });
}

function corsEnabled(url: any) {
  const xhr = new XMLHttpRequest();
  // use sync to avoid popup blocker
  xhr.open("HEAD", url, false);
  try {
    xhr.send();
  } catch (e) {}
  return xhr.status >= 200 && xhr.status <= 299;
}

function click(node: any) {
  return new Promise<void>((resolve) => {
    setTimeout(() => {
      try {
        node.dispatchEvent(new MouseEvent("click"));
      } catch (e) {
        const evt = document.createEvent("MouseEvents");
        evt.initMouseEvent(
          "click",
          true,
          true,
          window,
          0,
          0,
          0,
          80,
          20,
          false,
          false,
          false,
          false,
          0,
          null
        );
        node.dispatchEvent(evt);
      }
      resolve();
    }, 0);
  });
}

const isMacOSWebView =
  navigator &&
  /Macintosh/.test(navigator.userAgent) &&
  /AppleWebKit/.test(navigator.userAgent) &&
  !/Safari/.test(navigator.userAgent);

const global = window;
type SaveAs = (
  blob: Blob | string,
  name?: string,
  opts?: { autoBom: boolean }
) => Promise<void>;

export const saveAs: SaveAs =
  "download" in HTMLAnchorElement.prototype && !isMacOSWebView
    ? (blob, name = "download", opts) => {
        const URL = global.URL || global.webkitURL;
        const a = document.createElement("a");

        a.download = name;
        a.rel = "noopener";

        if (typeof blob === "string") {
          a.href = blob;
          if (a.origin !== location.origin) {
            if (corsEnabled(a.href)) {
              return download(blob, name, opts);
            }
            a.target = "_blank";
          }
          return click(a);
        } else {
          a.href = URL.createObjectURL(blob);
          setTimeout(function () {
            URL.revokeObjectURL(a.href);
          }, 4e4); // 40s
          return click(a);
        }
      }
    : "msSaveOrOpenBlob" in navigator
    ? (blob, name = "download", opts) => {
        if (typeof blob === "string") {
          if (corsEnabled(blob)) {
            return download(blob, name, opts);
          } else {
            const a = document.createElement("a");
            a.href = blob;
            a.target = "_blank";
            return click(a);
          }
        } else {
          return (navigator as any).msSaveOrOpenBlob(bom(blob, opts), name)
            ? Promise.resolve()
            : Promise.reject("unknown");
        }
      }
    : (blob, name, opts) => {
        if (typeof blob === "string")
          return download(blob, name as string, opts);

        const force = blob.type === "application/octet-stream";
        const isSafari =
          /constructor/i.test(HTMLElement.toString()) || (global as any).safari;
        const isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);

        if (
          (isChromeIOS || (force && isSafari) || isMacOSWebView) &&
          typeof FileReader !== "undefined"
        ) {
          return new Promise<void>((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = function () {
              let url = reader.result as string;
              url = isChromeIOS
                ? url
                : url.replace(/^data:[^;]*;/, "data:attachment/file;");
              location.href = url;
              resolve();
            };
            reader.onerror = function () {
              reject();
            };
            reader.readAsDataURL(blob);
          });
        } else {
          const URL = global.URL || global.webkitURL;
          const url = URL.createObjectURL(blob);
          location.href = url;
          setTimeout(function () {
            URL.revokeObjectURL(url);
          }, 4e4); // 40s
          return Promise.resolve();
        }
      };

export default saveAs;
