import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { logger } from '@isa/core/logging';
import { PurchaseOptionsModalContext } from '../purchase-options-modal.data';
import { PurchaseOptionsService } from './purchase-options.service';
import { PurchaseOptionsState } from './purchase-options.state';
import {
  Availability,
  Branch,
  CanAdd,
  FetchingAvailability,
  Item,
  ItemData,
  ItemPayloadWithSourceId,
  PurchaseOption,
} from './purchase-options.types';
import * as Selectors from './purchase-options.selectors';
import {
  getPurchaseOptionForOrderType,
  isDownload,
  isGiftCard,
  isItemDTO,
  isShoppingCartItemDTO,
  mapToItemData,
  mapToItemPayload,
} from './purchase-options.helpers';
import { Observable } from 'rxjs';
import { first, switchMap } from 'rxjs/operators';
import {
  DEFAULT_PRICE_DTO,
  DEFAULT_PRICE_VALUE,
  DEFAULT_VAT_VALUE,
} from '../constants';
import {
  AddToShoppingCartDTO,
  EntityDTOContainerOfDestinationDTO,
  UpdateShoppingCartItemDTO,
} from '@generated/swagger/checkout-api';
import { uniqueId } from 'lodash';
import { VATDTO } from '@generated/swagger/oms-api';
import { DomainCatalogService } from '@domain/catalog';
import { ItemDTO } from '@generated/swagger/cat-search-api';
import {
  Loyalty,
  OrderTypeFeature,
  Promotion,
} from '@isa/checkout/data-access';
import { ensureCurrencyDefaults } from '@isa/common/data-access';

@Injectable()
export class PurchaseOptionsStore extends ComponentStore<PurchaseOptionsState> {
  get type() {
    return this.get(Selectors.getType);
  }

  type$ = this.select(Selectors.getType);

  get useRedemptionPoints() {
    return this.get(Selectors.getUseRedemptionPoints);
  }

  useRedemptionPoints$ = this.select(Selectors.getUseRedemptionPoints);

  get shoppingCartId() {
    return this.get(Selectors.getShoppingCartId);
  }

  shoppingCartId$ = this.select(Selectors.getShoppingCartId);

  get customerFeatures() {
    return this.get((s) => s.customerFeatures) ?? {};
  }

  get items() {
    return this.get(Selectors.getItems);
  }

  items$ = this.select(Selectors.getItems);

  get purchaseOption() {
    return this.get(Selectors.getPurchaseOption);
  }

  purchaseOption$ = this.select(Selectors.getPurchaseOption);

  get availabilities() {
    return this.get(Selectors.getAvailabilities);
  }

  availabilities$ = this.select(Selectors.getAvailabilities);

  get defaultBranch() {
    return this.get(Selectors.getDefaultBranch);
  }

  defaultBranch$ = this.select(Selectors.getDefaultBranch);

  get pickupBranch() {
    return this.get(Selectors.getPickupBranch);
  }

  pickupBranch$ = this.select(Selectors.getPickupBranch);

  get inStoreBranch() {
    return this.get(Selectors.getInStoreBranch);
  }

  inStoreBranch$ = this.select(Selectors.getInStoreBranch);

  get purchasingOptions() {
    return this.get(Selectors.getPurchasingOptions);
  }

  purchasingOptions$ = this.select(Selectors.getPurchasingOptions);

  get selectedItemIds() {
    return this.get(Selectors.getSelectedItemIds);
  }

  selectedItemIds$ = this.select(Selectors.getSelectedItemIds);

  get purchaseOptionsInAvailabilities(): PurchaseOption[] {
    return this.get(Selectors.getPurchaseOptionsInAvailabilities);
  }

  getPurchaseOptionsInAvailabilities$ = this.select(
    Selectors.getPurchaseOptionsInAvailabilities,
  );

  get itemsForList() {
    return this.get(Selectors.getItemsForList);
  }

  itemsForList$ = this.select(Selectors.getItemsForList);

  get prices() {
    return this.get(Selectors.getPrices);
  }

  prices$ = this.select(Selectors.getPrices);

  get canAddResults() {
    return this.get(Selectors.getCanAddResults);
  }

  canAddResults$ = this.select(Selectors.getCanAddResults);

  get canContinue() {
    return this.get(Selectors.canContinue);
  }

  canContinue$ = this.select(Selectors.canContinue);

  get fetchingAvailabilities() {
    return this.get(Selectors.getFetchingAvailabilities);
  }

  fetchingAvailabilities$ = this.select(Selectors.getFetchingAvailabilities);

  get disabledPurchaseOptions() {
    return this.get((s) => s.disabledPurchaseOptions);
  }

  get vats$() {
    return this._service.getVats$();
  }

  #logger = logger(() => ({
    class: 'PurchaseOptionsStore',
    module: 'purchase-options',
    importMetaUrl: import.meta.url,
  }));

  constructor(
    private _service: PurchaseOptionsService,
    private _catalogService: DomainCatalogService,
  ) {
    super({
      shoppingCartId: undefined,
      defaultBranch: undefined,
      inStoreBranch: undefined,
      items: [],
      pickupBranch: undefined,
      purchaseOption: undefined,
      selectedItemIds: [],
      type: undefined,
      availabilities: [],
      prices: {},
      canAddResults: [],
      customerFeatures: {},
      fetchingAvailabilities: [],
      useRedemptionPoints: false,
      disabledPurchaseOptions: [],
    });
  }

  addFetchingAvailability = this.updater(
    (state, fetchState: FetchingAvailability) => {
      return {
        ...state,
        fetchingAvailabilities: [...state.fetchingAvailabilities, fetchState],
      };
    },
  );

  removeFetchingAvailability = this.updater(
    (state, fetchState: FetchingAvailability) => {
      return {
        ...state,
        fetchingAvailabilities: state.fetchingAvailabilities.filter(
          (f) => f.id !== fetchState.id,
        ),
      };
    },
  );

  async initialize({
    items,
    shoppingCartId,
    selectedBranch,
    selectedCustomer,
    type,
    inStoreBranch,
    pickupBranch,
    useRedemptionPoints: showRedemptionPoints,
    disabledPurchaseOptions,
  }: PurchaseOptionsModalContext) {
    const defaultBranch = await this._service.fetchDefaultBranch().toPromise();

    const customerFeatures =
      selectedCustomer?.features.reduce(
        (acc, feature) => {
          acc[feature.key] = feature.key;
          return acc;
        },
        {} as Record<string, string>,
      ) ?? {};

    this.patchState({
      type: type,
      shoppingCartId,
      useRedemptionPoints: showRedemptionPoints,
      items: items.map((item) => ({
        ...item,
        quantity: item['quantity'] ?? 1,
      })),
      defaultBranch: selectedBranch ?? defaultBranch,
      pickupBranch: pickupBranch ?? selectedBranch,
      inStoreBranch: inStoreBranch ?? selectedBranch,
      customerFeatures,
      disabledPurchaseOptions: disabledPurchaseOptions ?? [],
    });

    await this._loadAvailabilities();
    await this._loadCanAdd();
    this.selectSelectableItems();
  }

  // #region Private funtions for loading and setting Branches and Availabilities

  /**
   * Checks if a purchase option is disabled.
   * Disabled options will not have availability API calls made.
   *
   * @param option - The purchase option to check
   * @returns true if the option is in the disabledPurchaseOptions array
   *
   * @private
   */
  private isOptionDisabled(option: PurchaseOption): boolean {
    return this.disabledPurchaseOptions.includes(option);
  }

  private async _loadAvailabilities() {
    const items = this.items;

    const promises: Promise<unknown>[] = [];

    this._loadCatalogueAvailability();

    items.forEach((item) => {
      const itemData = mapToItemData(item, this.type);
      if (isDownload(item)) {
        if (!this.isOptionDisabled('download')) {
          promises.push(this._loadDownloadAvailability(itemData));
        }
      } else {
        if (!isGiftCard(item, this.type)) {
          if (!this.isOptionDisabled('pickup')) {
            promises.push(this._loadPickupAvailability(itemData));
          }
          if (!this.isOptionDisabled('in-store')) {
            promises.push(this._loadInStoreAvailability(itemData));
          }
          if (!this.isOptionDisabled('delivery')) {
            promises.push(this._loadDeliveryAvailability(itemData));
          }
          if (!this.isOptionDisabled('dig-delivery')) {
            promises.push(this._loadDigDeliveryAvailability(itemData));
          }
        }
        if (!this.isOptionDisabled('b2b-delivery')) {
          promises.push(this._loadB2bDeliveryAvailability(itemData));
        }
      }
    });

    await Promise.all(promises);
  }

  private async loadItemAvailability(itemId: number) {
    const item = this.items.find((item) => item.id === itemId);

    const promises: Promise<unknown>[] = [];

    const purchaseOption = this.purchaseOption;

    if (!item) return Promise.resolve();

    const itemData = mapToItemData(item, this.type);

    if (
      (purchaseOption === 'in-store' || purchaseOption === undefined) &&
      !this.isOptionDisabled('in-store')
    ) {
      promises.push(this._loadInStoreAvailability(itemData));
    }

    if (
      (purchaseOption === 'pickup' || purchaseOption === undefined) &&
      !this.isOptionDisabled('pickup')
    ) {
      promises.push(this._loadPickupAvailability(itemData));
    }

    if (purchaseOption === 'delivery' || purchaseOption === undefined) {
      if (!this.isOptionDisabled('delivery')) {
        promises.push(this._loadDeliveryAvailability(itemData));
      }
      if (!this.isOptionDisabled('dig-delivery')) {
        promises.push(this._loadDigDeliveryAvailability(itemData));
      }
      if (!this.isOptionDisabled('b2b-delivery')) {
        promises.push(this._loadB2bDeliveryAvailability(itemData));
      }
    }

    await Promise.all(promises);
  }

  private async _loadCatalogueAvailability() {
    const items = this.items;
    if (this.type === 'update') {
      const items = await this._catalogService
        .searchByEans({ eans: this.items.map((item) => item.product.ean) })
        .toPromise();
      if (items.result.length > 0)
        await this._addCatalogueAvailabilities(items.result);
    } else {
      await this._addCatalogueAvailabilities(items as ItemDTO[]);
    }
  }

  // #4813 - Function updated - "this.updater" didn't work, so "this.patchState" gets used instead
  // Added firstDayOfSale for catalogAvailability via ShoppingCart Route
  private async _addCatalogueAvailabilities(items: ItemDTO[]) {
    const currentAvailabilities = await this.availabilities$
      .pipe(first())
      .toPromise();
    const availabilities = [...currentAvailabilities];
    items.forEach((item) => {
      const catalogAvailability = item.catalogAvailability;
      availabilities.push({
        purchaseOption: 'catalog',
        itemId: item.id,
        ean: item.product.ean, // Hier brauchen wir zusätzlich die EAN - siehe purchase-options-list-item.component.ts: "get isEVT()"
        data: {
          price: catalogAvailability?.price,
          firstDayOfSale: catalogAvailability?.firstDayOfSale,
        },
      });
    });

    this.patchState({
      availabilities,
    });
  }

  private async _loadPickupAvailability(itemData: ItemData) {
    const branch = this.pickupBranch;
    if (!branch) return Promise.resolve();

    const id = uniqueId('availability_');
    try {
      this.addFetchingAvailability({
        id,
        itemId: itemData.sourceId,
        purchaseOption: 'pickup',
      });
      const res = await this._service
        .fetchPickupAvailability(itemData, itemData.quantity, branch)
        .toPromise();

      const availability: Availability = {
        itemId: itemData.sourceId,
        purchaseOption: 'pickup',
        data: res,
      };

      this._checkAndSetAvailability(availability);
    } catch (err) {
      this.#logger.error('Failed to load pickup availability', err, () => ({
        itemId: itemData.sourceId,
        quantity: itemData.quantity,
        branchId: branch?.id,
      }));
    }

    this.removeFetchingAvailability({
      id,
      itemId: itemData.sourceId,
      purchaseOption: 'pickup',
    });
  }

  private async _loadInStoreAvailability(itemData: ItemData) {
    const branch = this.inStoreBranch;
    if (!branch) return Promise.resolve();

    const id = uniqueId('availability_');
    try {
      this.addFetchingAvailability({
        id,
        itemId: itemData.sourceId,
        purchaseOption: 'in-store',
      });
      const res = await this._service
        .fetchInStoreAvailability(itemData, itemData.quantity, branch)
        .toPromise();

      const availability: Availability = {
        itemId: itemData.sourceId,
        purchaseOption: 'in-store',
        data: res,
      };

      this._checkAndSetAvailability(availability);
    } catch (err) {
      this.#logger.error('Failed to load in-store availability', err, () => ({
        itemId: itemData.sourceId,
        quantity: itemData.quantity,
        branchId: branch?.id,
      }));
    }

    this.removeFetchingAvailability({
      id,
      itemId: itemData.sourceId,
      purchaseOption: 'in-store',
    });
  }

  private async _loadDeliveryAvailability(itemData: ItemData) {
    const id = uniqueId('availability_');
    try {
      this.addFetchingAvailability({
        id,
        itemId: itemData.sourceId,
        purchaseOption: 'delivery',
      });
      const res = await this._service
        .fetchDeliveryAvailability(itemData, itemData.quantity)
        .toPromise();
      const availability: Availability = {
        itemId: itemData.sourceId,
        purchaseOption: 'delivery',
        data: res,
      };

      this._checkAndSetAvailability(availability);
    } catch (error) {
      this.#logger.error('Failed to load delivery availability', error, () => ({
        itemId: itemData.sourceId,
        quantity: itemData.quantity,
      }));
    }

    this.removeFetchingAvailability({
      id,
      itemId: itemData.sourceId,
      purchaseOption: 'delivery',
    });
  }

  private async _loadDigDeliveryAvailability(itemData: ItemData) {
    const id = uniqueId('availability_');
    try {
      this.addFetchingAvailability({
        id,
        itemId: itemData.sourceId,
        purchaseOption: 'dig-delivery',
      });
      const res = await this._service
        .fetchDigDeliveryAvailability(itemData, itemData.quantity)
        .toPromise();

      const availability: Availability = {
        itemId: itemData.sourceId,
        purchaseOption: 'dig-delivery',
        data: res,
      };

      this._checkAndSetAvailability(availability);
    } catch (error) {
      this.#logger.error(
        'Failed to load digital delivery availability',
        error,
        () => ({
          itemId: itemData.sourceId,
          quantity: itemData.quantity,
        }),
      );
    }

    this.removeFetchingAvailability({
      id,
      itemId: itemData.sourceId,
      purchaseOption: 'dig-delivery',
    });
  }

  private async _loadB2bDeliveryAvailability(itemData: ItemData) {
    const id = uniqueId('availability_');
    try {
      this.addFetchingAvailability({
        id,
        itemId: itemData.sourceId,
        purchaseOption: 'b2b-delivery',
      });
      const res = await this._service
        .fetchB2bDeliveryAvailability(itemData, itemData.quantity)
        .toPromise();

      const availability: Availability = {
        itemId: itemData.sourceId,
        purchaseOption: 'b2b-delivery',
        data: res,
      };

      this._checkAndSetAvailability(availability);
    } catch (error) {
      this.#logger.error(
        'Failed to load B2B delivery availability',
        error,
        () => ({
          itemId: itemData.sourceId,
          quantity: itemData.quantity,
        }),
      );
    }

    this.removeFetchingAvailability({
      id,
      itemId: itemData.sourceId,
      purchaseOption: 'b2b-delivery',
    });
  }

  private async _loadDownloadAvailability(itemData: ItemData) {
    const id = uniqueId('availability_');
    try {
      this.addFetchingAvailability({
        id,
        itemId: itemData.sourceId,
        purchaseOption: 'download',
      });
      const res = await this._service
        .fetchDownloadAvailability(itemData)
        .toPromise();

      const availability: Availability = {
        itemId: itemData.sourceId,
        purchaseOption: 'download',
        data: res,
      };

      this._checkAndSetAvailability(availability);
    } catch (error) {
      this.#logger.error('Failed to load download availability', error, () => ({
        itemId: itemData.sourceId,
      }));
    }

    this.removeFetchingAvailability({
      id,
      itemId: itemData.sourceId,
      purchaseOption: 'download',
    });
  }

  private _checkAndSetAvailability(availability: Availability) {
    if (this._service.isAvailable(availability.data)) {
      const availabilities = this.availabilities;

      const index = availabilities.findIndex(
        (a) =>
          a.itemId === availability.itemId &&
          a.purchaseOption === availability.purchaseOption,
      );

      if (index > -1) {
        availabilities[index] = availability;
      } else {
        availabilities.push(availability);
      }

      this.patchState({ availabilities });
    } else {
      const availabilities = this.availabilities.filter(
        (a) =>
          !(
            a.itemId === availability.itemId &&
            a.purchaseOption === availability.purchaseOption
          ),
      );
      this.patchState({ availabilities });
    }
  }

  private _addCanAddResult(canAdd: CanAdd) {
    let canAddResults = this.canAddResults;
    canAddResults = canAddResults.filter(
      (c) =>
        !(
          c.itemId === canAdd.itemId &&
          c.purchaseOption === canAdd.purchaseOption
        ),
    );
    canAddResults.push(canAdd);
    this.patchState({ canAddResults });
  }

  private async _loadCanAdd() {
    const payloads: Record<string, ItemPayloadWithSourceId[]> = {
      Abholung: [],
      Rücklage: [],
      Versand: [],
      Download: [],
    };

    this.patchState({ canAddResults: [] });

    this.items.forEach((item) => {
      // Get Rücklage availability
      const inStoreAvailability = this.availabilities.find(
        (a) => a.itemId === item.id && a.purchaseOption === 'in-store',
      );
      if (inStoreAvailability) {
        payloads['Rücklage'].push(
          mapToItemPayload({
            item,
            availability: inStoreAvailability.data,
            quantity: item.quantity ?? 1,
            type: this.type,
          }),
        );
      }

      // Get Versand availability
      let deliveryAvailability = this.availabilities.find(
        (a) => a.itemId === item.id && a.purchaseOption === 'delivery',
      );
      const digDeliveryAvailability = this.availabilities.find(
        (a) => a.itemId === item.id && a.purchaseOption === 'dig-delivery',
      );
      const b2bDeliveryAvailability = this.availabilities.find(
        (a) => a.itemId === item.id && a.purchaseOption === 'b2b-delivery',
      );
      deliveryAvailability =
        deliveryAvailability ||
        digDeliveryAvailability ||
        b2bDeliveryAvailability;
      if (deliveryAvailability) {
        payloads['Versand'].push(
          mapToItemPayload({
            item,
            availability: deliveryAvailability.data,
            quantity: item.quantity ?? 1,
            type: this.type,
          }),
        );
      }

      // Get Abholung availability
      const pickupAvailability = this.availabilities.find(
        (a) => a.itemId === item.id && a.purchaseOption === 'pickup',
      );
      if (pickupAvailability) {
        payloads['Abholung'].push(
          mapToItemPayload({
            item,
            availability: pickupAvailability.data,
            quantity: item.quantity ?? 1,
            type: this.type,
          }),
        );
      }

      // Get Download availability
      const downloadAvailability = this.availabilities.find(
        (a) => a.itemId === item.id && a.purchaseOption === 'download',
      );
      if (downloadAvailability) {
        payloads['Download'].push(
          mapToItemPayload({
            item,
            availability: downloadAvailability.data,
            quantity: item.quantity ?? 1,
            type: this.type,
          }),
        );
      }
    });

    for (const key in payloads) {
      const itemPayloads = payloads[key];
      if (itemPayloads.length > 0) {
        try {
          const res = await this._service.fetchCanAdd(
            this.shoppingCartId,
            key as OrderTypeFeature,
            itemPayloads,
            this.customerFeatures,
          );
          res.forEach((canAdd) => {
            const item = itemPayloads.find((i) => i.id === canAdd.id);
            this._addCanAddResult({
              canAdd: canAdd.status === 0,
              itemId: item.sourceId,
              purchaseOption: getPurchaseOptionForOrderType(
                key as OrderTypeFeature,
              ),
              message: canAdd.message,
            });
          });
        } catch (error) {
          this.#logger.error('Failed to load canAdd results', error, () => ({
            orderType: key,
            shoppingCartId: this.shoppingCartId,
            itemCount: itemPayloads.length,
          }));
        }
      }
    }
  }

  // #endregion

  setPurchaseOption(purchaseOption: PurchaseOption) {
    if (purchaseOption !== this.purchaseOption) {
      this.patchState({ purchaseOption });
    }
  }

  setSelectedItem(itemId: number, value: boolean) {
    const selectedItemIds = this.selectedItemIds;
    if (value && !selectedItemIds.includes(itemId)) {
      this.patchState({ selectedItemIds: [...selectedItemIds, itemId] });
    } else if (!value && selectedItemIds.includes(itemId)) {
      this.patchState({
        selectedItemIds: selectedItemIds.filter((id) => id !== itemId),
      });
    }
  }

  selectSelectableItems() {
    const items = this.items.filter((i) => this.canAddItem(i.id));

    this.patchState({ selectedItemIds: items.map((item) => item.id) });
  }

  canAddItem(itemId: number): boolean {
    const purchaseOption = this.purchaseOption;
    const canAdd = this.canAddResults.find(
      (c) => c.itemId === itemId && c.purchaseOption === purchaseOption,
    );

    if (canAdd) {
      return canAdd.canAdd;
    }

    return false;
  }

  resetSelectedItems() {
    this.patchState({ selectedItemIds: [] });
  }

  getItemsThatHaveAnAvailabilityForPurchaseOption(
    purchaseOption: PurchaseOption,
  ): Item[] {
    return this.get(
      Selectors.getItemsThatHaveAnAvailabilityForPurchaseOption(purchaseOption),
    );
  }

  getItemsThatHaveAnAvailabilityForPurchaseOption$(
    purchaseOption: PurchaseOption,
  ): Observable<Item[]> {
    return this.select(
      Selectors.getItemsThatHaveAnAvailabilityForPurchaseOption(purchaseOption),
    );
  }

  getItemsThatHaveAnAvailabilityAndCanAddForPurchaseOption(
    purchaseOption: PurchaseOption,
  ): Item[] {
    return this.get(
      Selectors.getItemsThatHaveAnAvailabilityAndCanAddForPurchaseOption(
        purchaseOption,
      ),
    );
  }

  getItemsThatHaveAnAvailabilityAndCanAddForPurchaseOption$(
    purchaseOption: PurchaseOption,
  ): Observable<Item[]> {
    return this.select(
      Selectors.getItemsThatHaveAnAvailabilityAndCanAddForPurchaseOption(
        purchaseOption,
      ),
    );
  }

  getAvailabilitiesForItem(itemId: number): Availability[] {
    return this.get(Selectors.getAvailabilitiesForItem(itemId));
  }

  getAvailabilitiesForItem$(itemId: number): Observable<Availability[]> {
    return this.select(Selectors.getAvailabilitiesForItem(itemId));
  }

  setInStoreBranch(branch: Branch | undefined) {
    if (this.inStoreBranch !== branch) {
      this.patchState({ inStoreBranch: branch });

      if (this.inStoreBranch) {
        const promises = this.items.map((item) => {
          const itemData = mapToItemData(item, this.type);
          return this._loadInStoreAvailability(itemData);
        });

        Promise.all(promises).then(() => {
          this._loadCanAdd();
        });
      }
    }
  }

  setPickupBranch(branch: Branch | undefined) {
    if (this.pickupBranch !== branch) {
      this.patchState({ pickupBranch: branch });

      if (this.pickupBranch) {
        const promises = this.items.map((item) => {
          const itemData = mapToItemData(item, this.type);
          return this._loadPickupAvailability(itemData);
        });

        Promise.all(promises).then(() => {
          this._loadCanAdd();
        });
      }
    }
  }

  async removeItem(itemId: number, removeFromShoppingCart: boolean) {
    const item = this.items.find((i) => i.id === itemId);

    if (removeFromShoppingCart && isShoppingCartItemDTO(item, this.type)) {
      this.removeItemFromShoppingCart(item);
    }

    const items = this.items.filter((i) => i.id !== itemId);
    this.setSelectedItem(itemId, false);
    this.patchState({ items });
  }

  async removeItemFromShoppingCart(item: Item) {
    try {
      await this._service.removeItemFromShoppingCart(
        this.shoppingCartId,
        item.id,
      );
    } catch (error) {
      this.#logger.error(
        'Failed to remove item from shopping cart',
        error,
        () => ({
          shoppingCartId: this.shoppingCartId,
          itemId: item.id,
        }),
      );
    }
  }

  setItemQuantity(itemId: number, quantity: number) {
    const items = this.items;
    if (quantity <= 0) {
      this.removeItem(itemId, true);
      return;
    }

    const index = items.findIndex((item) => item.id === itemId);
    if (index > -1 && items[index].quantity !== quantity) {
      items[index].quantity = quantity;
      this.patchState({ items });
    }

    this.loadItemAvailability(itemId).then(() => {
      this._loadCanAdd();
    });
  }

  getIsGiftCard(itemId: number) {
    return this.get(Selectors.getIsGiftCard(itemId));
  }

  getPrice(itemId: number) {
    return this.getPriceForPurchaseOption(itemId, this.purchaseOption);
  }

  getPrice$(itemId: number) {
    return this.purchaseOption$.pipe(
      switchMap((po) => this.getPriceForPurchaseOption$(itemId, po)),
    );
  }

  getPriceForPurchaseOption(itemId: number, purchaseOption: PurchaseOption) {
    return this.get(
      Selectors.getPriceForPurchaseOption(itemId, purchaseOption),
    );
  }

  getPriceForPurchaseOption$(itemId: number, purchaseOption: PurchaseOption) {
    return this.select(
      Selectors.getPriceForPurchaseOption(itemId, purchaseOption),
    );
  }

  getCanEditPrice(itemId: number) {
    return this.get(Selectors.getCanEditPrice(itemId));
  }

  getCanEditPrice$(itemId: number) {
    return this.select(Selectors.getCanEditPrice(itemId));
  }

  getCanEditVat(itemId: number) {
    return this.get(Selectors.getCanEditVat(itemId));
  }

  getCanEditVat$(itemId: number) {
    return this.select(Selectors.getCanEditVat(itemId));
  }

  getIsGiftCard$(itemId: number) {
    return this.select(Selectors.getIsGiftCard(itemId));
  }

  setPrice(itemId: number, value: number, manually = false) {
    const prices = this.prices;
    let price = prices[itemId];

    if (price?.value?.value !== value) {
      if (!price) {
        price = {
          ...DEFAULT_PRICE_DTO,
          value: { ...DEFAULT_PRICE_VALUE, value },
          vat: manually ? price?.vat : { ...DEFAULT_VAT_VALUE, ...price?.vat },
        };
      } else {
        price = { ...price, value: { ...price.value, value }, vat: price?.vat };
      }

      this.patchState({ prices: { ...prices, [itemId]: price } });
    }
  }

  setVat(itemId: number, vat: VATDTO) {
    const prices = this.prices;
    let price = prices[itemId];
    if (price?.vat?.vatType !== vat?.vatType) {
      if (!price) {
        price = {
          ...DEFAULT_PRICE_DTO,
          value: { ...DEFAULT_PRICE_VALUE, ...price?.value },
          vat: { ...DEFAULT_VAT_VALUE, ...vat },
        };
      } else {
        price = { ...price, value: price.value, vat: { ...price.vat, ...vat } };
      }

      this.patchState({ prices: { ...prices, [itemId]: price } });
    }
  }

  getCanAddResultForItemAndCurrentPurchaseOption(itemId: number) {
    return this.get(
      Selectors.getCanAddResultForItemAndCurrentPurchaseOption(itemId),
    );
  }

  getCanAddResultForItemAndCurrentPurchaseOption$(itemId: number) {
    return this.select(
      Selectors.getCanAddResultForItemAndCurrentPurchaseOption(itemId),
    );
  }

  getAvailabilityWithPurchaseOption(
    itemId: number,
    purchaseOption: PurchaseOption,
  ) {
    return this.get(
      Selectors.getAvailabilityWithPurchaseOption(itemId, purchaseOption),
    );
  }

  getAvailabilityWithPurchaseOption$(
    itemId: number,
    purchaseOption: PurchaseOption,
  ) {
    return this.select(
      Selectors.getAvailabilityWithPurchaseOption(itemId, purchaseOption),
    );
  }

  getCanAddResultWithPurchaseOption(
    itemId: number,
    purchaseOption: PurchaseOption,
  ) {
    return this.get(
      Selectors.getCanAddResultWithPurchaseOption(itemId, purchaseOption),
    );
  }

  getCanAddResultWithPurchaseOption$(
    itemId: number,
    purchaseOption: PurchaseOption,
  ) {
    return this.select(
      Selectors.getCanAddResultWithPurchaseOption(itemId, purchaseOption),
    );
  }

  addItemsToShoppingCart() {
    if (!this.canContinue || this.type !== 'add') {
      return;
    }
  }

  getAddToShoppingCartDTOForItem(
    itemId: number,
    purchaseOption: PurchaseOption,
  ): AddToShoppingCartDTO {
    const item = this.items.find((i) => i.id === itemId);

    if (!isItemDTO(item, this.type)) {
      throw new Error('Invalid item');
    }

    let price = this.getPriceForPurchaseOption(itemId, this.purchaseOption);
    const availability = this.getAvailabilityWithPurchaseOption(
      itemId,
      purchaseOption,
    );

    let promotion: Promotion | undefined = { value: item.promoPoints };
    let loyalty: Loyalty | undefined = undefined;
    const redemptionPoints: number | null = item.redemptionPoints || null;

    // "Lesepunkte einlösen" logic
    // If "Lesepunkte einlösen" is checked and item has redemption points, set price to 0 and remove promotion
    if (this.useRedemptionPoints) {
      // If loyalty is set, we need to remove promotion
      promotion = undefined;
      // Set loyalty points from item
      loyalty = { value: redemptionPoints };
      // Set price to 0
      price = ensureCurrencyDefaults({
        ...price,
        value: {
          ...price.value,
          value: 0,
          currency: 'EUR',
          currencySymbol: '€',
        },
      });
    }

    let destination: EntityDTOContainerOfDestinationDTO;
    if (purchaseOption === 'delivery') {
      destination = this._service.getDeliveryDestination(availability.data);
    } else if (purchaseOption === 'pickup') {
      destination = this._service.getPickupDestination(this.pickupBranch);
    } else if (purchaseOption === 'in-store') {
      destination = this._service.getInStoreDestination(this.inStoreBranch);
    } else if (purchaseOption === 'download') {
      destination = this._service.getDownloadDestination(availability.data);
    }

    return {
      quantity: item.quantity,
      availability: { ...availability.data, price },
      destination,
      itemType: item.type,
      product: {
        ...item.product,
        catalogProductNumber:
          item?.product?.catalogProductNumber ?? String(item.id),
      },
      promotion,
      loyalty,
    };
  }

  getUpdateShoppingCartItemDTOForItem(
    itemId: number,
    purchaseOption: PurchaseOption,
  ): UpdateShoppingCartItemDTO {
    const item = this.items.find((i) => i.id === itemId);

    if (!isShoppingCartItemDTO(item, this.type)) {
      throw new Error('Invalid item');
    }
    let price = this.getPriceForPurchaseOption(itemId, this.purchaseOption);
    const availability = this.getAvailabilityWithPurchaseOption(
      itemId,
      purchaseOption,
    );

    // If loyalty points is set we know it is a redemption item
    // we need to make sure we don't update the price
    if (this.useRedemptionPoints) {
      price = ensureCurrencyDefaults({
        ...price,
        value: {
          ...price.value,
          value: 0,
          currency: 'EUR',
          currencySymbol: '€',
        },
      });
    }

    let destination: EntityDTOContainerOfDestinationDTO;
    if (purchaseOption === 'delivery') {
      destination = this._service.getDeliveryDestination(availability.data);
    } else if (purchaseOption === 'pickup') {
      destination = this._service.getPickupDestination(this.pickupBranch);
    } else if (purchaseOption === 'in-store') {
      destination = this._service.getInStoreDestination(this.inStoreBranch);
    } else if (purchaseOption === 'download') {
      destination = this._service.getDownloadDestination(availability.data);
    }

    return {
      quantity: item.quantity,
      availability: { ...availability.data, price },
      destination,
    };
  }

  async save() {
    if (!this.canContinue) {
      return;
    }
    const type = this.type;
    const purchaseOption = this.purchaseOption;

    if (type === 'add') {
      const payloads = this.selectedItemIds.map((itemId) =>
        this.getAddToShoppingCartDTOForItem(itemId, purchaseOption),
      );
      await this._service.addItemToShoppingCart(this.shoppingCartId, payloads);
    } else if (type === 'update') {
      this.selectedItemIds.map((itemId) =>
        this.getUpdateShoppingCartItemDTOForItem(itemId, purchaseOption),
      );

      for (const itemId of this.selectedItemIds) {
        const item = this.items.find((i) => i.id === itemId);
        const payload = this.getUpdateShoppingCartItemDTOForItem(
          itemId,
          purchaseOption,
        );
        await this._service.updateItemInShoppingCart(
          this.shoppingCartId,
          item.id,
          payload,
        );
      }
    } else {
      throw new Error('Invalid type');
    }

    this.selectedItemIds.forEach((itemId) => {
      this.removeItem(itemId, false);
      this.resetSelectedItems();
    });
  }

  getFetchingAvailabilitiesForItem$(itemId: number) {
    return this.select(Selectors.getFetchingAvailabilitiesForItem(itemId));
  }
}
