const fs = require('fs')
const path = require('path')
const grayMatter = require('gray-matter')
const fetchPluginDocs = require('./fetch-plugin-docs')
const fetchDevPluginDocs = require('./fetch-dev-plugin-docs')

/**
 * Resolves nav-data from file with
 * resolution of remote plugin docs entries
 *
 * @param {string} navDataFile path to the nav-data.json file, relative to the cwd. Example: "data/docs-nav-data.json".
 * @param {object} options optional configuration object
 * @param {string} options.remotePluginsFile path to a remote-plugins.json file, relative to the cwd. Example: "data/docs-remote-plugins.json".
 * @returns {Promise<array>} the resolved navData. This includes NavBranch nodes pulled from remote plugin repositories, as well as filePath properties on all local NavLeaf nodes, and remoteFile properties on all NavLeafRemote nodes.
 */
async function resolveNavDataWithRemotePlugins(navDataFile, options = {}) {
  const { remotePluginsFile, currentPath } = options
  const navDataPath = path.join(process.cwd(), navDataFile)
  let navData = JSON.parse(fs.readFileSync(navDataPath, 'utf8'))
  return await appendRemotePluginsNavData(
    remotePluginsFile,
    navData,
    currentPath
  )
}

async function appendRemotePluginsNavData(
  remotePluginsFile,
  navData,
  currentPath
) {
  // Read in and parse the plugin configuration JSON
  const remotePluginsPath = path.join(process.cwd(), remotePluginsFile)
  const pluginEntries = JSON.parse(fs.readFileSync(remotePluginsPath, 'utf-8'))
  // Add navData for each plugin's component.
  // Note that leaf nodes include a remoteFile property object with the full MDX fileString
  const pluginEntriesWithDocs = await Promise.all(
    pluginEntries.map(
      async (entry) => await resolvePluginEntryDocs(entry, currentPath)
    )
  )

  const titleMap = {
    builders: 'Builders',
    provisioners: 'Provisioners',
    'post-processors': 'Post-Processors',
    datasources: 'Data Sources',
  }

  return navData.concat(
    pluginEntriesWithDocs.map((entry) => {
      return {
        title: entry.title,
        routes: Object.entries(entry.components).map(
          ([type, componentList]) => {
            return {
              title: titleMap[type],
              // Flat map to avoid ┐
              // > Proxmox         │
              //   > Builders      │
              //     > Proxmox <---┘
              //       > Overview
              //       > Clone
              //       > ISO
              routes: componentList.flatMap((c) => {
                if ('path' in c) {
                  return c
                } else if ('routes' in c) {
                  return c.routes
                }
              }),
            }
          }
        ),
      }
    })
  )
}

// Fetch remote plugin docs .mdx files, and
// transform each plugin's array of .mdx files into navData.
// Organize this navData by component, add it to the plugin config entry,
// and return the modified entry.
//
// Note that navData leaf nodes have a special remoteFile property,
// which contains { filePath, fileString } data for the remote
// plugin doc .mdx file
async function resolvePluginEntryDocs(pluginConfigEntry, currentPath) {
  const {
    title,
    path: slug,
    repo,
    version,
    pluginTier,
    isHcpPackerReady = false,
    sourceBranch = 'main',
    zipFile = '',
  } = pluginConfigEntry
  // Determine the pluginTier, which can be set manually,
  // or will be automatically set based on repo ownership
  const pluginOwner = repo.split('/')[0]
  const parsedPluginTier =
    pluginTier || (pluginOwner === 'hashicorp' ? 'official' : 'community')
  // Fetch the MDX files for the plugin entry
  var docsMdxFiles
  if (zipFile !== '') {
    docsMdxFiles = await fetchDevPluginDocs(zipFile)
  } else {
    docsMdxFiles = await fetchPluginDocs({ repo, tag: version })
  }
  // We construct a special kind of "NavLeaf" node, with a remoteFile property,
  // consisting of a { filePath, fileString, sourceUrl }, where:
  // - filePath is the path to the source file in the source repo
  // - fileString is a string representing the file source
  // - sourceUrl is a link to the original file in the source repo
  // We also add pluginData, which is used to add badges
  // such as the plugin's tier when rendering the page.
  const navNodes = docsMdxFiles.map((mdxFile) => {
    const { filePath, fileString } = mdxFile
    // Process into a NavLeaf, with a remoteFile attribute
    const dirs = path.dirname(filePath).split('/')
    const dirUrl = dirs.slice(2).join('/')
    const basename = path.basename(filePath, path.extname(filePath))
    // build urlPath
    // note that this will be prefixed to get to our final path
    const isIndexFile = basename === 'index'
    const urlPath = isIndexFile ? dirUrl : path.join(dirUrl, basename)
    // parse title, either from frontmatter or file name
    const { data: frontmatter } = grayMatter(fileString)
    const { nav_title, sidebar_title } = frontmatter
    const title = nav_title || sidebar_title || basename
    // construct sourceUrl (used for "Edit this page" link)
    const sourceUrl = `https://github.com/${repo}/blob/${sourceBranch}/${filePath}`
    // Construct and return a NavLeafRemote node
    return {
      title,
      path: urlPath,
      remoteFile: { filePath, fileString, sourceUrl },
      pluginData: {
        repo,
        tier: parsedPluginTier,
        isHcpPackerReady,
        version,
      },
    }
  })
  //
  navNodes.sort((a, b) => {
    // ensure casing does not affect ordering
    const aTitle = a.title.toLowerCase()
    const bTitle = b.title.toLowerCase()
    // (exception: "Overview" comes first)
    if (aTitle === 'overview') return -1
    if (bTitle === 'overview') return 1
    return aTitle < bTitle ? -1 : aTitle > bTitle ? 1 : 0
  })
  //
  const navNodesByComponent = navNodes.reduce((acc, navLeaf) => {
    const componentType = navLeaf.remoteFile.filePath.split('/')[1]
    if (!acc[componentType]) acc[componentType] = []
    acc[componentType].push(navLeaf)
    return acc
  }, {})
  //
  const components = Object.keys(navNodesByComponent).map((type) => {
    // Plugins many not contain every component type,
    // we return null if this is the case
    const rawNavNodes = navNodesByComponent[type]
    if (!rawNavNodes) return null
    // Avoid unnecessary nesting if there's only a single doc file
    const navData = normalizeNavNodes(title, rawNavNodes)
    // Prefix paths to fit into broader docs nav-data
    const pathPrefix = path.join(type, slug)
    const withPrefixedPaths = visitNavLeaves(navData, (n) => {
      const prefixedPath = path.join(pathPrefix, n.path)
      return { ...n, path: prefixedPath }
    })
    // If currentPath is provided, then remove the remoteFile
    // from all nodes except the currentPath. This ensures we deliver
    // only a single fileString in our getStaticProps JSON.
    // Without this optimization, we would send all fileStrings
    // for all NavLeafRemote nodes
    const withOptimizedFileStrings = visitNavLeaves(withPrefixedPaths, (n) => {
      if (!n.remoteFile) return n
      const noCurrentPath = typeof currentPath === 'undefined'
      const isPathMatch = currentPath === n.path
      if (noCurrentPath || isPathMatch) return n
      const { filePath } = n.remoteFile
      return { ...n, remoteFile: { filePath } }
    })
    // Return the component, with processed navData
    return { type, navData: withOptimizedFileStrings }
  })
  const componentsObj = components.reduce((acc, component) => {
    if (!component) return acc
    acc[component.type] = component.navData
    return acc
  }, {})
  return { ...pluginConfigEntry, components: componentsObj }
}

// For components with a single doc file, transform so that
// a single leaf node renders, rather than a nav branch
function normalizeNavNodes(pluginName, routes) {
  const isSingleLeaf =
    routes.length === 1 && typeof routes[0].path !== 'undefined'
  const navData = isSingleLeaf
    ? [{ ...routes[0], path: '' }]
    : [{ title: pluginName, routes }]
  return navData
}

// Traverse a clone of the given navData,
// modifying any NavLeaf nodes with the provided visitFn
function visitNavLeaves(navData, visitFn) {
  return navData.slice().map((navNode) => {
    if (typeof navNode.path !== 'undefined') {
      return visitFn(navNode)
    }
    if (navNode.routes) {
      return { ...navNode, routes: visitNavLeaves(navNode.routes, visitFn) }
    }
    return navNode
  })
}

module.exports = resolveNavDataWithRemotePlugins
