/**
 * 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.
 */

const CWD = process.cwd();
const glob = require('glob');
const fs = require('fs');
const path = require('path');
const assert = require('assert');

const env = require('./env.js');
const siteConfig = require(CWD + '/siteConfig.js');

const ENABLE_TRANSLATION = fs.existsSync(CWD + '/languages.js');

let versions;
if (fs.existsSync(CWD + '/versions.json')) {
  versions = require(CWD + '/versions.json');
} else {
  versions = [];
}

let languages;
if (fs.existsSync(CWD + '/languages.js')) {
  languages = require(CWD + '/languages.js');
} else {
  languages = [
    {
      enabled: true,
      name: 'English',
      tag: 'en',
    },
  ];
}

/*****************************************************************/

// included to prevent cyclical dependency with readMetadata.js

function splitHeader(content) {
  const lines = content.split('\n');
  let i = 1;
  for (; i < lines.length - 1; ++i) {
    if (lines[i] === '---') {
      break;
    }
  }
  return {
    header: lines.slice(1, i + 1).join('\n'),
    content: lines.slice(i + 1).join('\n'),
  };
}

// Extract markdown metadata header
function extractMetadata(content) {
  const metadata = {};
  const both = splitHeader(content);
  // if no content returned, then that means there was no header, and both.header is the content
  if (!both.content) {
    return {metadata, rawContent: both.header};
  }
  const lines = both.header.split('\n');
  for (let i = 0; i < lines.length - 1; ++i) {
    const keyvalue = lines[i].split(':');
    const key = keyvalue[0].trim();
    let value = keyvalue
      .slice(1)
      .join(':')
      .trim();
    try {
      value = JSON.parse(value);
    } catch (e) {}
    metadata[key] = value;
  }
  return {metadata, rawContent: both.content};
}

/*****************************************************************/

const versionFolder = CWD + '/versioned_docs/';

// available stores doc ids of documents that are available for
// each version
const available = {};
// versionFiles is used to keep track of what file to use with a
// given version/id of a document
const versionFiles = {};
let files = glob.sync(versionFolder + '**');
files.forEach(file => {
  const ext = path.extname(file);
  if (ext !== '.md' && ext !== '.markdown') {
    return;
  }
  const res = extractMetadata(fs.readFileSync(file, 'utf8'));
  const metadata = res.metadata;

  if (!metadata.original_id) {
    console.error(
      `No 'original_id' field found in ${file}. Perhaps you forgot to add it when importing prior versions of your docs?`
    );
    throw new Error(
      `No 'original_id' field found in ${file}. Perhaps you forgot to add it when importing prior versions of your docs?`
    );
  }
  if (!metadata.id) {
    console.error(`No 'id' field found in ${file}.`);
    throw new Error(`No 'id' field found in ${file}.`);
  } else if (metadata.id.indexOf('version-') === -1) {
    console.error(
      `The 'id' field in ${file} is missing the expected 'version-XX-' prefix. Perhaps you forgot to add it when importing prior versions of your docs?`
    );
    throw new Error(
      `The 'id' field in ${file} is missing the expected 'version-XX-' prefix. Perhaps you forgot to add it when importing prior versions of your docs?`
    );
  }

  if (!(metadata.original_id in available)) {
    available[metadata.original_id] = new Set();
  }
  // The version will be between "version-" and "-<metadata.original_id>"
  // e.g. version-1.0.0-beta.2-doc1 => 1.0.0-beta.2
  // e.g. version-1.0.0-doc2 => 1.0.0
  // e.g. version-1.0.0-getting-started => 1.0.0
  const version = metadata.id.substring(
    metadata.id.indexOf('version-') + 8, // version- is 8 characters
    metadata.id.lastIndexOf('-' + metadata.original_id)
  );
  available[metadata.original_id].add(version);

  if (!(version in versionFiles)) {
    versionFiles[version] = {};
  }
  versionFiles[version][metadata.original_id] = file;
});

// returns the version to use for a document based on its id and
// what the requested version is
function docVersion(id, req_version) {
  if (!available[id]) {
    return null;
  }
  // iterate through versions until a version less than or equal to the requested
  // is found, then check if that version has an available file to use
  let requestedFound = false;
  for (let i = 0; i < versions.length; i++) {
    if (versions[i] === req_version) {
      requestedFound = true;
    }
    if (!requestedFound) {
      continue;
    }
    if (available[id].has(versions[i])) {
      return versions[i];
    }
  }
  return null;
}

// returns whether a given file has content that differ from the
// document with the given id
function diffLatestDoc(file, id) {
  if (versions.length === 0) {
    return true;
  }

  const latest = versions[0];

  let version;
  try {
    version = docVersion(id, latest);
  } catch (e) {
    console.error(e);
    process.exit(1);
  }
  if (!version) {
    return true;
  }
  const latestFile = versionFiles[version][id];

  if (!latestFile || !fs.existsSync(latestFile)) {
    return true;
  }

  return (
    extractMetadata(fs.readFileSync(latestFile, 'utf8')).rawContent.trim() !==
    extractMetadata(fs.readFileSync(file, 'utf8')).rawContent.trim()
  );
}

// return metadata for a versioned file given the file, its version (requested),
// the version of the file to be used, and its language
function processVersionMetadata(file, version, useVersion, language) {
  const metadata = extractMetadata(fs.readFileSync(file, 'utf8')).metadata;
  metadata.source = 'version-' + useVersion + '/' + path.basename(file);

  const latestVersion = versions[0];

  if (!ENABLE_TRANSLATION && !siteConfig.useEnglishUrl) {
    metadata.permalink =
      'docs/' +
      (version !== latestVersion ? version + '/' : '') +
      metadata.original_id +
      '.html';
  } else {
    metadata.permalink =
      'docs/' +
      language +
      '/' +
      (version !== latestVersion ? version + '/' : '') +
      metadata.original_id +
      '.html';
  }
  metadata.id = metadata.id.replace(
    'version-' + useVersion + '-',
    'version-' + version + '-'
  );
  metadata.localized_id = metadata.id;
  metadata.id = (env.translation.enabled ? language + '-' : '') + metadata.id;
  metadata.language = language;
  metadata.version = version;

  return metadata;
}

// return all metadata of versioned documents
function docData() {
  const allIds = new Set();
  Object.keys(versionFiles).forEach(version => {
    Object.keys(versionFiles[version]).forEach(id => {
      allIds.add(id);
    });
  });

  const metadatas = [];

  languages.filter(language => language.enabled).forEach(language => {
    versions.forEach(version => {
      allIds.forEach(id => {
        let useVersion;
        try {
          useVersion = docVersion(id, version);
        } catch (e) {
          console.log(e);
          process.exit(1);
        }
        if (!useVersion) {
          return;
        }
        const file = versionFiles[useVersion][id];

        metadatas.push(
          processVersionMetadata(file, version, useVersion, language.tag)
        );
      });
    });
  });

  return metadatas;
}

// return the version of the sidebar to use given a requested version
function sidebarVersion(req_version) {
  // iterate through versions until a version less than or equal to the requested
  // is found, then check if that version has an available file to use
  let requestedFound = false;
  for (let i = 0; i < versions.length; i++) {
    if (versions[i] === req_version) {
      requestedFound = true;
    }
    if (!requestedFound) {
      continue;
    }
    if (
      fs.existsSync(
        CWD + '/versioned_sidebars/version-' + versions[i] + '-sidebars.json'
      )
    ) {
      return versions[i];
    }
  }
  throw new Error(
    `No sidebar file available to use for version ${req_version}. Verify that 'version-${req_version}-sidebars.json' exists.`
  );
}

// return whether or not the current sidebars.json file differs from the
// latest versioned one
function diffLatestSidebar() {
  if (versions.length === 0) {
    return true;
  }
  const latest = versions[0];

  const version = sidebarVersion(latest);
  const latestSidebar =
    CWD + '/versioned_sidebars/version-' + version + '-sidebars.json';
  if (!fs.existsSync(latestSidebar)) {
    return true;
  }
  const currentSidebar = CWD + '/sidebars.json';
  // if no current sidebar file, return false so no sidebar file gets copied
  if (!fs.existsSync(currentSidebar)) {
    return false;
  }

  // compare for equality between latest version sidebar with version prefixes
  // stripped and current sidebar
  return (
    JSON.stringify(JSON.parse(fs.readFileSync(latestSidebar, 'utf8'))).replace(
      new RegExp('version-' + version + '-', 'g'),
      ''
    ) !== JSON.stringify(JSON.parse(fs.readFileSync(currentSidebar, 'utf8')))
  );
}

// return all versioned sidebar data
function sidebarData() {
  const allSidebars = {};

  for (let i = 0; i < versions.length; i++) {
    const version = sidebarVersion(versions[i]);
    const sidebar = JSON.parse(
      fs
        .readFileSync(
          CWD + '/versioned_sidebars/version-' + version + '-sidebars.json',
          'utf8'
        )
        .replace(
          new RegExp('version-' + version + '-', 'g'),
          'version-' + versions[i] + '-'
        )
    );
    Object.assign(allSidebars, sidebar);
  }
  return allSidebars;
}

module.exports = {
  docVersion,
  diffLatestDoc,
  processVersionMetadata,
  docData,
  sidebarVersion,
  diffLatestSidebar,
  sidebarData,
};