text: import { __awaiter, __decorate } from "tslib";
  import * as taskGraphRunner from "task-graph-runner";
  import { AsyncData } from "./AsyncData";
  import { Task } from "@puzzl/core/lib/async/Task";
  import { action } from "mobx";
  import { OperationCanceledError } from "@puzzl/core/lib/async/cancellation";
  export class DataLoader {
      constructor(dataAdapterTypes, dataAdapters, logger, resetOnFetch = false) {
          this.dataAdapters = dataAdapters;
          this.logger = logger;
          this.resetOnFetch = resetOnFetch;
          this.asyncData = new Map();
          this.dataFetchTasks = new Map();
          this.dataRefreshCallbacks = new Map();
          this.dataWatchers = new Map();
          this.depsMap = this.buildDepsMap(dataAdapterTypes);
          let allAdapterTypes = [...this.depsMap.keys()];
          allAdapterTypes.forEach(dataAdapterType => {
              this.asyncData.set(dataAdapterType, new AsyncData());
              this.dataRefreshCallbacks.set(dataAdapterType, () => this.updateData(dataAdapterType, this.context, true));
          });
      }
      buildDepsMap(adapterTypes) {
          let depsMap = new Map();
          let unvisitedNodes = adapterTypes;
          let visitedNodes = new Set();
          while (unvisitedNodes.length) {
              let current = unvisitedNodes.shift();
              if (visitedNodes.has(current)) {
                  continue;
              }
              visitedNodes.add(current);
              let currentDeps = (this.dataAdapters.get(current).dependencies || []);
              depsMap.set(current, currentDeps);
              unvisitedNodes.push(...currentDeps);
          }
          return depsMap;
      }
      load(context) {
          this.context = context;
          taskGraphRunner({
              graph: this.depsMap,
              task: (adapterType) => __awaiter(this, void 0, void 0, function* () {
                  this.destroyWatcher(adapterType);
                  yield this.updateData(adapterType, this.context);
              })
          }).catch(e => {
              this.logger.error(e);
          });
      }
      dispose() {
          [...this.dataFetchTasks.values()].forEach(task => task.cancel());
          [...this.depsMap.keys()].forEach(dataAdapterType => this.destroyWatcher(dataAdapterType));
      }
      updateData(dataAdapterType, context, isWatcherRefresh = false) {
          if (this.dataFetchTasks.has(dataAdapterType)) {
              this.dataFetchTasks.get(dataAdapterType).cancel();
          }
          let dataFetchTask = new Task((cancelToken) => __awaiter(this, void 0, void 0, function* () {
              return this.fetchData(dataAdapterType, context, isWatcherRefresh, cancelToken)
                  .catch(e => {
                  if (!(e instanceof OperationCanceledError)) {
                      throw e;
                  }
              });
          }));
          dataFetchTask.start()
              .catch(e => this.logger.error(e));
          this.dataFetchTasks.set(dataAdapterType, dataFetchTask);
          return dataFetchTask.wait();
      }
      fetchData(dataAdapterType, context, isWatcherRefresh, cancelToken) {
          let resetRafId;
          if (this.resetOnFetch && !isWatcherRefresh) {
              resetRafId = requestAnimationFrame(() => {
                  resetRafId = void 0;
                  if (cancelToken.isCancelled()) {
                      return;
                  }
                  this.asyncData.get(dataAdapterType).reset();
              });
          }
          if (!this.dataAdapters.has(dataAdapterType)) {
              throw new Error(`Data adapter "${dataAdapterType}" is not exposed by any plugin`);
          }
          let dataAdapter = this.dataAdapters.get(dataAdapterType);
          return Promise.resolve(this.getDepAdapterData(dataAdapter))
              .then(depData => {
              if (!depData) {
                  return void 0;
              }
              return dataAdapter.load(context, cancelToken, depData);
          })
              .catch(e => {
              if (e instanceof OperationCanceledError) {
                  throw e;
              }
              this.logger.error(`Couldn't fetch data from adapter "${dataAdapterType}"`, e, { context });
              return void 0;
          })
              .then(data => {
              cancelToken.throwIfCancelled();
              if (resetRafId !== void 0) {
                  cancelAnimationFrame(resetRafId);
                  resetRafId = void 0;
              }
              this.asyncData.get(dataAdapterType).update(data);
              this.setupWatcher(dataAdapterType, context, data);
              if (isWatcherRefresh) {
                  this.refreshDependentAdapters(dataAdapterType, context);
              }
          });
      }
      refreshDependentAdapters(dataAdapterType, context) {
          this.depsMap.forEach((deps, dependant) => {
              if (deps.indexOf(dataAdapterType) !== -1) {
                  this.updateData(dependant, context, true);
              }
          });
      }
      getDepAdapterData(dataAdapter) {
          let dataAdapterDepTypes = (dataAdapter.dependencies || []);
          let depData = new Map();
          for (let depAdapterType of dataAdapterDepTypes) {
              let asyncData = this.asyncData.get(depAdapterType);
              if (asyncData.isLoading()) {
                  throw new Error(`Adapter dependency "${depAdapterType}" should have been loaded by now.`);
              }
              if (!asyncData.isLoaded()) {
                  return void 0;
              }
              depData.set(depAdapterType, asyncData.data);
          }
          return depData;
      }
      setupWatcher(dataAdapterType, context, lastData) {
          this.destroyWatcher(dataAdapterType);
          let dataAdapter = this.dataAdapters.get(dataAdapterType);
          if (dataAdapter.createWatcher) {
              let createWatcherResult = dataAdapter.createWatcher(context, lastData);
              let dataWatchers = Array.isArray(createWatcherResult) ? createWatcherResult : [createWatcherResult];
              dataWatchers.forEach(dataWatcher => {
                  dataWatcher.watch();
                  dataWatcher.onData.subscribe(this.dataRefreshCallbacks.get(dataAdapterType));
              });
              this.dataWatchers.set(dataAdapterType, dataWatchers);
          }
      }
      destroyWatcher(dataAdapterType) {
          let dataWatchers = this.dataWatchers.get(dataAdapterType);
          if (dataWatchers) {
              dataWatchers.forEach(dataWatcher => {
                  dataWatcher.onData.unsubscribe(this.dataRefreshCallbacks.get(dataAdapterType));
                  dataWatcher.unwatch();
              });
          }
      }
      getData() {
          return this.asyncData;
      }
  }
  __decorate([
      action
  ], DataLoader.prototype, "updateData", null);
  //# sourceMappingURL=DataLoader.js.map