/* globals $ */
import computed from 'ember-computed';
import Component from 'ember-component';
import injectService from 'ember-service/inject';
import { isXTag, matchXTags, generateFragments } from 'web-client/utilities/i18n-utils';
import { assign } from 'ember-platform';
import { LiteralWrapper } from 'ember-intl/helpers/l';
import { isEmpty, typeOf } from 'ember-utils';

/**
 * Usage/Example Doc: https://twitchtv.atlassian.net/wiki/display/ENG/Ember+Intl+Yield+Components
 * Dev Doc: https://twitchtv.atlassian.net/wiki/display/ENG/I18n+Dev+Documentation
 *
 * Properties:
 * args: passed as arguments for the translation
 * dynamicYields: placeholders for which the component will yield on. Can be either string (if there's just one) or string array
 *
 * The block statement passed to the component is evaluated to determine the value for each dynamic yield.
 *
 * Notes:
 * The args hash can take a component.
 * All string args will be replaced in the translation
 * Use X-Tags to be able to wrap a substring in the translation.
 * - Note: Any dynamic yields or components inside X-Tags will not be yielded for automatically. Another intl/string-yield component is required
 */
const comp = Component.extend({
  tagName: 'span',
  intl: injectService(),

  args: null,
  dynamicYields: null,

  /**
   * Looks up a translation key and Ember Intl will create a mixpanel event if it does not exist.
   */
  formatString(key, options) {
    return this.get('intl').t(key, options);
  },

  /**
   * Separating because:
   * String Args are passed directly intl-messageformat
   * Components Args need placeholders (for intl-messageformat) to be yielded internally
   */
  _args: computed('args', function () {
    let args = this.get('args') || {};
    let strings = {};
    let components = {};

    Object.keys(args).forEach((key) => {
      if (typeof args[key] === 'object') {
        components[key] = args[key];
      } else {
        strings[key] = args[key];
      }
    });
    return {strings, components};
  }),

  /**
   * Separating because:
   * Args yields need placeholders (for intl-messageformat)
   * XTags need to be identified for the regex split
   *
   * TODO: Investigate .[] or .@each to support dynamic dynamicYields
   */
  _dynamicYields: computed('dynamicYields', function () {
    let xTags = [];
    let args = [];
    let dynamicYields = this.get('dynamicYields') || [];

    if (typeOf(dynamicYields) === 'string') {
      dynamicYields = [dynamicYields]; // Can use just a string for a single dynamic yield
    }

    dynamicYields.forEach((elem) => {
      if (isXTag(elem)) {
        xTags.push(elem);
      } else {
        args.push(elem);
      }
    });

    return {args, xTags};
  }),

  /**
   * When an ICU string is created, all the arguments need values.
   * This creates simple placeholder values for the arguments that need to be yielded
   *
   * Result:
   * Map of argument to its placeholder value. Example:
   * game => {game}
   * channelName => {channelName}
   *
   * Contains components and dynamic arg yields
   */
  _yieldArgToPlaceholder: computed('_dynamicYields.args', '_args.components', function () {
    let dynamicArgYields = this.get('_dynamicYields.args');
    let componentArgs = this.get('_args.components');

    let yieldArgsObj = {};
    dynamicArgYields.forEach((elem) => {
      yieldArgsObj[elem] = `{${elem}}`;
    });

    Object.keys(componentArgs).forEach((elem) => {
      yieldArgsObj[elem] = `{${elem}}`;
    });

    return yieldArgsObj;
  }),

  /**
   * Creates the translation from the appropriate string args and args placeholders.
   */
  translation: computed('_yieldArgToPlaceholder', '_args.strings', 'key', function () {
    let intl = this.get('intl');
    let key = this.get('key');

    if (isEmpty(key)) {
      throw new Error('Key must be a valid string');
    }

    let yieldArgToPlaceholder = this.get('_yieldArgToPlaceholder');
    let stringArgs = this.get('_args.strings');

    let options = assign({}, yieldArgToPlaceholder, stringArgs);
    try {
      if (key instanceof LiteralWrapper) {
        return intl.formatMessage(key.value, options);
      }

      return this.formatString(key, options);
    } catch (error) {
      if (error.message.indexOf('A value must be provided for') === 0) {
        console.warn('Confirm that all arguments for translation are defined either in args hash or dynamicYields array',
            false, {id: 'intl-yield'});
      }
      throw error;
    }
  }),

  /**
   * Returns an array of objects that's iterated through in the template.
   * Each object can be either details about a yield (xTag or argument), component, or a text
   */
  fragments: computed('translation', '_yieldArgToPlaceholder', '_args.components', '_dynamicYields.xTags', function () {
    let translation = this.get('translation');
    let placeholderToFragmentData = {}; // Example: {game} => {type: 'yield', text: 'game'}
    let dynamicXTagYields = this.get('_dynamicYields.xTags');

    matchXTags(translation, dynamicXTagYields).forEach((key) => {
      let $key = $(key);

      placeholderToFragmentData[key] = {
        type: 'yield',
        name: $key.prop('tagName').toLowerCase(),
        text: $key.html()
      };
    });

    let componentArgs = this.get('_args.components');

    let yieldArgToPlaceholder = this.get('_yieldArgToPlaceholder');
    Object.keys(yieldArgToPlaceholder).forEach((namedArg) => {
      let placeholder = yieldArgToPlaceholder[namedArg];

      if (namedArg in componentArgs) {
        placeholderToFragmentData[placeholder] = {
          type: 'component',
          name: namedArg,
          component: componentArgs[namedArg]
        };
      } else {
        placeholderToFragmentData[placeholder] = {
          type: 'yield',
          name: namedArg,
          text: namedArg
        };
      }
    });

    let yieldStrings = Object.keys(placeholderToFragmentData);
    return generateFragments(translation, yieldStrings)
        .map((key) => {
          if (key in placeholderToFragmentData) {
            return placeholderToFragmentData[key];
          }

          // If it's not in the placeholder map, it's just a simple string/text
          return {
            type: 'text',
            text: key
          };
        });
  })
});

comp.reopenClass({
  positionalParams: ['key']
});

export default comp;
