import JSZip from 'jszip';
import FileSaver from 'file-saver';

const urlPattern = /\b((?:[a-z][\w\-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]|\((?:[^\s()<>]|(?:\([^\s()<>]+\)))*\))+(?:\((?:[^\s()<>]|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i
const assetPattern = /\/.*?\.(png|jpg|jpeg|icon|ogg|mp4|webm|json)/ig;

export default (editor, opts = {}) => {
  let pfx = editor.getConfig('stylePrefix');
  let btnExp = document.createElement('button');
  let commandName = 'sp-frontpanel-export';
  const domComponents = editor.DomComponents;
  const codeManager = editor.CodeManager;
  let globalScripts = [];

  const _match = function (str, pattern) {
    if (str.match) {
      return str.match(pattern) || [];
    }

    return [];
  }

  const fetchAssets = async (id, url, zip) => {
    return new Promise(async (res, rej) => {
      const content = await fetch(url).then(resp => resp.blob());
      const name = url.split('/').pop() || `${id}`;
      zip.pack('', name, content);
      res();
    })
  };

  const replaceUrl = (text, zip, patterns = [], prefix = "./assets") => {
    let _text = text;

    const results = [];
    const promises = [];

    patterns.forEach(pattern => results.push(..._match(_text, pattern)));

    results.forEach((url, index) => {
      if (url.startsWith('http') || url.startsWith('https') || url.startsWith('/')) {
        promises.push(fetchAssets(index, url, zip));
        const name = url.split('/').pop();
        _text = _text.replace(new RegExp(url, 'g'), `${prefix}/${name}`);
      }
    });

    return { promise: Promise.all(promises), _text };
  }

  const parseCss = async (css, zip, options) => {
    return new Promise((resolve, reject) => {
      const { promise, _text } = replaceUrl(css, zip, [assetPattern], "../assets");

      promise.then(() => resolve(_text));
    })
  };

  const parseHtml = async (html, zip, options) => {
    return new Promise((resolve, reject) => {
      const js = codeManager.getCode(domComponents.getWrapper(), 'js').trim();
      const replacedHtml = replaceUrl(html, zip, [urlPattern, assetPattern], "./assets");
      const replacedJs = replaceUrl(js, zip, [assetPattern], "./assets");

      Promise.all([replacedHtml.promise, replacedJs.promise]).then(() => {
        resolve(`
          <!doctype html>
          <html lang="en">
            <head>
              <meta charset="utf-8">
              <link rel="stylesheet" href="./css/style.css">
              <link rel="stylesheet" href="./css/suanpan-sdk.all.css">
              <script src="./js/suanpan-sdk.dashboardPage.min.js"></script>
              <script>
                function runCode() {
                  ${globalScripts.join('\n')}

                  ${replacedJs._text}
                }
              </script>
              <script>
                window.appConfig = ${JSON.stringify(options.appConfig)};
                window.sp({
                  url: location.protocol + "//" + window.appConfig.dashboardHost + '/dashboard',
                  path: '/socket/' + window.appConfig.id,
                }).then((s) => {
                  window.spSocket = s;
                  runCode();
                });
                window.loader = {
                  ele: '<div class="loader"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>',
                  add: function(triggerId) {
                    $('body').find('[loader]').each(function(i, el) {
                      if (!$(el).children('.loader').length) {
                        $(el).append(window.loader.ele);
                      }
                    })
                  },
                  remove: function(triggerId) {
                    $('body').find('#' + triggerId).children('.loader').remove();
                  }
                };
              </script>
            </head>
            <body>${replacedHtml._text}</body>
          <html>
        `)
      })
    })
  }

  const asyncCall = (cb) => {
    return new Promise((resolve, reject) => {
      cb().then(() => {
        resolve();
      }, (err) => {
        reject(err);
      })
    })
  }

  let config = {
    addExportBtn: 1,
    btnLabel: '导出静态文件',
    filenamePfx: 'grapesjs_template',
    filename: null,
    root: {
      'suanpan-sdk.all.css': {
        content: async (ed, zip) => asyncCall(async () => {
          // 拉取 suanpan-sdk css
          const content = await fetch(window.appConfig.suanpanSdkAllCSSPath).then(resp => resp.text());
          zip.pack('text/css', 'suanpan-sdk.all.css', content);
        })
      },
      'style.css': {
        content: async (ed, zip, options) => asyncCall(async () => {
          zip.pack('text/css', 'style.css', await parseCss(ed.getCss(), zip, options));
        })
      },
      'index.html': {
        content: async (ed, zip, options) => asyncCall(async () => {
          let html = codeManager.getCode(domComponents.getComponent(), 'html');
          const matchResult = html.match(/<script(\s|>)(\s|.)*?<\/script>/g);
          matchResult.forEach(function (r) {
            globalScripts.push($(r)[0].innerHTML);
            html = html.replace(r, '');
          })
          zip.pack('text/html', 'index.html', await parseHtml(html, zip, options))
        }),
      },
      'suanpan-sdk.dashboardPage.min.js': {
        content: async (ed, zip) => {
          return asyncCall(async () => {
            const content = await fetch(window.appConfig.suanpanSdkAllPath).then(resp => resp.text());
            return zip.pack('text/javascript', 'suanpan-sdk.dashboardPage.min.js', content);
          })
        },
      }
    },
    isBinary: null,
    ...opts,
  };

  btnExp.innerHTML = config.btnLabel;
  btnExp.className = `${pfx}btn-prim`;
  btnExp.setAttribute('style', 'position: absolute;  top: 10px;  right: 10px;');

  // Add command
  editor.Exporter = {
    createFile(name, content, type) {
      return new Promise((resolve, reject) => {
        window.appDataStore && window.appDataStore.upload(name, content, () => { }, (error) => {
          console.error(error);
          reject(error);
        }, () => { resolve() });
      });
    },

    async createDirectory(root, opts = {}) {
      const { zip = null } = opts;
      root = typeof root === 'function' ? await root(editor) : root;
      const promises = [];

      for (const name in root) {
        if (root.hasOwnProperty(name)) {
          promises.push(root[name].content(editor, zip, opts));
        }
      }

      Promise.all(promises).then(() => {
        if (opts.callback) {
          opts.callback();
        }

        if (opts.download) {
          zip.root.generateAsync({ type: 'blob' })
            .then(content => {
              FileSaver.saveAs(content, `dashboard_static_${Date.now()}.zip`);
            });
        }
      });
    },

    async export(options) {
      const zip = new JSZip();
      const uploadPath = options.uploadPath || '';
      const _this = this;

      options.zip = {
        root: zip,
        css: zip.folder('css'),
        js: zip.folder('js'),
        assets: zip.folder('assets'),
        pack(type, name, content) {
          switch (type) {
            case 'text/javascript':
              this.js.file(name, content);
              return _this.createFile(`${uploadPath}/js/${name}`, new Blob([content], { type }));
            case 'text/css':
              this.css.file(name, content);
              return _this.createFile(`${uploadPath}/css/${name}`, new Blob([content], { type }));
            case 'text/html':
              this.root.file(name, content);
              return _this.createFile(`${uploadPath}/${name}`, new Blob([content], { type }));
          }

          this.assets.file(name, content);
          return _this.createFile(`${uploadPath}/assets/${name}`, new Blob([content], { type }));
        }
      };

      this.createDirectory(config.root, options);
    }
  };
};
