/* globals require */
/* eslint-disable no-console */

const Handlebars = require('handlebars');
const fs = require('fs');
const readlineSync = require('readline-sync');
const colors = require('colors/safe');
const EOL = require('os').EOL;
const yamljs = require('yaml-js');

const baseLocaleSourcePath = './i18n/translations/en-us.yaml';

/**
 * This script helps with externalizing strings from handlebar files.
 * Just run the script from command line. Prompts will be shown
 *
 * Options: Check under User Definable comment
 * 
 * Limitations:
 * Can only handle one i18n change per line
 * I18n arguments must be on the same line
 */

// User Definable Start
const numContextLines = 5; // How many lines to show before and after
const experimentName = null; // Example: 'INTL_I18N';

const createStringReplacement = function (keyName, phrase, experimentName) {
  return `t \'${keyName}\'`;
  // Use the following if you desire to use an alternate component that uses an experiment
  // return `t-experiment-with-default \'${keyName}\' (i18n \'${phrase}\')${experimentName ? '  ' + experimentName : ''}`;
}
// User Definable End

function process(content, options, type, self) {
  if (!self.done && content.path.type === 'PathExpression' && content.path.original === 'i18n') {
    let start = content.loc.start;
    let end = content.loc.end;
    let currLine = Math.max(0, start.line-1);
    let origLine = options.origStr[currLine];

    let keyName;
    if (start.line !== end.line) {
      console.log(`This script does not support I18n that spans multiple lines. Skipping I18n on line: ${start.line}`);
      return;
    }

    // To grab some context (strings before and after)
    let startLine = Math.max(currLine-numContextLines, 0);
    let endLine = Math.min(options.origStr.length, end.line-1+numContextLines);

    console.log(options.origStr.slice(startLine, currLine).join(EOL));
    console.log(colors.green(options.origStr[currLine])); // printing this separately for colors
    console.log(options.origStr.slice(currLine+1, endLine).join(EOL));

    let phrase = content.params[0].value;
    if (phrase.indexOf("'") >= 0) {
      console.log('This has a single quote, externalize this manually.');
    }

    keyName = readlineSync.question('Key name? or (s)kip or (d)one\n');
    if (keyName === 's') {
      return;
    } else if (keyName === 'd') {
      self.done = true;
      return;
    }

    
    if (keyName in options.yamlObj || keyName in options.newYamlObj) {
      console.log('Key name already exists, not adding to yaml file');
    } else {
      options.newYamlObj[keyName] = {
        phrase: phrase
      };
    }
    options.changedKeys.push({
      key: keyName,
      phrase: phrase
    });

    let replacement = createStringReplacement(keyName, phrase, experimentName);

    if (type === 'MustacheStatement') {
      replacement = `{{${replacement}}}`;
    } else { // SubExpression
      replacement = `(${replacement})`;
    }
    // Replacing this in the string array because it's difficult to preserve formatting
    options.origStr[currLine] = origLine.slice(0, start.column) + replacement + origLine.slice(end.column);
  }
}

class TreeVisitor {
  constructor() {
    this.done = false;
  }

  accept(object, options = {}) {
    var handler = this[object.type];

    if (handler) {
      return handler.call(this, object, options);
    }
  }
  
  Hash(hashNode, options) {
    hashNode.pairs = hashNode.pairs.map((statement) => {
      return this.accept(statement, options);
    });
    return hashNode;
  }

  Program(programNode, options) {
    programNode.body = programNode.body.map((statement) => {
      return this.accept(statement, options);
    });
    return programNode;
  }

  BlockStatement(blockNode, options) {
    if (blockNode.path.original === 't-experiment-with-default') {
      return blockNode;
    }

    if (blockNode.program) { blockNode.program = this.accept(blockNode.program, options); }
    if (blockNode.inverse) { blockNode.inverse = this.accept(blockNode.inverse, options); }
    if (blockNode.hash) { blockNode.hash = this.accept(blockNode.hash, options); }

    if (blockNode.params) {
      blockNode.params = blockNode.params.map((statement) => {
        return this.accept(statement, options);
      });
    }

    return blockNode;
  }

  HashPair(hashPairNode, options) {
    if (hashPairNode.value) { hashPairNode.value = this.accept(hashPairNode.value, options); }
  }

  SubExpression(content, options) {
    if (content.path.original === 't-experiment-with-default') {
      return content;
    }

    process(content, options, 'SubExpression', this);
    let self = this;
    content.params = content.params.map(function (statement) {
      return self.accept(statement, options);
    });
    return content;
  }
  
  MustacheStatement(content, options) {
    process(content, options, 'MustacheStatement', this);
    return content;
  }
}

function formatKeys(obj) {
  return obj.map(function (keyObj) {
    // replace single quote with two single quotes
    return `${keyObj.key}: \n  phrase: '${keyObj.phrase}'`;
  }).join(EOL);
}


let fileName = readlineSync.question('File name: \n');
let template = fs.readFileSync(fileName, 'utf8');
let yamlObj = yamljs.load(fs.readFileSync(baseLocaleSourcePath, 'utf8'));

let ast = Handlebars.parse(template);
let options = {origStr: template.split(EOL), changedKeys: [], yamlObj: yamlObj, newYamlObj: {}};
new TreeVisitor().accept(ast, options);

fs.writeFileSync(fileName, options.origStr.join(EOL), 'utf8');
if (Object.keys(options.newYamlObj).length > 0) {
  console.log(EOL, EOL);
  console.log('Insert the following into en-us.yaml. Remember to change any translations that are plurals.');
  console.log(formatKeys(options.changedKeys));
}
