/**
 * Copyright (c) 2017-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

async function execute() {
  const commander = require('commander');
  commander
    .option('--skip-image-compression')
    .option('--skip-next-release')
    .parse(process.argv);
  require('../write-translations.js');
  const metadataUtils = require('./metadataUtils');
  const blog = require('./blog');
  const docs = require('./docs');
  const CWD = process.cwd();
  const fs = require('fs-extra');
  const readMetadata = require('./readMetadata.js');
  const path = require('path');
  const {minifyCss, isSeparateCss, autoPrefixCss} = require('./utils');
  const React = require('react');
  const mkdirp = require('mkdirp');
  const glob = require('glob');
  const chalk = require('chalk');
  const Site = require('../core/Site.js');
  const env = require('./env.js');
  const loadConfig = require('./config.js');
  const siteConfig = loadConfig(`${CWD}/siteConfig.js`);
  const translate = require('./translate.js');
  const feed = require('./feed.js');
  const sitemap = require('./sitemap.js');
  const join = path.join;
  const sep = path.sep;
  const escapeStringRegexp = require('escape-string-regexp');
  const {renderToStaticMarkupWithDoctype} = require('./renderUtils');
  const imagemin = require('imagemin');
  const imageminJpegtran = require('imagemin-jpegtran');
  const imageminOptipng = require('imagemin-optipng');
  const imageminSvgo = require('imagemin-svgo');
  const imageminGifsicle = require('imagemin-gifsicle');

  // create the folder path for a file if it does not exist, then write the file
  function writeFileAndCreateFolder(file, content) {
    mkdirp.sync(path.dirname(file));
    fs.writeFileSync(file, content);

    // build extra file for extension-less url if "cleanUrl" siteConfig is true
    if (siteConfig.cleanUrl && file.indexOf('index.html') === -1) {
      const extraFile = file.replace(/\.html$/, '/index.html');
      mkdirp.sync(path.dirname(extraFile));
      fs.writeFileSync(extraFile, content);
    }
  }

  console.log('generate.js triggered...');

  readMetadata.generateMetadataDocs();
  const Metadata = require('../core/metadata.js');

  // TODO: what if the project is a github org page? We should not use
  // siteConfig.projectName in this case. Otherwise a GitHub org doc URL would
  // look weird: https://myorg.github.io/myorg/docs

  // TODO: siteConfig.projectName is a misnomer. The actual project name is
  // `title`. `projectName` is only used to generate a folder, which isn't
  // needed when the project's a GitHub org page

  const buildDir = join(CWD, 'build', siteConfig.projectName);
  fs.removeSync(join(CWD, 'build'));

  // create html files for all docs by going through all doc ids
  const mdToHtml = metadataUtils.mdToHtml(Metadata, siteConfig);
  Object.keys(Metadata).forEach(id => {
    const metadata = Metadata[id];
    const file = docs.getFile(metadata);
    if (!file) {
      return;
    }
    const rawContent = metadataUtils.extractMetadata(file).rawContent;
    const str = docs.getMarkup(rawContent, mdToHtml, metadata, siteConfig);
    const targetFile = join(buildDir, metadata.permalink);
    writeFileAndCreateFolder(targetFile, str);

    // generate english page redirects when languages are enabled
    const redirectMarkup = docs.getRedirectMarkup(metadata, siteConfig);
    if (!redirectMarkup) {
      return;
    }
    const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`;
    const redirectFile = join(
      buildDir,
      metadata.permalink.replace(
        new RegExp(`^${docsPart}en`),
        siteConfig.docsUrl,
      ),
    );
    writeFileAndCreateFolder(redirectFile, redirectMarkup);
  });

  // copy docs assets if they exist
  if (fs.existsSync(join(CWD, '..', readMetadata.getDocsPath(), 'assets'))) {
    fs.copySync(
      join(CWD, '..', readMetadata.getDocsPath(), 'assets'),
      join(buildDir, siteConfig.docsUrl, 'assets'),
    );
  }

  // create html files for all blog posts (each article)
  if (fs.existsSync(join(__dirname, '..', 'core', 'MetadataBlog.js'))) {
    fs.removeSync(join(__dirname, '..', 'core', 'MetadataBlog.js'));
  }
  readMetadata.generateMetadataBlog(siteConfig);
  const MetadataBlog = require('../core/MetadataBlog.js');

  let files = glob.sync(join(CWD, 'blog', '**', '*.*'));
  files
    .sort()
    .reverse()
    .forEach(file => {
      // Why normalize? In case we are on Windows.
      // Remember the nuance of glob: https://www.npmjs.com/package/glob#windows
      const normalizedFile = path.normalize(file);
      const extension = path.extname(normalizedFile);
      if (extension !== '.md' && extension !== '.markdown') {
        return;
      }
      const urlPath = blog.fileToUrl(normalizedFile);
      const blogPost = blog.getPostMarkup(normalizedFile, siteConfig);
      if (!blogPost) {
        return;
      }
      const targetFile = join(buildDir, 'blog', urlPath);
      writeFileAndCreateFolder(targetFile, blogPost);
    });

  // create html files for all blog pages (collections of article previews)
  const blogPages = blog.getPagesMarkup(MetadataBlog.length, siteConfig);
  Object.keys(blogPages).forEach(pagePath => {
    const targetFile = join(buildDir, 'blog', pagePath);
    writeFileAndCreateFolder(targetFile, blogPages[pagePath]);
  });

  // create rss files for all blog pages, if there are any blog files
  if (MetadataBlog.length > 0) {
    let targetFile = join(buildDir, 'blog', 'feed.xml');
    writeFileAndCreateFolder(targetFile, feed());
    targetFile = join(buildDir, 'blog', 'atom.xml');
    writeFileAndCreateFolder(targetFile, feed('atom'));
  }

  // create sitemap
  if (MetadataBlog.length > 0 || Object.keys(Metadata).length > 0) {
    sitemap((err, xml) => {
      if (!err) {
        const targetFile = join(buildDir, 'sitemap.xml');
        writeFileAndCreateFolder(targetFile, xml);
      }
    });
  }

  // copy blog assets if they exist
  if (fs.existsSync(join(CWD, 'blog', 'assets'))) {
    fs.copySync(join(CWD, 'blog', 'assets'), join(buildDir, 'blog', 'assets'));
  }

  // copy all static files from docusaurus
  const libStaticDir = join(__dirname, '..', 'static');
  files = glob.sync(join(libStaticDir, '**'));
  files.forEach(file => {
    // Why normalize? In case we are on Windows.
    // Remember the nuance of glob: https://www.npmjs.com/package/glob#windows
    const targetFile = path.normalize(file).replace(libStaticDir, buildDir);
    // parse css files to replace colors according to siteConfig
    if (file.match(/\.css$/)) {
      let cssContent = fs.readFileSync(file, 'utf8');

      if (
        !siteConfig.colors ||
        !siteConfig.colors.primaryColor ||
        !siteConfig.colors.secondaryColor
      ) {
        console.error(
          `${chalk.yellow(
            'Missing color configuration.',
          )} Make sure siteConfig.colors includes primaryColor and secondaryColor fields.`,
        );
      }

      Object.keys(siteConfig.colors).forEach(key => {
        const color = siteConfig.colors[key];
        cssContent = cssContent.replace(new RegExp(`\\$${key}`, 'g'), color);
      });

      if (siteConfig.fonts) {
        Object.keys(siteConfig.fonts).forEach(key => {
          const fontString = siteConfig.fonts[key]
            .map(font => `"${font}"`)
            .join(', ');
          cssContent = cssContent.replace(
            new RegExp(`\\$${key}`, 'g'),
            fontString,
          );
        });
      }

      mkdirp.sync(path.dirname(targetFile));
      fs.writeFileSync(targetFile, cssContent);
    } else if (!fs.lstatSync(file).isDirectory()) {
      mkdirp.sync(path.dirname(targetFile));
      fs.copySync(file, targetFile);
    }
  });

  // Copy all static files from user.
  const userStaticDir = join(CWD, 'static');
  files = glob.sync(join(userStaticDir, '**'), {dot: true});
  files.forEach(file => {
    // Why normalize? In case we are on Windows.
    // Remember the nuance of glob: https://www.npmjs.com/package/glob#windows
    const normalizedFile = path.normalize(file);
    // parse css files to replace colors and fonts according to siteConfig
    if (
      normalizedFile.match(/\.css$/) &&
      !isSeparateCss(normalizedFile, siteConfig.separateCss)
    ) {
      const mainCss = join(buildDir, 'css', 'main.css');
      let cssContent = fs.readFileSync(normalizedFile, 'utf8');
      cssContent = `${fs.readFileSync(mainCss, 'utf8')}\n${cssContent}`;

      Object.keys(siteConfig.colors).forEach(key => {
        const color = siteConfig.colors[key];
        cssContent = cssContent.replace(new RegExp(`\\$${key}`, 'g'), color);
      });

      if (siteConfig.fonts) {
        Object.keys(siteConfig.fonts).forEach(key => {
          const fontString = siteConfig.fonts[key]
            .map(font => `"${font}"`)
            .join(', ');
          cssContent = cssContent.replace(
            new RegExp(`\\$${key}`, 'g'),
            fontString,
          );
        });
      }

      fs.writeFileSync(mainCss, cssContent);
    } else if (
      normalizedFile.match(/\.png$|.jpg$|.svg$|.gif$/) &&
      !commander.skipImageCompression
    ) {
      const parts = normalizedFile.split(`${sep}static${sep}`);
      const targetFile = join(buildDir, parts[1]);
      const targetDirectory = path.dirname(targetFile);
      mkdirp.sync(targetDirectory);
      imagemin([normalizedFile], targetDirectory, {
        use: [
          imageminOptipng(),
          imageminJpegtran(),
          imageminSvgo({
            plugins: [{removeViewBox: false}],
          }),
          imageminGifsicle(),
        ],
      }).catch(error => {
        // if image compression fail, just copy it as it is
        console.error(error);
        fs.copySync(normalizedFile, targetFile);
      });
    } else if (!fs.lstatSync(normalizedFile).isDirectory()) {
      const targetFile = normalizedFile.replace(userStaticDir, buildDir);
      mkdirp.sync(path.dirname(targetFile));
      fs.copySync(normalizedFile, targetFile);
    }
  });

  // Use cssnano to minify the final combined CSS.
  // Use autoprefixer to add vendor prefixes
  const mainCss = join(buildDir, 'css', 'main.css');
  const cssContent = fs.readFileSync(mainCss, 'utf8');
  const minifiedCSS = await minifyCss(cssContent);
  const css = await autoPrefixCss(minifiedCSS);
  fs.writeFileSync(mainCss, css);

  // compile/copy pages from user
  const enabledLanguages = env.translation
    .enabledLanguages()
    .map(lang => lang.tag);
  const userPagesDir = join(CWD, 'pages');
  files = glob.sync(join(userPagesDir, '**'));
  files.forEach(file => {
    // Why normalize? In case we are on Windows.
    // Remember the nuance of glob: https://www.npmjs.com/package/glob#windows
    const normalizedFile = path.normalize(file);
    const relativeFile = normalizedFile.replace(userPagesDir, '');
    // render .js files to strings
    if (normalizedFile.match(/\.js$/)) {
      const pageID = path.basename(normalizedFile, '.js');

      // make temp file for sake of require paths
      let tempFile = join(__dirname, '..', 'pages', relativeFile);
      tempFile = tempFile.replace(
        path.basename(normalizedFile),
        `temp${path.basename(normalizedFile)}`,
      );
      mkdirp.sync(path.dirname(tempFile));
      fs.copySync(normalizedFile, tempFile);

      const ReactComp = require(tempFile);

      let targetFile = join(buildDir, relativeFile);
      targetFile = targetFile.replace(/\.js$/, '.html');

      const regexLang = new RegExp(
        `${escapeStringRegexp(`${userPagesDir}${sep}`)}(.*)${escapeStringRegexp(
          sep,
        )}`,
      );
      const match = regexLang.exec(normalizedFile);
      const langParts = match[1].split(sep);
      if (langParts.indexOf('en') !== -1) {
        // Copy and compile a page for each enabled language from the English file.
        for (let i = 0; i < enabledLanguages.length; i++) {
          const language = enabledLanguages[i];
          // Skip conversion from English file if a file exists for this language.
          if (
            language === 'en' ||
            !fs.existsSync(
              normalizedFile.replace(`${sep}en${sep}`, sep + language + sep),
            )
          ) {
            translate.setLanguage(language);
            const str = renderToStaticMarkupWithDoctype(
              <Site
                language={language}
                config={siteConfig}
                title={ReactComp.title}
                description={ReactComp.description}
                metadata={{id: pageID}}>
                <ReactComp config={siteConfig} language={language} />
              </Site>,
            );
            writeFileAndCreateFolder(
              // TODO: use path functions
              targetFile.replace(`${sep}en${sep}`, sep + language + sep),
              str,
            );
          }
        }

        // write to base level
        const language = env.translation.enabled ? 'en' : '';
        translate.setLanguage(language);
        const str = renderToStaticMarkupWithDoctype(
          <Site
            title={ReactComp.title}
            language={language}
            config={siteConfig}
            description={ReactComp.description}
            metadata={{id: pageID}}>
            <ReactComp config={siteConfig} language={language} />
          </Site>,
        );
        writeFileAndCreateFolder(
          targetFile.replace(`${sep}en${sep}`, sep),
          str,
        );
      } else {
        // allow for rendering of other files not in pages/en folder
        const language = env.translation.enabled ? 'en' : '';
        translate.setLanguage(language);
        const str = renderToStaticMarkupWithDoctype(
          <Site
            title={ReactComp.title}
            language={language}
            config={siteConfig}
            description={ReactComp.description}
            metadata={{id: pageID}}>
            <ReactComp config={siteConfig} language={language} />
          </Site>,
        );
        writeFileAndCreateFolder(
          targetFile.replace(`${sep}en${sep}`, sep),
          str,
        );
      }
      fs.removeSync(tempFile);
    } else if (siteConfig.wrapPagesHTML && normalizedFile.match(/\.html$/)) {
      const pageID = path.basename(normalizedFile, '.html');
      const targetFile = join(buildDir, relativeFile);
      const str = renderToStaticMarkupWithDoctype(
        <Site language="en" config={siteConfig} metadata={{id: pageID}}>
          <div
            dangerouslySetInnerHTML={{
              __html: fs.readFileSync(normalizedFile, {encoding: 'utf8'}),
            }}
          />
        </Site>,
      );

      writeFileAndCreateFolder(targetFile, str);
    } else if (!fs.lstatSync(normalizedFile).isDirectory()) {
      // copy other non .js files
      const targetFile = join(buildDir, relativeFile);
      mkdirp.sync(path.dirname(targetFile));
      fs.copySync(normalizedFile, targetFile);
    }
  });

  // Generate CNAME file if a custom domain is specified in siteConfig
  if (siteConfig.cname) {
    const targetFile = join(buildDir, 'CNAME');
    fs.writeFileSync(targetFile, siteConfig.cname);
  }
}

module.exports = execute;
