import { Chain as ChainFunction } from './types';

import { ChainableQuery as IChainableQuery, Model, MultiQueryFunction, Query, QueryFunction, Resource } from './types';

interface ChainableQuery extends IChainableQuery<Model> {
  $Model?: Resource<Model>;
}

// Chain a query into a new one (when the query updates so does the new query)
export const Chain: ChainFunction = chainConstructor;

function chainConstructor(origQry: ChainableQuery,
                          modelOrQryFn: (Resource<Model> | QueryFunction),
                          qryFn?: QueryFunction): Query<Model> {
  let initialSync = false;
  let ResToQry: Resource<Model>;
  if (!qryFn) {
    ResToQry = origQry.$Model;
    qryFn = modelOrQryFn as QueryFunction;
  } else {
    ResToQry = modelOrQryFn as Resource<Model>;
  }

  // Make sure we have at least finished an initial load before generating the
  // query object
  const _qry = origQry.$promise.then(() => {
    initialSync = true;
    // We copy the array here so that we can do things like .map without auto-updating repercussions.
    return qryFn.call(newqry, [...origQry]);
  });
  const newqry: Query<Model> = ResToQry.query(_qry);

  // Watch our results. If they change then reattempt the query
  origQry.$emitter.on('update', (newRes: any) => {
    // Only do this if we have initially synced
    if (initialSync) {
      // We copy the array here so that we can do things like .map without auto-updating repercussions.
      newqry.replace(qryFn.call(newqry, [...newRes]));
    }
  });

  return newqry;
}
Chain.all = all;

function all(origQueries: Array<Query<Model>>, Model: Resource<Model>, qryFn: MultiQueryFunction) {
  let newqry: Query<Model>;
  const proms = [];
  let initialSync: any;
  let allqries: any[];

  for (let ii = 0; ii < origQueries.length; ii++) {
    const origQry = origQueries[ii];
    proms.push(origQry.$promise);
    origQry.$emitter.on('update', (newRes: any) => {
      if (initialSync) {
        allqries[ii] = [...newRes];
        newqry.replace(qryFn.call(newqry, allqries));
      }
    });
  }

  newqry = Model.query(Promise.all(proms).then(res => {
    initialSync = true;
    allqries = [];
    for (const result of res) {
      allqries.push([...result]);
    }
    return qryFn.call(newqry, allqries);
  }));

  return newqry;
}
