/**
 * DEPRECATED Экспериментальная реализация
 *
 * Следует использовать pinia-хранилище storeRecords
 *
 */

/**  */
class Model {

  type;
  id;
  attributes;
  relationships;
  meta;
  links;

  constructor(data, store) {
    this.store = store;
    this.attributes = {};
    this.relationships = {};
    this.meta = {};
    this.links = {};
    this.merge(data);
  }

  getAttribute(name) {
    return this.attributes[name]
  }

  getRelationship(name) {
    const data = this.relationships[name].data;
    return this.store.find(data)
  }

  identifier() {
    return {
      id: this.id,
      type: this.type
    }
  }

  merge(data) {
    if ("type" in data) {
      this.type = data.type
    }

    if ("id" in data) {
      this.id = data.id
    }

    if ("attributes" in data) {
      Object.assign(this.attributes, data.attributes);
      Object.keys(data.attributes).forEach((name) => {
        if (Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), name) || Object.getOwnPropertyDescriptor(this, name)) {
          return;
        }
        this[name] = null;
        Object.defineProperty(this, name, {
          get: () => this.getAttribute(name),
          configurable: true,
          enumerable: true,
        });
      });
    }

    if ("relationships" in data) {
      Object.entries(data.relationships).forEach(([name, relationship]) => {
        this.relationships[name] = this.relationships[name] || {};
        Object.assign(this.relationships[name], relationship);
        if (Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), name) || Object.getOwnPropertyDescriptor(this, name)) {
          return;
        }
        this[name] = null;
        Object.defineProperty(this, name, {
          get: () => this.getRelationship(name),
          configurable: true,
          enumerable: true,
        });
      });
    }

    if ("links" in data) {
      this.links = data.links
    }

    if ("meta" in data) {
      this.meta = data.meta
    }
  }
}


class Store {

  models;
  graph;

  constructor(models = {}) {
    this.models = models;
    this.graph = {};
  }

  find(type, id = null) {
    if (type === null) {
      return null;
    }

    if (Array.isArray(type)) {
      return type.map((identifier) => this.find(identifier));
    }

    if (typeof type === 'object') {
      return this.find(type.type, type.id);
    }

    return (this.graph[type] && this.graph[type][id]) || null;
  }

  findAll(type) {
    if (!this.graph[type]) {
      return [];
    }

    return Object.keys(this.graph[type]).map((id) => this.graph[type][id]);
  }

  sync(document) {
    const syncResource = this.syncResource.bind(this);

    if ('included' in document) {
      document.included.map(syncResource);
    }

    return Array.isArray(document.data) ? document.data.map(syncResource) : syncResource(document.data);
  }

  syncResource(data) {
    const { type, id } = data;
    this.graph[type] = this.graph[type] || {};

    if (this.graph[type][id]) {
        this.graph[type][id].merge(data);
    } else {
        this.graph[type][id] = this.createModel(data);
    }

    return this.graph[type][id];
  }

  createModel(data) {
    return new(this.models[data.type] || this.models["*"] || Model)(data, this);
  }

  forget(item) {
      delete this.graph[item.type][item.id];
  }

  reset() {
    this.graph = {}
  }
}

function fixedEncodeURIComponent(str) {
  return encodeURIComponent(str).replace(/[!'()*]/g, (function(s) {
      return "%" + s.charCodeAt(0).toString(16).toUpperCase()
  }))
}


class Query {

  query;

  constructor(query = {}) {
    this.query = Object.assign({}, query)
  }

  append(key, value = []) {
    if (typeof key === 'object') {
      Object.entries(key).map((entry) => this.append.apply(this, entry));
    } else {
      this.query[key] = (this.query[key] ? this.query[key] + ',' : '') + value;
    }
  return this;
  }


  set(key, value) {
    if (typeof key === 'object') {
      Object.entries(key).map((entry) => this.set.apply(this, entry));
    } else {
      this.query[key] = value;
    }
  return this;
  }

  delete(key) {
    if (Array.isArray(key)) {
      key.forEach((key) => this.delete(key));
    } else {
      delete this.query[key];
    }
    return this;
  }

  toString() {
    return Object.entries(this.query).sort(((t, e) => t[0].localeCompare(e[0]))).map((([t, e]) => fixedEncodeURIComponent(t) + "=" + fixedEncodeURIComponent(e))).join("&")
  }
}

const jsonapi_store = new Store();

async function jsonapi_query(entrypoint, options = {}) {
  options.headers = options.headers || {};
  options.headers['Accept'] = 'application/vnd.api+json';
  options.headers['x-auth-token'] = window.x_auth_token;

  if (options.body) {
      options.body = JSON.stringify(options.body);
      options.headers['Content-Type'] = 'application/vnd.api+json';
  }

  const response = await fetch('./api/dev/' + entrypoint, options);
  if (response.status === 204) {
    return { response };
  } else {
    const document = await response.json();
    const data = jsonapi_store.sync(document);
    return { response, document, data };
  }
}

export { Model, Store, Query, jsonapi_store, jsonapi_query };
