var __rest = (this && this.__rest) || function (s, e) {
  var t = {};
  for (var p in s)
    if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
      t[p] = s[p];
  if (s != null && typeof Object.getOwnPropertySymbols === "function")
    for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
      if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
        t[p[i]] = s[p[i]];
    }
  return t;
};
import { ChangeOperation, DataType, DataUnit, DataUnitStorage } from "@sankhyalabs/core";
import { gql } from "graphql-request";
import { DataFetcher } from "../../DataFetcher";
import { DISTINCT_FILTER_NAME_PREFIX } from "@sankhyalabs/ezui/dist/collection/utils/constants";
import { DataUnitTransient } from "@sankhyalabs/core/dist/dataunit/metadata/UnitMetadata";
import DataUnitDataLoader from "./DataUnitDataLoader";
import PreloadManager from "./cache/PreloadManager";
import ResourceIDUtils from "../../../../resourceid/ResourceIDUtils";
const DATA_UNIT_NAME_PARSER = /dd:\/\/([^/]+)\/([^/?]+)/;
export default class DataUnitFetcher {
  constructor() {
    this.templateByQuery = new Map();
    this.buldTemplates();
  }
  buldTemplates() {
    this.templateByQuery.set("fetchDataUnit", gql `query($name: String!) {
              $queryAlias$: fetchDataUnit(name: $name){
                name
                fields{
                  name
                  defaultValue
                  label
                  visible
                  standAlone
                  readOnly
                  required
                  dataType
                  userInterface
                  calculated
                  group
                  order
                  properties{
                    name
                    value
                  }
                  dependencies{
                    masterFields
                    type
                    expression
                  }
                }
                children{
                  name
                  label
                  links{
                    source
                    target
                  }
                }
              }
            }`);
    this.templateByQuery.set("saveData", gql `mutation($changes: [InputBatchChange!]!) {
            $queryAlias$: batchOperationDataUnit(changes: $changes){
              oldId
              id
              label
              ownerDataUnitName
              fields {
                name
                value
              }
            }
          }`);
    this.templateByQuery.set("fetchDataRecord", gql `query($dataunit: String! $recordID: [String!]) {
            $queryAlias$: fetchDataUnit(name: $dataunit){
              record(id: $recordID){
                id
                label
                fields {
                  name
                  value
                }
              }
            }
          }`);
    this.templateByQuery.set("fetchDistinctColumn", gql `query($dataUnit: String!, $fieldName: String!, $argument: String, $filters: [InputFilter], $parentRecordId: String) {
          $queryAlias$: selectDistinct(dataUnit: $dataUnit, fieldName: $fieldName, argument: $argument, filters: $filters, parentRecordId: $parentRecordId)
      }`);
  }
  static parseDataUnitName(name) {
    if (name == undefined) {
      return undefined;
    }
    const parts = DATA_UNIT_NAME_PARSER.exec(name);
    if (!parts) {
      return undefined;
    }
    return { entityName: parts[1], resourceID: parts[2] };
  }
  getDataUnit(entityName, resourceID, parentDataUnit, configName) {
    if (PreloadManager.applicationResourceID == undefined) {
      //Por falta de um lugar melhor pra iniciar essa variável, fazemos isso aqui.
      ResourceIDUtils.getResourceID().then(resourceID => PreloadManager.applicationResourceID = resourceID);
    }
    const duName = `dd://${entityName}/${resourceID}${configName ? "/" + configName : ""}`;
    const dataUnit = parentDataUnit != undefined ? parentDataUnit.getChildDataunit(duName) : new DataUnit(duName);
    dataUnit.metadataLoader = (dataUnit) => this.loadMetadata(dataUnit);
    dataUnit.dataLoader = (dataUnit, request) => DataUnitDataLoader.loadData(dataUnit, request);
    dataUnit.saveLoader = (_dataUnit, changes) => this.saveData(dataUnit, changes);
    dataUnit.removeLoader = (dataUnit, recordIds) => this.removeRecords(dataUnit, recordIds);
    dataUnit.recordLoader = (dataUnit, recordIds) => this.loadRecord(dataUnit, recordIds);
    dataUnit.allRecordsLoader = (dataUnit) => DataUnitDataLoader.getCachedRecords(dataUnit);
    return dataUnit;
  }
  loadMetadata(dataUnit) {
    return new Promise((resolve, reject) => {
      DataFetcher.get()
        .callGraphQL({
        values: { name: dataUnit.name },
        query: this.templateByQuery.get("fetchDataUnit"),
      })
        .then((resp) => {
        var _a;
        const metadata = {
          name: resp.name,
          label: resp.name,
          children: [...resp.children],
          fields: []
        };
        (_a = resp.fields) === null || _a === void 0 ? void 0 : _a.forEach((source) => {
          let properties = undefined;
          if (Array.isArray(source.properties)) {
            properties = {};
            if (source.calculated) {
              properties.gridHeaderTooltip = "Campos calculados não podem ser ordenados";
            }
            source.properties.forEach((prop) => (properties[prop.name] = prop.value));
          }
          metadata.fields.push(Object.assign(Object.assign({}, source), { properties }));
        });
        metadata.fields.sort((a, b) => a.order - b.order);
        resolve(metadata);
      })
        .catch((error) => {
        reject(error);
      });
    });
  }
  loadSelectDistinct(dataUnit, fieldName, argument) {
    const { parentRecordId, filters } = dataUnit.getLastLoadRequest() || {};
    const filteredFilters = filters.filter(f => f.name !== `${DISTINCT_FILTER_NAME_PREFIX}${fieldName}`);
    const values = {
      dataUnit: dataUnit.name,
      argument,
      fieldName,
      parentRecordId,
      filters: filteredFilters
    };
    return new Promise((resolve, reject) => {
      DataFetcher.get()
        .callGraphQL({
        values,
        query: this.templateByQuery.get("fetchDistinctColumn"),
      })
        .then((result) => {
        resolve(result);
      })
        .catch((error) => {
        reject(error);
      });
    });
  }
  addTransientProperties(dataUnit, updatingFields) {
    const loaderProps = dataUnit.getGlobalLoaderProps();
    if (loaderProps == undefined || loaderProps.size === 0) {
      return updatingFields;
    }
    if (updatingFields == undefined) {
      updatingFields = {};
    }
    Array.from(loaderProps.entries()).forEach(([name, value]) => {
      updatingFields[`transient.${name}`] = value;
    });
    return updatingFields;
  }
  getUpdatingFields(dataUnit, originalUpdatingFields) {
    if (originalUpdatingFields == undefined) {
      return;
    }
    const updatingFields = Object.assign({}, originalUpdatingFields);
    Object.keys(updatingFields).forEach(key => {
      const descriptor = dataUnit.getField(key);
      if (descriptor != undefined && descriptor.standAlone) {
        delete updatingFields[key];
      }
    });
    return this.addTransientProperties(dataUnit, updatingFields);
  }
  saveData(dataUnit, duChanges) {
    const updatedRecordsIds = [];
    const addedRecordsIds = [];
    const changes = duChanges.map((change) => {
      const { dataUnit: changeDU, record, operation } = change;
      const dataUnitInstance = DataUnitStorage.get(changeDU);
      const updatingFields = this.getUpdatingFields(dataUnitInstance, change.updatingFields);
      let parsedUpdatingFields;
      if (updatingFields != undefined) {
        parsedUpdatingFields = Object.entries(updatingFields).map(([fieldName, value]) => {
          const descriptor = dataUnitInstance.getField(fieldName);
          const dataType = descriptor ? descriptor.dataType : DataType.TEXT;
          return { fieldName, dataType, value: dataUnitInstance.valueToString(fieldName, value) };
        });
      }
      if (changeDU === dataUnit.name) {
        if (operation === ChangeOperation.INSERT || operation === ChangeOperation.COPY) {
          addedRecordsIds.push(record.__record__id__);
        }
        else {
          updatedRecordsIds.push(record.__record__id__);
        }
      }
      const reqChange = { dataUnit: changeDU, fields: parsedUpdatingFields, operation, recordId: record.__record__id__ };
      if (change.sourceId) {
        reqChange.sourceId = change.sourceId;
      }
      if (record.__parent__record__id__) {
        reqChange.parentRecordId = record.__parent__record__id__;
      }
      return reqChange;
    });
    return new Promise((resolve, reject) => {
      const changesFormatted = changes.map((_a) => {
        var rest = __rest(_a, []);
        return rest;
      });
      DataFetcher.get()
        .callGraphQL({
        values: { changes: changesFormatted },
        query: this.templateByQuery.get("saveData"),
      })
        .then((resp) => {
        const dataUnitRecords = [];
        resp === null || resp === void 0 ? void 0 : resp.forEach((responseRecord) => {
          const duRecord = {
            __record__id__: responseRecord.id,
            __record__label__: responseRecord.label,
            __owner__dataunit__name__: responseRecord.ownerDataUnitName,
          };
          const dataUnitInstance = DataUnitStorage.get(duRecord.__owner__dataunit__name__) || dataUnit;
          if (responseRecord.oldId) {
            duRecord.__old__id__ = responseRecord.oldId;
          }
          responseRecord.fields.forEach(({ name, value }) => {
            var _a;
            const valueFromString = (_a = dataUnitInstance === null || dataUnitInstance === void 0 ? void 0 : dataUnitInstance.valueFromString) === null || _a === void 0 ? void 0 : _a.call(dataUnitInstance, name, value);
            duRecord[name] = (valueFromString !== undefined) ? valueFromString : value;
          });
          dataUnitRecords.push(duRecord);
        });
        this.updateCache(dataUnit, dataUnitRecords, updatedRecordsIds, addedRecordsIds);
        resolve(dataUnitRecords);
      })
        .catch((error) => {
        reject(error);
      });
    });
  }
  updateCache(dataUnit, savedRecords, toUpdate, toInsert) {
    const recordsById = new Map(savedRecords.map(record => [record.__old__id__ || record.__record__id__, record]));
    const addedRecords = toInsert.map(recordId => {
      const copy = Object.assign({}, recordsById.get(recordId));
      delete copy.__old__id__;
      return copy;
    });
    if (addedRecords.length > 0) {
      const firstRecord = dataUnit.records[0];
      PreloadManager.insertRecords(dataUnit, firstRecord, addedRecords);
    }
    const updatedRecords = toUpdate.map(recordId => (Object.assign({}, recordsById.get(recordId))));
    PreloadManager.updateRecords(dataUnit, updatedRecords);
  }
  getTransientInfo(dataUnit, recordID) {
    const { records } = dataUnit.getSelectionInfo();
    return Object.entries(records.filter(record => record.__record__id__ == recordID)[0]).filter(([key]) => key.startsWith(DataUnitTransient.DATA_UNIT_TRANSIENT_PREFIX_NAME)).map(([name, value]) => ({ fieldName: name, value, dataType: DataType.TEXT }));
  }
  removeRecords(dataUnit, recordIds) {
    const changes = recordIds.map((recordId) => {
      return { dataUnit: dataUnit.name, operation: ChangeOperation.DELETE, recordId, fields: this.getTransientInfo(dataUnit, recordId) };
    });
    return new Promise((resolve, reject) => {
      DataFetcher.get()
        .callGraphQL({
        values: { changes: changes },
        query: this.templateByQuery.get("saveData"),
      })
        .then((_resp) => {
        PreloadManager.removeRecords(dataUnit, dataUnit.records.filter(record => recordIds.includes(record.__record__id__)));
        resolve(recordIds);
      })
        .catch((error) => {
        reject(error);
      });
    });
  }
  loadRecord(dataUnit, recordIds) {
    return new Promise((resolve, reject) => {
      DataFetcher.get()
        .callGraphQL({
        values: { recordID: recordIds, dataunit: dataUnit.name },
        query: this.templateByQuery.get("fetchDataRecord"),
      })
        .then((response) => {
        const dataUnitRecords = [];
        response.record.forEach(responseRecord => {
          const duRecord = { __record__id__: responseRecord.id, __record__label__: responseRecord.label };
          responseRecord.fields.forEach(({ name, value }) => {
            duRecord[name] = dataUnit.valueFromString(name, value);
          });
          dataUnitRecords.push(duRecord);
        });
        //TODO: Chamar o worker para persistir a nova versão do registro
        resolve(dataUnitRecords);
      })
        .catch((error) => {
        reject(error);
      });
    });
  }
}
