#!/usr/bin/env node
/* eslint-disable no-console */
/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: MPL-2.0
 */

// edited to work with the appdir by @raphaelbadia

const gzSize = require("gzip-size")
const mkdirp = require("mkdirp")
const fs = require("fs")
const path = require("path")

// Pull options from `package.json`
const options = getOptions()
const BUILD_OUTPUT_DIRECTORY = getBuildOutputDirectory(options)

// first we check to make sure that the build output directory exists
const nextMetaRoot = path.join(process.cwd(), BUILD_OUTPUT_DIRECTORY)
try {
  fs.accessSync(nextMetaRoot, fs.constants.R_OK)
} catch (err) {
  console.error(
    `No build output found at "${nextMetaRoot}" - you may not have your working directory set correctly, or not have run "next build".`
  )
  process.exit(1)
}

// if so, we can import the build manifest
const buildMeta = require(path.join(nextMetaRoot, "build-manifest.json"))
const appDirMeta = require(path.join(nextMetaRoot, "app-build-manifest.json"))

// this memory cache ensures we dont read any script file more than once
// bundles are often shared between pages
const memoryCache = {}

// since _app is the template that all other pages are rendered into,
// every page must load its scripts. we'll measure its size here
const globalBundle = buildMeta.pages["/_app"]
const globalBundleSizes = getScriptSizes(globalBundle)

// next, we calculate the size of each page's scripts, after
// subtracting out the global scripts
const allPageSizes = Object.values(buildMeta.pages).reduce((acc, scriptPaths, i) => {
  const pagePath = Object.keys(buildMeta.pages)[i]
  const scriptSizes = getScriptSizes(scriptPaths.filter((scriptPath) => !globalBundle.includes(scriptPath)))

  acc[pagePath] = scriptSizes

  return acc
}, {})

const globalAppDirBundle = buildMeta.rootMainFiles
const globalAppDirBundleSizes = getScriptSizes(globalAppDirBundle)

const allAppDirSizes = Object.values(appDirMeta.pages).reduce((acc, scriptPaths, i) => {
  const pagePath = Object.keys(appDirMeta.pages)[i]
  const scriptSizes = getScriptSizes(scriptPaths.filter((scriptPath) => !globalAppDirBundle.includes(scriptPath)))
  acc[pagePath] = scriptSizes

  return acc
}, {})

// format and write the output
const rawData = JSON.stringify({
  ...allAppDirSizes,
  __global: globalAppDirBundleSizes,
})

// log ouputs to the gh actions panel
console.log(rawData)

mkdirp.sync(path.join(nextMetaRoot, "analyze/"))
fs.writeFileSync(path.join(nextMetaRoot, "analyze/__bundle_analysis.json"), rawData)

// --------------
// Util Functions
// --------------

// given an array of scripts, return the total of their combined file sizes
function getScriptSizes(scriptPaths) {
  const res = scriptPaths.reduce(
    (acc, scriptPath) => {
      const [rawSize, gzipSize] = getScriptSize(scriptPath)
      acc.raw += rawSize
      acc.gzip += gzipSize

      return acc
    },
    { raw: 0, gzip: 0 }
  )

  return res
}

// given an individual path to a script, return its file size
function getScriptSize(scriptPath) {
  const encoding = "utf8"
  const p = path.join(nextMetaRoot, scriptPath)

  let rawSize, gzipSize
  if (Object.keys(memoryCache).includes(p)) {
    rawSize = memoryCache[p][0]
    gzipSize = memoryCache[p][1]
  } else {
    const textContent = fs.readFileSync(p, encoding)
    rawSize = Buffer.byteLength(textContent, encoding)
    gzipSize = gzSize.sync(textContent)
    memoryCache[p] = [rawSize, gzipSize]
  }

  return [rawSize, gzipSize]
}

/**
 * Reads options from `package.json`
 */
function getOptions(pathPrefix = process.cwd()) {
  const pkg = require(path.join(pathPrefix, "package.json"))

  return { ...pkg.nextBundleAnalysis, name: pkg.name }
}

/**
 * Gets the output build directory, defaults to `.next`
 *
 * @param {object} options the options parsed from package.json.nextBundleAnalysis using `getOptions`
 * @returns {string}
 */
function getBuildOutputDirectory(options) {
  return options.buildOutputDirectory || ".next"
}
