import {createFullOverrideContext, updateOverrideContexts} from './repeat-utilities';

/**
 * A strategy for repeating a template over a Map.
 */
export class MapRepeatStrategy {
  /**
   * Gets a Map observer.
   * @param items The items to be observed.
   */
  getCollectionObserver(observerLocator, items) {
    return observerLocator.getMapObserver(items);
  }

  /**
   * Process the provided Map entries.
   * @param items The entries to process.
   */
  instanceChanged(repeat, items) {
    let removePromise = repeat.removeAllViews(true, !repeat.viewsRequireLifecycle);
    if (removePromise instanceof Promise) {
      removePromise.then(() => this._standardProcessItems(repeat, items));
      return;
    }
    this._standardProcessItems(repeat, items);
  }

  /**
   * @internal
   */
  _standardProcessItems(repeat, items) {
    let index = 0;
    let overrideContext;

    items.forEach((value, key) => {
      overrideContext = createFullOverrideContext(repeat, value, index, items.size, key);
      repeat.addView(overrideContext.bindingContext, overrideContext);
      ++index;
    });
  }

  /**
   * Handle changes in a Map collection.
   * @param map The underlying Map collection.
   * @param records The change records.
   */
  instanceMutated(repeat, map, records) {
    let key;
    let i;
    let ii;
    let overrideContext;
    let removeIndex;
    let addIndex;
    let record;
    let rmPromises = [];
    let viewOrPromise;

    for (i = 0, ii = records.length; i < ii; ++i) {
      record = records[i];
      key = record.key;
      switch (record.type) {
      case 'update':
        removeIndex = this._getViewIndexByKey(repeat, key);
        viewOrPromise = repeat.removeView(removeIndex, true, !repeat.viewsRequireLifecycle);
        if (viewOrPromise instanceof Promise) {
          rmPromises.push(viewOrPromise);
        }
        overrideContext = createFullOverrideContext(repeat, map.get(key), removeIndex, map.size, key);
        repeat.insertView(removeIndex, overrideContext.bindingContext, overrideContext);
        break;
      case 'add':
        addIndex = repeat.viewCount() <= map.size - 1 ? repeat.viewCount() : map.size - 1;
        overrideContext = createFullOverrideContext(repeat, map.get(key), addIndex, map.size, key);
        repeat.insertView(map.size - 1, overrideContext.bindingContext, overrideContext);
        break;
      case 'delete':
        if (record.oldValue === undefined) { return; }
        removeIndex = this._getViewIndexByKey(repeat, key);
        viewOrPromise = repeat.removeView(removeIndex, true, !repeat.viewsRequireLifecycle);
        if (viewOrPromise instanceof Promise) {
          rmPromises.push(viewOrPromise);
        }
        break;
      case 'clear':
        repeat.removeAllViews(true, !repeat.viewsRequireLifecycle);
        break;
      default:
        continue;
      }
    }

    if (rmPromises.length > 0) {
      Promise.all(rmPromises).then(() => {
        updateOverrideContexts(repeat.views(), 0);
      });
    } else {
      updateOverrideContexts(repeat.views(), 0);
    }
  }

  /**
   * @internal
   */
  _getViewIndexByKey(repeat, key) {
    let i: number;
    let ii: number;
    let child;

    for (i = 0, ii = repeat.viewCount(); i < ii; ++i) {
      child = repeat.view(i);
      if (child.bindingContext[repeat.key] === key) {
        return i;
      }
    }

    return undefined;
  }
}
