"use strict";

/**
 * Класс для работы с мета-данными КБ
 */

 class ClientbaseMetadata {

  _instance;
  _promises = {};
  dataTablesSource;
  dataTables;
  fields;
  groups;
  users;
  dataTablesSourceRoot;
  dataTablesRoot;
  fieldsRoot;

  fieldTypes = {

    NUMBER: 1,               // числовое
    DATE: 2,               // дата
    TEXT: 3,               // текст
    LIST: 4,               // список
    LINK: 5,               // связь
    FILE: 6,               // файл
    USER: 7,               // пользователь
    IMAGE: 9,               // изображение
    GROUP: 14,              // группа
    //  Системные типы полей КБ
    ID: 10,              // id
    USER_ID: 11,              // Кто добавил / user_id
    ADD_TIME: 12,              // Время добавления / add_time
    STATUS: 13

  };


  constructor() {

        if (ClientbaseMetadata._instance) {
          return ClientbaseMetadata._instance;
        }

        ClientbaseMetadata._instance = this;

  }

  //private
  //Загружаем данные из API по URL
  async api(url, method="GET", body=null) {

      url = "./api/dev/" + url;

      let response = await fetch(url, {
          method: method,
          credentials: "include",
          headers: {
              'X-Auth-Token' : window.x_auth_token || null,
              'Content-Type': 'application/vnd.api+json'
          },
          body: body
      });

      response = await response.json();
      return response;

  }

  //private
  //Получаем и сохраняем данные
  //dataProcessor - функция обработки/сохранения данных
  async loadData(url, dataProcessor) {

    if (!this._promises[url]) {

      var self = this;

      this._promises[url] = new Promise(async function(resolve, reject) {

        let data = await self.api(url);

        resolve(dataProcessor(data));

      });

    }

    return this._promises[url];

  }

  //Получить список таблиц
  async getDataTables(isRoot=false, force=false) {

    var result = '', 
      storeVar = "dataTables" + (isRoot ? "Root" : ""), 
      sourceVar = "dataTablesSource" + (isRoot ? "Root" : ""), 
      loadURL = "table" + (isRoot ? "?accessType=root" : "");

    if (this[storeVar] && !force) {
      result = this[storeVar];
    } else {
      self = this;
      result = this.loadData(loadURL, function(result) {
        self[sourceVar] = result.data;
        self[storeVar] = {};
        self[sourceVar].forEach(function(item) {
          this[storeVar][item.id] = item.attributes.table_name;
        }, self);
        return self[storeVar];
      });
    }

    return result;

  }

  //Получить список полей таблицы с возможностью фильтра по типу поля
  //fieldType = 0 - все типы полей
  async getFields(tableId, fieldType=0, isRoot=false, force=false, linkTable=false) {

    var storeVar = "fields" + (isRoot ? "Root" : "");

    if (!this[storeVar]) {
      this[storeVar] = {};
    }

    if (typeof fieldType === "string") {
      let upFieldType = fieldType.toUpperCase();
      if (this.fieldTypes[upFieldType]) {
        fieldType = this.fieldTypes[upFieldType];
      }
    }

    var result = '';

    if (this[storeVar][tableId] && !force) {
      
      result = this[storeVar][tableId][fieldType];

    } else {

      let url = "table/" + tableId + "?include=fields&fieldType=" + fieldType + (isRoot ? "&accessType=root" : "");

      self = this;

      result = await this.loadData(url, async (data) => {
        this[storeVar][tableId] = {};
        this[storeVar][tableId][0] = {};

        for (const item of data.included || []) {
          const id = item.id;
          const type = item.attributes.field_type;
          const name = item.attributes.field_name;

          if (!this[storeVar][tableId][type]) {
            this[storeVar][tableId][type] = {};
          }

          this[storeVar][tableId][0][id] = name;
          this[storeVar][tableId][type][id] = name;

          if (linkTable && type == 5) {
            const linkTableId = item.attributes.type_value.split("|")[0];
            const linkFields = await this.getFields(linkTableId, 0, true);

            for (const [linkFieldId, linkFieldName] of Object.entries(linkFields)) {
              const compositeId = `${id}.${linkFieldId}`;
              const compositeName = `${name}.${linkFieldName}`;

              // Запись в "type 0" и "type 5" — по желанию
              this[storeVar][tableId][0][compositeId] = compositeName;

              if (!this[storeVar][tableId][type]) {
                this[storeVar][tableId][type] = {};
              }
              this[storeVar][tableId][type][compositeId] = compositeName;
            }
              
          }
        }

        return this[storeVar][tableId][fieldType];
      });

    }

    return result;

  }

  //Проверить является ли поле типом "Связь"
  //Поля должны быть предварительно загружены
  isLinkField(tableId, fieldId, isRoot = true) {
    let storeVar = "fields" + (isRoot ? "Root" : "");
    return this[storeVar]?.[tableId]?.[5]?.[fieldId] ? true : false;
  }

  //Получить список подтаблиц таблицы
  async getSubtables(tableId, isRoot=false, force=false) {

    var storeVar = "subtables" + (isRoot ? "Root" : "");

    if (!this[storeVar]) {
      this[storeVar] = {};
    }

    var result = '';

    if (this[storeVar][tableId] && !force) {
      result = this[storeVar][tableId];
    } else {
      let url = "table/" + tableId + "?include=subtables" + (isRoot ? "&accessType=root" : "");

      self = this;

      result = this.loadData(url, function(result) {
        self[storeVar][tableId] = {};

        if (result.included) {
          result.included.forEach(function (item) {
            let id = item.attributes.link_table_id;
            let name = item.attributes.name;

            this[storeVar][tableId][id] = name;
          }, self);
          return self[storeVar][tableId];
        }
      });

    }

    return result;
  }

  //Получить список фильтров таблицы
  async getFilters(tableId, isRoot=false, force=false) {

    var storeVar = "filters" + (isRoot ? "Root" : "");

    if (!this[storeVar]) {
      this[storeVar] = {};
    }

    var result = '';

    if (this[storeVar][tableId] && !force) {
      result = this[storeVar][tableId];
    } else {
      let url = "table/" + tableId + "?include=filters" + (isRoot ? "&accessType=root" : "");
      self = this;
      result = this.loadData(url, function(result) {
        self[storeVar][tableId] = {};
        result.included.forEach(function (item) {
          let id = item.id;
          let name = item.attributes.filter_name;
          this[storeVar][tableId][id] = name;
        }, self);
        return self[storeVar][tableId];
      });
    }
    return result;
  }

  async getGroups(force=false) {

    var result = '';

    if (this.groups && !force) {
      result = this.groups;
    } else {
      self = this;
      result = this.loadData("group", function(result) {
        self.groups = {};
        result.data.forEach(function(item) {
          this.groups[item.id] = item.attributes.name;
        }, self);
        return self.groups;
      });
    }
    return result;
  }



}



/**
 * Общий фронтенд-сервис КБ для старого и нового кода
 *
 */
window.CB = (function(){

    /**********************************************************************************************************
     *  Работа со стилями компонентов
     *********************************************************************************************************/
    const styles = (()=>{
        /** аккумулятор глобальных стилей компонентов */
        let glob = [];

        /**
         * Добавить глобальные стили (в общий style в head)
         *
         * @param string value строка css-спецификаций
         * @returns void
         */
        function globed(value) {
            glob.push(value);
        }

        /**
         * Добавить локальные стили данного компонента (в vue-стиле)
         *
         * TODO: не реализовано, пока повторяет логику globed()
         *
         * @param string value строка css-спецификаций
         * @returns string атрибут локализации данного компонента (x-v-data-SWB34UHS16)
         */
        function scoped(value) {
            globed(value);
        }

        /**
         * Добавить глобальные стили на страницу
         */
        function inject() {
            const style = Object.assign(document.createElement('style'), { textContent: glob.join('') } );
            const ref = document.head.getElementsByTagName('style')[0] || null;
            document.head.insertBefore(style, ref);
        }

        return {
            globed,
            scoped,
            inject,
        }
    })();

    /**********************************************************************************************************
     *  Глобальное хранилище КБ
     *********************************************************************************************************/
    const store = (()=>{
        /** глобальный vuex-store */
        let store = Vuex.createStore({
            state: () => ({
                hello: 'hello from CB.store!',
            }),
        });

        /** Получить хранилище */
        function useStore() {
            return store;
        }

        /** Подключить модуль хранилища */
        function register(path, module) {
            store.registerModule(path, module);
        }

        /** Отключить модуль хранилища */
        function unregister(path, module) {
            store.unregisterModule(path, module);
        }

        /** Применить мутацию */
        function commit(type, payload) {
            store.commit(type, payload)
        }

        /** Вызвать действие */
        function dispatch(type, payload) {
            store.dispatch(type, payload)
        }

        return {
            useStore,
            register,
            unregister,
            dispatch,
        }
    })();

    /**********************************************************************************************************
     *  Publisher/Subscriber
     *********************************************************************************************************/
    const pubsub = (() => {
      let subscribers = {};

      return {
        /**
         * Подписаться на событие.
         * @param {string} event идентификатор события
         * @param {function} callback
         */
        async subscribe(event, callback) {
          if (!subscribers[event]) {
            subscribers[event] = [];
          }
          subscribers[event].push(callback);
        },

        /**
         * Отписаться от события.
         * @param {string} event идентификатор события
         * @returns
         */
        async unsubscribe(event) {
          if (!subscribers[event]) {
            return;
          }

          delete(subscribers[event]);
        },

        /**
         * Запустить событие.
         * @param {string} event идентификатор события
         * @param {any} data
         * @returns
         */
        async publish(event, data) {
          if (!subscribers[event]) {
            return;
          }
          subscribers[event].forEach(subscriberCallback => subscriberCallback(data));
        },
      }
    })();

    /**********************************************************************************************************
     *  Реализация DataRepositoryInterface для фронта
     *********************************************************************************************************/
     const repository = ( () => {

      function getRecord(table, id) {
        throw 'getRecord  is not implemented'
      }

      function query(table, options) {
        // использовать библиотеку https://github.com/crcn/sift.js для формирования запросов,
        // обеспечив совместимость с форматом описания запросов на беке (src/CBEXT/query_language)
        throw 'query  is not implemented'
      }

      function createRecord(table, data) {
        throw 'createRecord  is not implemented'
      }

      function updateRecord(table, id, data) {
        throw 'updateRecord  is not implemented'
      }

      function deleteRecord(table, id) {
        throw 'deleteRecord  is not implemented'
      }

      return {
          getRecord,
          query,
          createRecord,
          updateRecord,
          deleteRecord,
      }
  })();


  const metadata = new ClientbaseMetadata();

  /**********************************************************************************************************
     *  CB - глобальный сервис КБ
     *********************************************************************************************************/
    return {
        /** Работа со стилями компонентов */
        styles,
        /** DEPRECATED, Общее хранилище данных */
        store,
        /** Отображение чатов в трее */
        display_chats_in_tray: {},
        /** DEPRECATED ? прогрузка глобальных переменных с бека */
        globals: {
            user: {},
            config: {},
        },
        /** Подписка, публикация событий */
        pubsub,
        /** Доступ к записям пользовательских таблиц для старого кода */
        repository,
        metadata,
        /** Заполняется конструктором отчетов */
        report: {},
    }
})();
