#!/usr/bin/env node
/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const fs = require('fs');
const os = require('os');
const path = require('path');
const rmSync = require('rimraf').sync;
const ncp = require('ncp');
const {spawnSync} = require('child_process');
const util = require('util');

const writeFileAsync = util.promisify(fs.writeFile.bind(fs));
const cpAsync = util.promisify(ncp);

const SCRIPT_NAME = path.basename(__filename);
const ROOT_PATH = path.join(__dirname, '..');

const PLAYWRIGHT_CORE_FILES = ['bin/PrintDeps.exe', 'lib', 'types', 'NOTICE', 'LICENSE', 'bin/android-driver.apk', 'bin/android-driver-target.apk'];

const PACKAGES = {
  'playwright': {
    description: 'A high-level API to automate web browsers',
    browsers: ['chromium', 'firefox', 'webkit', 'ffmpeg'],
    // We copy README.md additionally for Playwright so that it looks nice on NPM.
    files: [...PLAYWRIGHT_CORE_FILES, 'README.md'],
  },
  'playwright-core': {
    description: 'A high-level API to automate web browsers',
    browsers: [],
    files: PLAYWRIGHT_CORE_FILES,
  },
  'playwright-webkit': {
    description: 'A high-level API to automate WebKit',
    browsers: ['webkit'],
    files: PLAYWRIGHT_CORE_FILES,
  },
  'playwright-firefox': {
    description: 'A high-level API to automate Firefox',
    browsers: ['firefox'],
    files: PLAYWRIGHT_CORE_FILES,
  },
  'playwright-chromium': {
    description: 'A high-level API to automate Chromium',
    browsers: ['chromium', 'ffmpeg'],
    files: [...PLAYWRIGHT_CORE_FILES],
  },
  'playwright-electron': {
    version: '0.4.0', // Manually manage playwright-electron version.
    description: 'A high-level API to automate Electron',
    browsers: ['ffmpeg'],
    files: [...PLAYWRIGHT_CORE_FILES],
  },
  'playwright-android': {
    version: '0.0.8', // Manually manage playwright-android version.
    description: 'A high-level API to automate Chrome for Android',
    browsers: ['ffmpeg'],
    files: [...PLAYWRIGHT_CORE_FILES],
  },
};

// 1. Parse CLI arguments
const args = process.argv.slice(2);
if (args.some(arg => arg === '--help')) {
  console.log(usage());
  process.exit(1);
} else if (args.length < 1) {
  console.log(`Please specify package name, e.g. 'playwright' or 'playwright-chromium'.`);
  console.log(`Try running ${SCRIPT_NAME} --help`);
  process.exit(1);
} else if (args.length < 2) {
  console.log(`Please specify output path`);
  console.log(`Try running ${SCRIPT_NAME} --help`);
  process.exit(1);
}

const packageName = args[0];
const outputPath = path.resolve(args[1]);
const packagePath = path.join(__dirname, 'output', packageName);
const package = PACKAGES[packageName];
if (!package) {
  console.log(`ERROR: unknown package ${packageName}`);
  process.exit(1);
}

// 2. Setup cleanup if needed
if (!args.some(arg => arg === '--no-cleanup')) {
  process.on('exit', () => {
    rmSync(packagePath, {});
  });
  process.on('SIGINT', () => process.exit(2));
  process.on('SIGHUP', () => process.exit(3));
  process.on('SIGTERM', () => process.exit(4));
  process.on('uncaughtException', error => {
    console.error(error);
    process.exit(5);
  });
  process.on('unhandledRejection', error => {
    console.error(error);
    process.exit(6);
  });
}

(async () => {
  // 3. Copy package files.
  rmSync(packagePath, {});
  fs.mkdirSync(packagePath, { recursive: true });
  await copyToPackage(path.join(__dirname, 'common') + path.sep, packagePath + path.sep);
  if (fs.existsSync(path.join(__dirname, packageName))) {
    // Copy package-specific files, these can overwrite common ones.
    await copyToPackage(path.join(__dirname, packageName) + path.sep, packagePath + path.sep);
  }
  for (const file of package.files)
    await copyToPackage(path.join(ROOT_PATH, file), path.join(packagePath, file));

  await copyToPackage(path.join(ROOT_PATH, 'api.json'), path.join(packagePath, 'api.json'));
  await copyToPackage(path.join(ROOT_PATH, 'src/protocol/protocol.yml'), path.join(packagePath, 'protocol.yml'));

  // 4. Generate package.json
  const pwInternalJSON = require(path.join(ROOT_PATH, 'package.json'));
  await writeToPackage('package.json', JSON.stringify({
    name: packageName,
    version: package.version || pwInternalJSON.version,
    description: package.description,
    repository: pwInternalJSON.repository,
    engines: pwInternalJSON.engines,
    homepage: pwInternalJSON.homepage,
    main: 'index.js',
    bin: {
      playwright: './lib/cli/cli.js',
    },
    exports: {
      // Root import: we have a wrapper ES Module to support the following syntax.
      // const { chromium } = require('playwright');
      // import { chromium } from 'playwright';
      '.': {
        import: './index.mjs',
        require: './index.js',
      },
      // Anything else can be required/imported by providing a relative path.
      './': './',
    },
    scripts: {
      install: 'node install.js',
    },
    author: pwInternalJSON.author,
    license: pwInternalJSON.license,
    dependencies: pwInternalJSON.dependencies
  }, null, 2));

  // 5. Generate browsers.json
  const browsersJSON = require(path.join(ROOT_PATH, 'browsers.json'));
  for (const browser of browsersJSON.browsers)
    browser.installByDefault = package.browsers.includes(browser.name);
  await writeToPackage('browsers.json', JSON.stringify(browsersJSON, null, 2));

  // 6. Bake commit SHA into the package
  const commitSHA = spawnSync('git', ['rev-parse', 'HEAD'], {cwd: __dirname, encoding: 'utf8'});
  await writeToPackage('commitinfo', commitSHA.stdout.trim());

  // 7. Run npm pack
  const shell = os.platform() === 'win32';
  const {stdout, stderr, status} = spawnSync('npm', ['pack'], {cwd: packagePath, encoding: 'utf8', shell});
  if (status !== 0) {
    console.log(`ERROR: "npm pack" failed`);
    console.log(stderr);
    process.exit(1);
  }
  const tgzName = stdout.trim();

  // 7. Move result to the outputPath
  fs.renameSync(path.join(packagePath, tgzName), outputPath);
  console.log(outputPath);
})();

async function writeToPackage(fileName, content) {
  const toPath = path.join(packagePath, fileName);
  await writeFileAsync(toPath, content);
}

async function copyToPackage(fromPath, toPath) {
  try {
    fs.mkdirSync(path.dirname(toPath), { recursive: true });
  } catch (e) {
    // the folder might exist already
  }
  await cpAsync(fromPath, toPath);
}

function usage() {
  return `
usage: ${SCRIPT_NAME} <package-name> <output-path> [--no-cleanup]

Creates a .tgz of the package and saves it at the given output path

  --no-cleanup    skip cleaning up generated files from package directory

Example:
  ${SCRIPT_NAME} playwright ./playwright.tgz
`;
}

