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

const CWD = process.cwd();

const path = require('path');
const fs = require('fs');
const glob = require('glob');
const program = require('commander');

const metadataUtils = require('./metadataUtils');

const env = require('./env.js');
const blog = require('./blog.js');

const loadConfig = require('./config');

const siteConfig = loadConfig(`${CWD}/siteConfig.js`);
const versionFallback = require('./versionFallback.js');
const utils = require('./utils.js');

const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`;

// Get a list of all IDs that have been deleted in any version.
// We will assume these should not be in the current/next version.
const allDeletedIds = new Set();
if (siteConfig.deletedDocs) {
  Object.values(siteConfig.deletedDocs).forEach((idList) => {
    idList.forEach((id) => allDeletedIds.add(id));
  });
}

const SupportedHeaderFields = new Set([
  'id',
  'title',
  'author',
  'authorURL',
  'authorFBID',
  'sidebar_label',
  'original_id',
  'hide_title',
  'layout',
  'custom_edit_url',
  'description',
]);

let allSidebars;
if (fs.existsSync(`${CWD}/sidebars.json`)) {
  allSidebars = require(`${CWD}/sidebars.json`);
} else {
  allSidebars = {};
}

// Can have a custom docs path. Top level folder still needs to be in directory
// at the same level as `website`, not inside `website`.
//   e.g., docs/whereDocsReallyExist
//         website-docs/
// All .md docs still (currently) must be in one flat directory hierarchy.
//   e.g., docs/whereDocsReallyExist/*.md (all .md files in this dir)
function getDocsPath() {
  return siteConfig.customDocsPath ? siteConfig.customDocsPath : 'docs';
}

function shouldGenerateNextReleaseDocs() {
  return !(env.versioning.enabled && program.skipNextRelease);
}

// returns map from id to object containing sidebar ordering info
function readSidebar(sidebars = {}) {
  Object.assign(sidebars, versionFallback.sidebarData());

  const items = {};

  Object.keys(sidebars).forEach((sidebar) => {
    const categories = sidebars[sidebar];
    const sidebarItems = [];

    Object.keys(categories).forEach((category) => {
      const categoryItems = categories[category];
      categoryItems.forEach((categoryItem) => {
        if (typeof categoryItem === 'object') {
          switch (categoryItem.type) {
            case 'subcategory':
              categoryItem.ids.forEach((subcategoryItem) => {
                sidebarItems.push({
                  id: subcategoryItem,
                  category,
                  subcategory: categoryItem.label,
                  order: sidebarItems.length + 1,
                });
              });
              return;
            default:
              return;
          }
        }

        // Is a regular id value.
        sidebarItems.push({
          id: categoryItem,
          category,
          subcategory: null,
          order: sidebarItems.length + 1,
        });
      });
    });

    for (let i = 0; i < sidebarItems.length; i++) {
      const item = sidebarItems[i];
      let previous = null;
      let next = null;

      if (i > 0) {
        previous = sidebarItems[i - 1].id;
      }

      if (i < sidebarItems.length - 1) {
        next = sidebarItems[i + 1].id;
      }

      items[item.id] = {
        previous,
        next,
        sidebar,
        category: item.category,
        subcategory: item.subcategory,
        order: item.order,
      };
    }
  });

  return items;
}

// process the metadata for a document found in either 'docs' or 'translated_docs'
function processMetadata(file, refDir) {
  const result = metadataUtils.extractMetadata(fs.readFileSync(file, 'utf8'));
  const language = utils.getLanguage(file, refDir) || 'en';

  const metadata = {};
  Object.keys(result.metadata).forEach((fieldName) => {
    if (SupportedHeaderFields.has(fieldName)) {
      metadata[fieldName] = result.metadata[fieldName];
    } else {
      console.warn(`Header field "${fieldName}" in ${file} is not supported.`);
    }
  });

  const rawContent = result.rawContent;

  if (!metadata.id) {
    metadata.id = path.parse(file).name;
  }
  if (metadata.id.includes('/')) {
    throw new Error('Document id cannot include "/".');
  }

  // If a file is located in a subdirectory, prepend the subdir to it's ID
  // Example:
  //  (file: 'docusaurus/docs/projectA/test.md', ID 'test', refDir: 'docusaurus/docs')
  //  returns 'projectA/test'
  const subDir = utils.getSubDir(file, refDir);
  if (subDir) {
    metadata.id = `${subDir}/${metadata.id}`;
  }

  // Example: `docs/projectA/test.md` source is `projectA/test.md`
  metadata.source = subDir
    ? `${subDir}/${path.basename(file)}`
    : path.basename(file);
  metadata.versioned_source = metadata.source;

  if (!metadata.title) {
    metadata.title = metadata.id;
  }

  const langPart =
    env.translation.enabled || siteConfig.useEnglishUrl ? `${language}/` : '';
  let versionPart = '';
  if (env.versioning.enabled) {
    metadata.version = 'next';
    versionPart = 'next/';
  }

  metadata.permalink = `${docsPart}${langPart}${versionPart}${metadata.id}.html`;

  // change ids previous, next
  metadata.localized_id = metadata.id;
  metadata.id = (env.translation.enabled ? `${language}-` : '') + metadata.id;
  metadata.language = env.translation.enabled ? language : 'en';

  const items = readSidebar(allSidebars);
  const id = metadata.localized_id;
  const item = items[id];
  if (item) {
    metadata.sidebar = item.sidebar;
    metadata.category = item.category;
    metadata.subcategory = item.subcategory;
    metadata.order = item.order;

    if (item.next) {
      metadata.next_id = item.next;
      metadata.next =
        (env.translation.enabled ? `${language}-` : '') + item.next;
    }
    if (item.previous) {
      metadata.previous_id = item.previous;
      metadata.previous =
        (env.translation.enabled ? `${language}-` : '') + item.previous;
    }
  }

  return {metadata, rawContent};
}

// process metadata for all docs and save into core/metadata.js
function generateMetadataDocs() {
  let order;
  try {
    order = readSidebar(allSidebars);
  } catch (e) {
    console.error(e);
    process.exit(1);
  }

  const enabledLanguages = env.translation
    .enabledLanguages()
    .map((language) => language.tag);

  const metadatas = {};
  const defaultMetadatas = {};

  if (shouldGenerateNextReleaseDocs()) {
    // metadata for english files
    const docsDir = path.join(CWD, '../', getDocsPath());
    let files = glob.sync(`${docsDir}/**`);
    files.forEach((file) => {
      const extension = path.extname(file);

      if (extension === '.md' || extension === '.markdown') {
        const res = processMetadata(file, docsDir);

        if (!res) {
          return;
        }
        const metadata = res.metadata;
        if (
          allDeletedIds.has(metadata.id) ||
          (metadata.original_id && allDeletedIds.has(metadata.original_id))
        ) {
          return;
        }
        metadatas[metadata.id] = metadata;

        // create a default list of documents for each enabled language based on docs in English
        // these will get replaced if/when the localized file is downloaded from crowdin
        enabledLanguages
          .filter((currentLanguage) => currentLanguage !== 'en')
          .forEach((currentLanguage) => {
            const baseMetadata = Object.assign({}, metadata);
            baseMetadata.id = baseMetadata.id
              .toString()
              .replace(/^en-/, `${currentLanguage}-`);
            if (baseMetadata.permalink) {
              baseMetadata.permalink = baseMetadata.permalink
                .toString()
                .replace(
                  new RegExp(`^${docsPart}en/`),
                  `${docsPart}${currentLanguage}/`,
                );
            }
            if (baseMetadata.next) {
              baseMetadata.next = baseMetadata.next
                .toString()
                .replace(/^en-/, `${currentLanguage}-`);
            }
            if (baseMetadata.previous) {
              baseMetadata.previous = baseMetadata.previous
                .toString()
                .replace(/^en-/, `${currentLanguage}-`);
            }
            baseMetadata.language = currentLanguage;
            defaultMetadatas[baseMetadata.id] = baseMetadata;
          });
        Object.assign(metadatas, defaultMetadatas);
      }
    });

    // metadata for non-english docs
    const translatedDir = path.join(CWD, 'translated_docs');
    files = glob.sync(`${CWD}/translated_docs/**`);
    files.forEach((file) => {
      if (!utils.getLanguage(file, translatedDir)) {
        return;
      }

      const extension = path.extname(file);

      if (extension === '.md' || extension === '.markdown') {
        const res = processMetadata(file, translatedDir);
        if (!res) {
          return;
        }
        const metadata = res.metadata;
        if (
          allDeletedIds.has(metadata.id) ||
          (metadata.original_id && allDeletedIds.has(metadata.original_id))
        ) {
          return;
        }
        metadatas[metadata.id] = metadata;
      }
    });
  }

  // metadata for versioned docs
  const versionData = versionFallback.docData();
  versionData.forEach((metadata) => {
    const id = metadata.localized_id;
    if (order[id]) {
      metadata.sidebar = order[id].sidebar;
      metadata.category = order[id].category;
      metadata.subcategory = order[id].subcategory;
      metadata.order = order[id].order;

      if (order[id].next) {
        metadata.next_id = order[id].next.replace(
          `version-${metadata.version}-`,
          '',
        );
        metadata.next =
          (env.translation.enabled ? `${metadata.language}-` : '') +
          order[id].next;
      }
      if (order[id].previous) {
        metadata.previous_id = order[id].previous.replace(
          `version-${metadata.version}-`,
          '',
        );
        metadata.previous =
          (env.translation.enabled ? `${metadata.language}-` : '') +
          order[id].previous;
      }
    }
    metadatas[metadata.id] = metadata;
  });

  // Get the titles of the previous and next ids so that we can use them in
  // navigation buttons in DocsLayout.js
  Object.keys(metadatas).forEach((metadata) => {
    if (metadatas[metadata].previous) {
      if (metadatas[metadatas[metadata].previous]) {
        metadatas[metadata].previous_title =
          metadatas[metadatas[metadata].previous].title;
      } else {
        metadatas[metadata].previous_title = 'Previous';
      }
    }
    if (metadatas[metadata].next) {
      if (metadatas[metadatas[metadata].next]) {
        metadatas[metadata].next_title =
          metadatas[metadatas[metadata].next].title;
      } else {
        metadatas[metadata].next_title = 'Next';
      }
    }
  });

  fs.writeFileSync(
    path.join(__dirname, '/../core/metadata.js'),
    `${
      '/**\n' +
      ' * @' +
      'generated\n' + // separate this out for Nuclide treating @generated as readonly
      ' */\n' +
      'module.exports = '
    }${JSON.stringify(metadatas, null, 2)};\n`,
  );
}

// process metadata for blog posts and save into core/MetadataBlog.js
function generateMetadataBlog(config = siteConfig) {
  const metadatas = [];

  const files = glob.sync(`${CWD}/blog/**/*.*`);
  files
    .sort()
    .reverse()
    .forEach((file) => {
      const extension = path.extname(file);
      if (extension !== '.md' && extension !== '.markdown') {
        return;
      }
      const metadata = blog.getMetadata(file, config);
      // Extract, YYYY, MM, DD from the file name
      const filePathDateArr = path.basename(file).split('-');
      metadata.date = new Date(
        `${filePathDateArr[0]}-${filePathDateArr[1]}-${filePathDateArr[2]}T06:00:00.000Z`,
      );
      // allow easier sorting of blog by providing seconds since epoch
      metadata.seconds = Math.round(metadata.date.getTime() / 1000);

      metadatas.push(metadata);
    });

  const sortedMetadatas = metadatas.sort(
    (a, b) => parseInt(b.seconds, 10) - parseInt(a.seconds, 10),
  );

  fs.writeFileSync(
    path.join(__dirname, '/../core/MetadataBlog.js'),
    `${
      '/**\n' +
      ' * @' +
      'generated\n' + // separate this out for Nuclide treating @generated as readonly
      ' */\n' +
      'module.exports = '
    }${JSON.stringify(sortedMetadatas, null, 2)};\n`,
  );
}

module.exports = {
  getDocsPath,
  readSidebar,
  processMetadata,
  shouldGenerateNextReleaseDocs,
  generateMetadataDocs,
  generateMetadataBlog,
};
