/*
 * Copyright (C) 2022 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {Injectable} from '@angular/core';
import {SCBackendAggregationConfiguration, SCFacet, SCFacetBucket, SCThing} from '@openstapps/core';

/**
 * TODO
 */
@Injectable()
export class DataFacetsProvider {
  // eslint-disable-next-line no-empty, @typescript-eslint/no-empty-function
  constructor() {}

  /**
   * Adds buckets to a map of buckets (e.g. if a buckets array is [{foo: 1}, {bar: 3}],
   * its bucketsMap is {foo: 1, bar: 3}), if a field 'bar' is added to it it becomes:
   * {foo: 1, bar: 4}
   * @param bucketsMap Buckets array transformed into a map
   * @param fields A field that should be added to buckets (its map)
   */
  // eslint-disable-next-line class-methods-use-this
  addBuckets(bucketsMap: {[key: string]: number}, fields: string[]): {[key: string]: number} {
    for (const field of fields) {
      bucketsMap[field] = bucketsMap[field] === undefined ? 1 : bucketsMap[field] + 1;
    }

    return bucketsMap;
  }

  /**
   * Converts a buckets array to a map
   * @param buckets Buckets from a facet
   */
  // eslint-disable-next-line class-methods-use-this
  bucketsToMap(buckets: SCFacetBucket[]): {[key: string]: number} {
    const bucketsMap: {[key: string]: number} = {};
    for (const bucket of buckets) {
      bucketsMap[bucket.key] = bucket.count;
    }

    return bucketsMap;
  }

  /**
   * Extract facets from data items, optionally combine them with a list of existing facets
   * @param items Items to extract facets from
   * @param aggregations Aggregations configuration(s) from backend
   * @param facets Existing facets to be combined with the facets from the items
   */
  extractFacets(
    items: SCThing[],
    aggregations: SCBackendAggregationConfiguration[],
    facets: SCFacet[] = [],
  ): SCFacet[] {
    if (items.length === 0) {
      if (facets.length === 0) {
        return [];
      }

      return facets;
    }
    const combinedFacetsMap: {[key: string]: {[key: string]: number}} = this.facetsToMap(facets);
    for (const item of items) {
      for (const aggregation of aggregations) {
        let fieldValues = item[aggregation.fieldName as keyof SCThing] as string | string[] | undefined;
        if (fieldValues === undefined) {
          continue;
        }
        if (typeof fieldValues === 'string') {
          fieldValues = [fieldValues];
        }
        if (aggregation.onlyOnTypes === undefined) {
          combinedFacetsMap[aggregation.fieldName] = this.addBuckets(
            combinedFacetsMap[aggregation.fieldName] || {},
            fieldValues,
          );
        } else if (aggregation.onlyOnTypes.includes(item.type)) {
          combinedFacetsMap[aggregation.fieldName] = this.addBuckets(
            combinedFacetsMap[aggregation.fieldName] || {},
            fieldValues,
          );
        }
      }
    }

    return this.mapToFacets(combinedFacetsMap);
  }

  /**
   * Converts facets array into a map (for quicker operations with facets)
   * @param facets Array of facets
   */
  facetsToMap(facets: SCFacet[]): {[key: string]: {[key: string]: number}} {
    const facetsMap: {[key: string]: {[key: string]: number}} = {};
    for (const facet of facets) {
      facetsMap[facet.field] = this.bucketsToMap(facet.buckets);
    }

    return facetsMap;
  }

  /**
   * Converts a buckets map into buckets array (as it is inside of a facet)
   * @param bucketsMap A map from a buckets array
   */
  // eslint-disable-next-line class-methods-use-this
  mapToBuckets(bucketsMap: {[key: string]: number}): SCFacetBucket[] {
    const buckets: SCFacetBucket[] = [];
    for (const key in bucketsMap) {
      if (bucketsMap.hasOwnProperty(key)) {
        const bucket: SCFacetBucket = {key: key, count: bucketsMap[key]};
        buckets.push(bucket);
      }
    }

    return buckets;
  }

  /**
   * Converts facets map into an array of facets (as they are provided by backend)
   * @param facetsMap A map from facets array
   */
  mapToFacets(facetsMap: {[key: string]: {[key: string]: number}}): SCFacet[] {
    const facets: SCFacet[] = [];
    for (const key in facetsMap) {
      if (facetsMap.hasOwnProperty(key)) {
        const facet: SCFacet = {buckets: [], field: ''};
        facet.field = key;
        facet.buckets = this.mapToBuckets(facetsMap[key]);
        facets.push(facet);
      }
    }

    return facets;
  }
}
