// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import fs from 'fs';
import path from 'path';
import {parse, print, types} from 'recast';
import {promisify} from 'util';

const readDir = promisify(fs.readdir);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
const b = types.builders;

const FRONT_END_FOLDER = path.join(__dirname, '..', '..', 'front_end');
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let legacyStatements: any[] = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let legacyTypeDefs: any[] = [];
let targetNamespace = '';
function rewriteSource(source: string, fileName: string) {
  const ast = parse(source);
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const statements: any[] = [];
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const typeDefs: any[] = [];

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  ast.program.body = ast.program.body.map((statement: any) => {
    try {
      switch (statement.type) {
        case 'ExpressionStatement':
          if (statement.expression.type === 'CallExpression') {
            break;
          }

          // Remove Foo = Foo || {}
          if (statement.expression.type === 'AssignmentExpression') {
            if (statement.expression.left.name === targetNamespace) {
              return b.emptyStatement();
            }
          }

          // Remove typedefs of the type Foo.ThingName;
          if (statement.expression.type === 'MemberExpression') {
            // Keep going to the left of namespaces to check the leftmost,
            // and if it is the target namespace, pull it.
            let current = statement.expression.object;
            while (current.object) {
              current = current.object;
            }

            if (current.name === targetNamespace) {
              typeDefs.push(statement);
              return b.emptyStatement();
            }
          }

          if (statement.expression.left.type === 'MemberExpression') {
            // Remove self.Foo = self.Foo || {}
            if (statement.expression.left.object.name === 'self') {
              // Make sure to only grab the statement if it follows the self.X = self.X || {} pattern.
              if (statement.expression.right.type !== 'LogicalExpression' || statement.expression.right.operator !== '||') {
                break;
              }

              // Grab the namespace from the RHS of self.[Namespace]
              if (statement.expression.right.type === 'LogicalExpression') {
                targetNamespace = statement.expression.right.left.property.name;
              }
              return b.emptyStatement();
            }

            // Keep going to the left of namespaces to check the leftmost,
            // and if it is the target namespace, pull it.
            let current = statement.expression.left.object;
            while (current.object) {
              current = current.object;
            }

            if (current.name === targetNamespace) {
              statements.push(statement);
              return b.emptyStatement();
            }

          }
          break;
      }
    } catch (e) {
      console.warn(`Unexpected expression in ${fileName}:`);
      console.warn(print(statement).code);
      console.log(statement);
      process.exit(1);
    }

    return statement;
  });


  // Rewrite legacy RHS to use module name.
  const remappedStatements = statements.map(statement => {
    if (statement.expression.type === 'AssignmentExpression') {
      const { name } = statement.expression.right;
      const innerNamespace = capitalizeFirstLetter(fileName).replace(/.js$/, '');
      statement.expression.right.name = `${targetNamespace}Module.${innerNamespace}.${name}`;
    }

    return statement;
  });

  legacyStatements = [...legacyStatements, ...remappedStatements];
  legacyTypeDefs = [...legacyTypeDefs, ...typeDefs];

  return print(ast).code;
}

function createLegacy() {
  const ast = parse('');
  ast.program.body = legacyStatements.concat(legacyTypeDefs);
  return print(ast).code;
}

function capitalizeFirstLetter(string: string) {
  return string.charAt(0).toUpperCase() + string.slice(1);
}

async function main(folder: string) {
  const pathName = path.join(FRONT_END_FOLDER, folder);
  const srcDir = await readDir(pathName);
  for (const srcFile of srcDir) {
    if (srcFile !== 'ARIAUtils.js' && !srcFile.endsWith('.js')) {
      continue;
    }

    const filePath = path.join(pathName, srcFile);
    const srcFileContents = await readFile(filePath, { encoding: 'utf-8' });
    const dstFileContents = rewriteSource(srcFileContents, srcFile);

    await writeFile(filePath, dstFileContents);
  }

  // Create a foo-legacy.js file
  const dstLegacy = path.join(pathName, `${folder}-legacy.js`);
  const legacyContents = `// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as ${targetNamespace}Module from './${folder}.js';

self.${targetNamespace} = self.${targetNamespace} || {};
${targetNamespace} = ${targetNamespace} || {};

${createLegacy()}
`;
  await writeFile(dstLegacy, legacyContents);
}

if (!process.argv[2]) {
  console.error('No arguments specified. Run this script with "<folder-name>". For example: "ui"');
  process.exit(1);
}

main(process.argv[2]);
