/**
 * For the full copyright and license information, please view the
 * docs/licenses/LICENSE.txt file that was distributed with this source code.
 */

import ProductSuppliersMap from '@pages/product/supplier/product-suppliers-map';
import {Supplier, ProductSupplier, BaseProductSupplier} from '@pages/product/supplier/types';

export default class ProductSuppliersCollection {
  private defaultSupplierId: string;

  private wholesalePrice: number;

  private map: any;

  private $collectionRow: JQuery;

  private $productSuppliersCollection: JQuery;

  private $productsTable: JQuery;

  private $productsTableBody: JQuery;

  private readonly prototypeTemplate: string;

  private readonly prototypeName: string;

  private selectedSuppliers: Array<Supplier>;

  private readonly productSuppliers: Record<string, ProductSupplier>;

  private readonly baseDataForSupplier: BaseProductSupplier;

  constructor(
    productSuppliersFormId: string,
    defaultSupplierId: string,
    wholesalePrice: number,
  ) {
    this.defaultSupplierId = defaultSupplierId;
    this.wholesalePrice = wholesalePrice;
    this.map = ProductSuppliersMap(productSuppliersFormId);
    this.$productSuppliersCollection = $(this.map.productSuppliersCollection);
    this.$collectionRow = this.$productSuppliersCollection.parents(this.map.productSuppliersCollectionRow);
    this.$productsTable = $(this.map.productSuppliersTable);
    this.$productsTableBody = $(this.map.productsSuppliersTableBody);

    this.selectedSuppliers = [];
    this.productSuppliers = {};
    this.prototypeTemplate = this.$productSuppliersCollection.data('prototype');
    this.prototypeName = this.$productSuppliersCollection.data('prototypeName');
    this.baseDataForSupplier = this.getBaseDataForSupplier();

    this.init();
  }

  setSelectedSuppliers(selectedSuppliers: Array<Supplier>): void {
    this.selectedSuppliers = selectedSuppliers;

    // First add product suppliers
    const selectedSupplierIds: string[] = [];
    this.selectedSuppliers.forEach((supplier: Supplier) => {
      selectedSupplierIds.push(supplier.supplierId);
      this.addSupplier(supplier);
    });

    // Then remove the unselected ones
    const storedSupplierIds = Object.keys(this.productSuppliers);
    storedSupplierIds.forEach((supplierId: string) => {
      if (!selectedSupplierIds.includes(supplierId)) {
        this.removeSupplier(supplierId);
      }
    });

    // Update suppliers list
    this.renderSuppliers();
    // Update internal data based on list values
    this.memorizeCurrentSuppliers();
    // Toggle component visibility
    this.toggleRowVisibility();
  }

  setDefaultSupplierId(defaultSupplierId: string): void {
    this.defaultSupplierId = defaultSupplierId;
    this.selectedSuppliers.forEach((supplier: Supplier) => {
      // eslint-disable-next-line no-param-reassign
      supplier.isDefault = supplier.supplierId === defaultSupplierId;
    });

    // Update internal data (mostly the isDefault value)
    this.memorizeCurrentSuppliers();
  }

  private init(): void {
    // First memorize product suppliers data
    this.memorizeCurrentSuppliers();
    // Then init selected suppliers from the initial table content
    this.selectedSuppliers = this.getSuppliersFromTable();
    // Finally toggle component visibility
    this.toggleRowVisibility();

    this.$productsTable.on('change', ':input', () => {
      this.memorizeCurrentSuppliers();
    });
    this.$productsTable.on('change', 'select[name$="[currency_id]"]', (e) => {
      const symbol = $(e.target).find(':selected')?.attr('symbol');
      $(e.target)
        .parents(this.map.productsSupplierRowSelector)
        .find('.money-type .input-group-prepend .input-group-text')
        // @ts-ignore
        .html(symbol);
    });
  }

  private addSupplier(supplier: Supplier): void {
    const defaultSupplier: ProductSupplier | undefined = this.getDefaultProductSupplier();
    const defaultPrice: number = defaultSupplier?.price || this.wholesalePrice;

    if (typeof this.productSuppliers[supplier.supplierId] === 'undefined') {
      const newSupplier: ProductSupplier = Object.create(this.baseDataForSupplier);
      newSupplier.supplierId = supplier.supplierId;
      newSupplier.supplierName = supplier.supplierName;
      newSupplier.price = defaultPrice;
      this.productSuppliers[supplier.supplierId] = newSupplier;
    } else {
      const existingSupplier: ProductSupplier = this.productSuppliers[supplier.supplierId];

      if (existingSupplier.removed) {
        existingSupplier.removed = false;
        // Update existing supplier with default price
        existingSupplier.price = defaultPrice;
      }
    }
  }

  private removeSupplier(supplierId: string): void {
    if (Object.prototype.hasOwnProperty.call(this.productSuppliers, supplierId)) {
      this.productSuppliers[supplierId].removed = true;
    }
  }

  /**
   * Memorize suppliers to be able to re-render them later.
   * Flag `removed` allows identifying whether supplier was removed from list or should be rendered
   */
  private memorizeCurrentSuppliers(): void {
    const rows = document.querySelectorAll(this.map.productsSuppliersRows);

    if (!rows.length) {
      return;
    }

    rows.forEach((row: HTMLElement) => {
      const supplierIndex: string = <string> row.dataset.supplierIndex;
      const supplierId = <string> $(this.map.productSupplierRow.supplierIdInput(supplierIndex)).val();

      this.productSuppliers[supplierId] = {
        supplierId,
        productSupplierId: <string> $(this.map.productSupplierRow.productSupplierIdInput(supplierIndex)).val(),
        supplierName: <string> $(this.map.productSupplierRow.supplierNameInput(supplierIndex)).val(),
        reference: <string> $(this.map.productSupplierRow.referenceInput(supplierIndex)).val(),
        price: <number> $(this.map.productSupplierRow.priceInput(supplierIndex)).val(),
        currencyId: <string> $(this.map.productSupplierRow.currencyIdInput(supplierIndex)).val(),
        isDefault: supplierId === this.defaultSupplierId,
        removed: false,
      };
    });
  }

  private getSuppliersFromTable(): Array<Supplier> {
    const suppliers: Array<Supplier> = [];
    const rows = document.querySelectorAll(this.map.productsSuppliersRows);

    if (!rows.length) {
      return suppliers;
    }

    rows.forEach((row: HTMLElement) => {
      const supplierIndex: string = <string> row.dataset.supplierIndex;
      const supplierId = <string> $(this.map.productSupplierRow.supplierIdInput(supplierIndex)).val();

      suppliers.push({
        supplierId,
        supplierName: <string> $(this.map.productSupplierRow.supplierNameInput(supplierIndex)).val(),
        isDefault: supplierId === this.defaultSupplierId,
      });
    });

    return suppliers;
  }

  private renderSuppliers(): void {
    this.$productsTableBody.empty();

    // Loop through select suppliers so that we use the same order as in the select list
    this.selectedSuppliers.forEach((selectedSupplier: Supplier) => {
      const supplier = this.productSuppliers[selectedSupplier.supplierId];

      if (supplier.removed) {
        return;
      }

      const productSupplierRow = this.prototypeTemplate.replace(
        new RegExp(this.prototypeName, 'g'),
        <string> supplier.supplierId,
      );

      this.$productsTableBody.append(productSupplierRow);
      // Fill inputs
      const rowMap = this.map.productSupplierRow;
      $(rowMap.supplierIdInput(supplier.supplierId)).val(supplier.supplierId);
      $(rowMap.supplierNamePreview(supplier.supplierId)).html(supplier.supplierName);
      $(rowMap.supplierNameInput(supplier.supplierId)).val(supplier.supplierName);
      $(rowMap.productSupplierIdInput(supplier.supplierId)).val(supplier.productSupplierId);
      $(rowMap.referenceInput(supplier.supplierId)).val(supplier.reference);
      $(rowMap.priceInput(supplier.supplierId)).val(supplier.price);
      $(rowMap.currencyIdInput(supplier.supplierId)).val(supplier.currencyId);

      // Update currency symbol
      const symbol = $(rowMap.currencyIdInput(supplier.supplierId)).find(':selected')?.attr('symbol');

      if (symbol) {
        $(rowMap.currencySymbol(supplier.supplierId)).html(symbol);
      }
    });
  }

  private toggleRowVisibility() {
    if (this.selectedSuppliers.length === 0) {
      this.hideCollectionRow();

      return;
    }

    this.showCollectionRow();
  }

  private showCollectionRow(): void {
    this.$collectionRow.removeClass('d-none');
  }

  private hideCollectionRow(): void {
    this.$collectionRow.addClass('d-none');
  }

  /**
   * Create a "shadow" prototype just to parse default values set inside the input fields,
   * this allow to build an object with default values set in the FormType
   */
  private getBaseDataForSupplier(): BaseProductSupplier {
    const rowPrototype = new DOMParser().parseFromString(
      this.prototypeTemplate,
      'text/html',
    );

    return {
      removed: false,
      productSupplierId: <string> this.extractFromPrototype(this.map.productSupplierRow.productSupplierIdInput, rowPrototype),
      reference: <string> this.extractFromPrototype(this.map.productSupplierRow.referenceInput, rowPrototype),
      price: <number> this.extractFromPrototype(this.map.productSupplierRow.priceInput, rowPrototype),
      currencyId: <string> this.extractFromPrototype(this.map.productSupplierRow.currencyIdInput, rowPrototype),
      isDefault: false,
    };
  }

  private extractFromPrototype(selector: (supplierIndex: string) => string, rowPrototype: Document): number | string | null {
    const rowField: HTMLInputElement | null = rowPrototype.querySelector(selector(this.prototypeName));

    return rowField?.value ?? null;
  }

  private getDefaultProductSupplier(): ProductSupplier| undefined {
    return Object.values(this.productSuppliers).find((productSupplier: ProductSupplier) => productSupplier.isDefault);
  }
}
