import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import {
  AddToShoppingCartDTO,
  AvailabilityDTO,
  BranchDTO,
  BuyerDTO,
  BuyerResult,
  CheckoutDTO,
  CommunicationDetailsDTO,
  DestinationDTO,
  NotificationChannel,
  OLAAvailabilityDTO,
  PayerDTO,
  PaymentDTO,
  PaymentType,
  ResponseArgsOfDestinationDTO,
  ShippingAddressDTO,
  ShoppingCartDTO,
  StoreCheckoutService,
  UpdateShoppingCartItemDTO,
  InputDTO,
  ItemPayload,
} from '@swagger/checkout';
import { DisplayOrderDTO, OrderCheckoutService, ReorderValues } from '@swagger/oms';
import { isNullOrUndefined, memorize } from '@utils/common';
import { combineLatest, Observable, of, concat, isObservable, throwError } from 'rxjs';
import { bufferCount, catchError, filter, first, map, mergeMap, shareReplay, switchMap, tap, withLatestFrom } from 'rxjs/operators';

import * as DomainCheckoutSelectors from './store/domain-checkout.selectors';
import * as DomainCheckoutActions from './store/domain-checkout.actions';
import { DomainAvailabilityService } from '@domain/availability';
import { HttpErrorResponse } from '@angular/common/http';
import { ApplicationService } from '@core/application';

@Injectable()
export class DomainCheckoutService {
  constructor(
    private store: Store<any>,
    private applicationService: ApplicationService,
    private storeCheckoutService: StoreCheckoutService,
    private orderCheckoutService: OrderCheckoutService,
    private availabilityService: DomainAvailabilityService
  ) {}

  //#region shoppingcart
  getShoppingCart({ processId, latest }: { processId: number; latest?: boolean }): Observable<ShoppingCartDTO> {
    let _latest = latest;
    return this.store.select(DomainCheckoutSelectors.selectShoppingCartByProcessId, { processId }).pipe(
      filter((cart) => {
        if (isNullOrUndefined(cart)) {
          this.createShoppingCart({ processId }).subscribe();
          return false;
        } else if (cart && _latest) {
          _latest = false;
          this.storeCheckoutService
            .StoreCheckoutGetShoppingCart({
              shoppingCartId: cart.id,
            })
            .pipe(
              map((response) => response.result),
              tap((shoppingCart) =>
                this.store.dispatch(
                  DomainCheckoutActions.setShoppingCart({
                    processId,
                    shoppingCart,
                  })
                )
              )
            )
            .subscribe();
          return false;
        }
        return true;
      })
    );
  }

  createShoppingCart({ processId }: { processId: number }): Observable<ShoppingCartDTO> {
    return this.storeCheckoutService.StoreCheckoutCreateShoppingCart().pipe(
      map((response) => response.result),
      tap((shoppingCart) =>
        this.store.dispatch(
          DomainCheckoutActions.setShoppingCart({
            processId,
            shoppingCart,
          })
        )
      )
    );
  }

  addItemToShoppingCart({ processId, items }: { processId: number; items: AddToShoppingCartDTO[] }): Observable<ShoppingCartDTO> {
    return this.getShoppingCart({ processId }).pipe(
      first(),
      mergeMap((cart) =>
        this.storeCheckoutService
          .StoreCheckoutAddItemToShoppingCart({
            items,
            shoppingCartId: cart.id,
          })
          .pipe(
            map((response) => response.result),
            tap((shoppingCart) =>
              this.store.dispatch(
                DomainCheckoutActions.setShoppingCart({
                  processId,
                  shoppingCart,
                })
              )
            ),
            tap((shoppingCart) => this.updateProcessCount(processId, shoppingCart?.items?.length))
          )
      )
    );
  }

  setDestinationForCustomer({
    processId,
    customerFeatures,
  }: {
    processId: number;
    customerFeatures: { [key: string]: string };
  }): Observable<BuyerResult> {
    return this.getShoppingCart({ processId }).pipe(
      first(),
      mergeMap((shoppingCart) =>
        this.storeCheckoutService
          .StoreCheckoutSetLogisticianOnDestinationsByBuyer({
            shoppingCartId: shoppingCart?.id,
            payload: { customerFeatures },
          })
          .pipe(map((response) => response.result))
      )
    );
  }

  canAddDestination({ processId, destinationDTO }: { processId: number; destinationDTO: DestinationDTO }) {
    return this.getShoppingCart({ processId }).pipe(
      first(),
      mergeMap((cart) =>
        this.storeCheckoutService.StoreCheckoutCanAddDestination({
          shoppingCartId: cart.id,
          payload: destinationDTO,
        })
      ),
      map((response) => {
        if (response.result?.ok) {
          return true;
        }
        return Object.values(response.invalidProperties).join('; ') || response.message;
      })
    );
  }

  canAddItem({
    processId,
    availability,
    orderType,
  }: {
    processId: number;
    availability: OLAAvailabilityDTO;
    orderType: string;
  }): Observable<true | string> {
    return this.getShoppingCart({ processId }).pipe(
      first(),
      withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
      mergeMap(([shoppingCart, customerFeatures]) =>
        this.storeCheckoutService
          .StoreCheckoutCanAddItem({
            shoppingCartId: shoppingCart?.id,
            payload: {
              customerFeatures,
              availabilities: [availability],
              orderType,
            },
          })
          .pipe(
            map((response) => {
              if (response.result.ok) {
                return true;
              }
              return response.message;
            })
          )
      )
    );
  }

  canAddItems({ processId, payload, orderType }: { processId: number; payload: ItemPayload[]; orderType: string }) {
    return this.getShoppingCart({ processId }).pipe(
      first(),
      withLatestFrom(this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId })),
      mergeMap(([shoppingCart, customerFeatures]) => {
        payload = payload?.map((p) => {
          return {
            ...p,
            customerFeatures,
            orderType,
          };
        });
        return this.storeCheckoutService
          .StoreCheckoutCanAddItems({
            shoppingCartId: shoppingCart.id,
            payload,
          })
          .pipe(
            map((response) => {
              return response.result;
            })
          );
      })
    );
  }

  updateShoppingCartItemAvailability({
    shoppingCartId,
    shoppingCartItemId,
    availability,
  }: {
    shoppingCartId: number;
    shoppingCartItemId: number;
    availability: AvailabilityDTO;
  }) {
    return this.storeCheckoutService.StoreCheckoutUpdateShoppingCartItemAvailability({
      shoppingCartId,
      shoppingCartItemId,
      availability,
    });
  }

  updateItemInShoppingCart({
    processId,
    shoppingCartItemId,
    update,
  }: {
    processId: number;
    shoppingCartItemId: number;
    update: UpdateShoppingCartItemDTO;
  }): Observable<ShoppingCartDTO> {
    return this.getShoppingCart({ processId }).pipe(
      first(),
      mergeMap((shoppingCart) =>
        this.storeCheckoutService
          .StoreCheckoutUpdateShoppingCartItem({
            shoppingCartId: shoppingCart.id,
            shoppingCartItemId,
            values: update,
          })
          .pipe(
            map((response) => response.result),
            tap((shoppingCart) => this.store.dispatch(DomainCheckoutActions.setShoppingCart({ processId, shoppingCart }))),
            tap((shoppingCart) => this.updateProcessCount(processId, shoppingCart?.items?.length))
          )
      )
    );
  }

  //#endregion

  //#region Checkout

  getCheckout({ processId, refresh }: { processId: number; refresh?: boolean }): Observable<CheckoutDTO> {
    let _refresh = refresh;
    return this.store.select(DomainCheckoutSelectors.selectCheckoutByProcessId, { processId }).pipe(
      filter((checkout) => {
        if (isNullOrUndefined(checkout) || _refresh) {
          _refresh = false;
          this.createCheckout({ processId }).subscribe();
          return false;
        }
        return true;
      })
    );
  }

  createCheckout({ processId }: { processId: number }) {
    return this.getShoppingCart({ processId }).pipe(
      first(),
      mergeMap((shoppingCart) =>
        this.storeCheckoutService
          .StoreCheckoutCreateOrRefreshCheckout({
            shoppingCartId: shoppingCart?.id,
          })
          .pipe(
            map((response) => response.result),
            tap((checkout) => this.store.dispatch(DomainCheckoutActions.setCheckout({ processId, checkout })))
          )
      )
    );
  }

  /* @internal */
  _setNotificationChannels({
    processId,
    notificationChannels,
  }: {
    processId: number;
    notificationChannels: NotificationChannel;
  }): Observable<CheckoutDTO> {
    return this.getCheckout({ processId }).pipe(
      first(),
      mergeMap((checkout) =>
        this.storeCheckoutService
          .StoreCheckoutSetNotificationChannels({
            checkoutId: checkout.id,
            notificationChannel: notificationChannels,
          })
          .pipe(
            map((response) => response.result),
            tap((checkout) => this.store.dispatch(DomainCheckoutActions.setCheckout({ processId, checkout })))
          )
      )
    );
  }

  getPayment({ processId }: { processId: number }): Observable<PaymentDTO> {
    return this.getCheckout({ processId }).pipe(
      first(),
      mergeMap((checkout) =>
        this.storeCheckoutService
          .StoreCheckoutGetCheckoutPayment({
            checkoutId: checkout.id,
          })
          .pipe(map((response) => response.result))
      )
    );
  }

  getOlaErrors({ processId }: { processId: number }): Observable<number[]> {
    return this.store.select(DomainCheckoutSelectors.selectOlaErrorsByProcessId, { processId });
  }

  setPayment({ processId, paymentType }: { processId: number; paymentType: PaymentType }): Observable<CheckoutDTO> {
    return this.getCheckout({ processId }).pipe(
      first(),
      mergeMap((checkout) =>
        this.storeCheckoutService
          .StoreCheckoutSetPaymentType({
            checkoutId: checkout?.id,
            paymentType,
          })
          .pipe(
            map((response) => response.result),
            tap((checkout) => this.store.dispatch(DomainCheckoutActions.setCheckout({ processId, checkout })))
          )
      )
    );
  }

  _setBuyer({ processId, buyer }: { processId: number; buyer: BuyerDTO }): Observable<CheckoutDTO> {
    return this.getCheckout({ processId }).pipe(
      first(),
      mergeMap((checkout) =>
        this.storeCheckoutService
          .StoreCheckoutSetBuyer({
            checkoutId: checkout?.id,
            buyerDTO: buyer,
          })
          .pipe(
            map((response) => response.result),
            tap((checkout) => this.store.dispatch(DomainCheckoutActions.setCheckout({ processId, checkout })))
          )
      )
    );
  }

  _setPayer({ processId, payer }: { processId: number; payer: PayerDTO }): Observable<CheckoutDTO> {
    return this.getCheckout({ processId }).pipe(
      first(),
      mergeMap((checkout) =>
        this.storeCheckoutService
          .StoreCheckoutSetPayer({
            checkoutId: checkout?.id,
            payerDTO: payer,
          })
          .pipe(
            map((response) => response.result),
            tap((checkout) => this.store.dispatch(DomainCheckoutActions.setCheckout({ processId, checkout })))
          )
      )
    );
  }

  setSpecialCommentOnItem({ processId, specialComment }: { processId: number; specialComment: string }) {
    const shoppingCart$ = this.getShoppingCart({ processId }).pipe(first());

    return shoppingCart$.pipe(
      mergeMap((cart) =>
        concat(
          ...cart.items.map((item) =>
            this.storeCheckoutService.StoreCheckoutUpdateShoppingCartItem({
              shoppingCartId: cart.id,
              shoppingCartItemId: item.id,
              values: { specialComment },
            })
          )
        ).pipe(bufferCount(cart.items?.length))
      )
    );
  }

  checkAvailabilities({ processId }: { processId: number }): Observable<any> {
    const shoppingCart$ = this.getShoppingCart({ processId }).pipe(first());
    const itemsToCheck$ = shoppingCart$.pipe(
      map((cart) => cart?.items?.filter((item) => item?.data?.features?.orderType === 'Download' && !item.data.availability.lastRequest))
    );

    return itemsToCheck$.pipe(
      withLatestFrom(shoppingCart$),
      switchMap(async ([items, cart]) => {
        const errorIds = [];

        for (const item of items) {
          const availability = await this.availabilityService
            .getDownloadAvailability({
              item: {
                ean: item.data.product.ean,
                itemId: Number(item.data.product.catalogProductNumber),
                price: item.data.availability.price,
              },
            })
            .toPromise();

          if (!availability || !this.availabilityService.isAvailable({ availability })) {
            errorIds.push(item.id);
          } else {
            await this.updateShoppingCartItemAvailability({
              shoppingCartId: cart.id,
              shoppingCartItemId: item.id,
              availability: {
                ...availability,
                lastRequest: new Date().toISOString(),
              },
            }).toPromise();
          }
        }

        this.setOlaErrors({ processId, errorIds });

        if (errorIds.length > 0) {
          throw throwError(new Error(`Artikel nicht verfügbar`));
        } else {
          return of(undefined);
        }
      })
    );
  }

  updateAvailabilities({ processId }: { processId: number }): Observable<any> {
    const shoppingCart$ = this.getShoppingCart({ processId }).pipe(first());
    const itemsToUpdate$ = shoppingCart$.pipe(
      map((cart) =>
        cart?.items?.filter(
          (item) => item?.data?.features?.orderType === 'DIG-Versand' || item?.data?.features?.orderType === 'B2B-Versand'
        )
      )
    );

    return itemsToUpdate$.pipe(
      mergeMap((itemsToUpdate) => {
        if (!(itemsToUpdate?.length > 0)) {
          return of(undefined);
        }

        return concat(
          ...itemsToUpdate.map((itemToUpdate) => {
            const orderType = itemToUpdate?.data?.features?.orderType;
            let availability$: Observable<AvailabilityDTO>;
            if (orderType === 'DIG-Versand') {
              availability$ = this.availabilityService.getDigDeliveryAvailability({
                item: {
                  ean: itemToUpdate.data.product.ean,
                  itemId: Number(itemToUpdate.data.product.catalogProductNumber),
                  price: itemToUpdate.data.availability.price,
                },
                quantity: itemToUpdate.data.quantity,
              });
            } else if (orderType === 'B2B-Versand') {
              availability$ = this.availabilityService.getB2bDeliveryAvailability({
                item: {
                  ean: itemToUpdate.data.product.ean,
                  itemId: Number(itemToUpdate.data.product.catalogProductNumber),
                  price: itemToUpdate.data.availability.price,
                },
                quantity: itemToUpdate.data.quantity,
              });
            }

            if (!isObservable(availability$)) {
              return of(undefined);
            }

            return availability$.pipe(
              mergeMap((availability) => {
                let updatedAvailability = availability;
                // Bei Preisupdate der Versandoptionen soll immer der Preis genommen werden, der im Warenkorb steht
                if (orderType === 'DIG-Versand' || orderType === 'B2B-Versand') {
                  const itemPrice = itemToUpdate.data.availability.price;
                  updatedAvailability = {
                    ...availability,
                    price: itemPrice,
                  };
                }
                return this.updateItemInShoppingCart({
                  processId,
                  shoppingCartItemId: itemToUpdate.id,
                  update: {
                    availability: updatedAvailability,
                  },
                });
              })
            );
          })
        ).pipe(bufferCount(itemsToUpdate.length));
      })
    );
  }

  completeCheckout({ processId }: { processId: number }): Observable<DisplayOrderDTO[]> {
    const refreshShoppingCart$ = this.getShoppingCart({ processId, latest: true }).pipe(first());
    const refreshCheckout$ = this.getCheckout({ processId, refresh: true }).pipe(first());

    const itemOrderOptions$ = this.getShoppingCart({ processId }).pipe(
      first(),
      map((shoppingCart) => {
        const shoppingCartItems = shoppingCart?.items?.map((item) => item.data) || [];

        const hasTakeAway = shoppingCartItems.some((item) => item.features.orderType === 'Rücklage');
        const hasPickUp = shoppingCartItems.some((item) => item.features.orderType === 'Abholung');
        const hasDownload = shoppingCartItems.some((item) => item.features.orderType === 'Download');
        const hasDelivery = shoppingCartItems.some((item) => item.features.orderType === 'Versand');
        const hasDigDelivery = shoppingCartItems.some((item) => item.features.orderType === 'DIG-Versand');
        const hasB2BDelivery = shoppingCartItems.some((item) => item.features.orderType === 'B2B-Versand');

        return {
          hasTakeAway,
          hasPickUp,
          hasDownload,
          hasDelivery,
          hasDigDelivery,
          hasB2BDelivery,
        };
      }),
      shareReplay()
    );

    const customerTypes$ = this.getCustomerFeatures({ processId }).pipe(
      first(),
      map((features) => {
        const isOnline = !!features?.webshop;
        const isGuest = !!features?.guest;
        const isB2B = !!features?.b2b;
        const hasCustomerCard = !!features?.p4mUser;
        const isStaff = !!features?.staff;

        return { isOnline: isOnline, isGuest, isB2B, hasCustomerCard, isStaff };
      })
    );

    const setSpecialComment$ = this.getSpecialComment({ processId }).pipe(
      first(),
      mergeMap((specialComment) => {
        if (!!specialComment) {
          return this.setSpecialCommentOnItem({ processId, specialComment });
        }
        return of(specialComment);
      })
    );

    const shippingAddressDestination$ = this.getCheckout({ processId }).pipe(
      first(),
      map((checkout) => checkout?.destinations?.filter((dest) => dest.data.target === 2 || dest.data.target === 16)),
      shareReplay()
    );

    const setBuyer$ = this.getBuyer({ processId }).pipe(
      first(),
      mergeMap((buyer) => this._setBuyer({ processId, buyer }))
    );

    const setNotificationChannels$ = this.getNotificationChannels({ processId }).pipe(
      first(),
      mergeMap((notificationChannels) => this._setNotificationChannels({ processId, notificationChannels }))
    );

    const setPayer$ = combineLatest([itemOrderOptions$, customerTypes$]).pipe(
      first(),
      mergeMap(([itemOrderOptions, customerTypes]) => {
        if (
          customerTypes.isB2B ||
          itemOrderOptions.hasB2BDelivery ||
          itemOrderOptions.hasDelivery ||
          itemOrderOptions.hasDigDelivery ||
          itemOrderOptions.hasDownload
        ) {
          return this.getPayer({ processId }).pipe(first());
        }
        return of(undefined);
      }),
      mergeMap((payer) => (payer ? this._setPayer({ processId, payer }) : of(undefined)))
    );

    const updateDestination$ = itemOrderOptions$.pipe(
      withLatestFrom(this.getCustomerFeatures({ processId })),
      mergeMap(([{ hasDownload, hasDelivery, hasDigDelivery, hasB2BDelivery }, customerFeatures]) => {
        const needsUpdate = hasDownload || hasDelivery || hasDigDelivery || hasB2BDelivery;
        return needsUpdate ? this.setDestinationForCustomer({ processId, customerFeatures }) : of(undefined);
      })
    );

    const checkAvailabilities$ = this.checkAvailabilities({ processId });

    const updateAvailabilities$ = this.updateAvailabilities({ processId });

    const setPaymentType$ = itemOrderOptions$.pipe(
      mergeMap(({ hasDownload, hasDelivery, hasDigDelivery, hasB2BDelivery }) => {
        const paymentType = hasDownload || hasDelivery || hasDigDelivery || hasB2BDelivery ? 128 /* Rechnung */ : 4; /* Bar */
        return this.setPayment({ processId, paymentType });
      }),
      shareReplay()
    );

    const setDestination$ = combineLatest([
      this.getCheckout({ processId }).pipe(first()),
      shippingAddressDestination$,
      this.getShippingAddress({ processId }).pipe(first()),
    ]).pipe(
      mergeMap(([checkout, destinations, shippingAddress]) => {
        const obs: Observable<ResponseArgsOfDestinationDTO>[] = [];
        if (destinations?.length > 0) {
          destinations.forEach((destination) => {
            const updatedDestination: DestinationDTO = { ...destination.data, shippingAddress: { ...shippingAddress } };
            obs.push(
              this.storeCheckoutService.StoreCheckoutUpdateDestination({
                checkoutId: checkout.id,
                destinationId: destination.id,
                destinationDTO: updatedDestination,
              })
            );
          });
          return concat(...obs).pipe(bufferCount(destinations?.length));
        }
        return of(destinations);
      }),
      shareReplay()
    );

    const completeOrder$ = this.getCheckout({ processId }).pipe(
      first(),
      mergeMap((checkout) =>
        this.orderCheckoutService
          .OrderCheckoutCreateOrder({
            checkoutId: checkout.id,
          })
          .pipe(
            catchError((error) => {
              if (error instanceof HttpErrorResponse) {
                if (error.status === 409) {
                  this.store.dispatch(DomainCheckoutActions.setOrders({ orders: error.error.result }));
                }
                return throwError(error);
              }
            }),
            map((response) => {
              this.store.dispatch(DomainCheckoutActions.setOrders({ orders: response.result }));
              return response.result;
            })
          )
      )
    );

    return updateDestination$
      .pipe(tap(console.log.bind(window, 'updateDestination$')))
      .pipe(
        mergeMap((_) => refreshShoppingCart$.pipe(tap(console.log.bind(window, 'refreshShoppingCart$')))),
        mergeMap((_) => setSpecialComment$.pipe(tap(console.log.bind(window, 'setSpecialComment$')))),
        mergeMap((_) => refreshCheckout$.pipe(tap(console.log.bind(window, 'refreshCheckout$')))),
        mergeMap((_) => checkAvailabilities$.pipe(tap(console.log.bind(window, 'checkAvailabilities$')))),
        mergeMap((_) => updateAvailabilities$.pipe(tap(console.log.bind(window, 'updateAvailabilities$')))),
        mergeMap((_) => setBuyer$.pipe(tap(console.log.bind(window, 'setBuyer$')))),
        mergeMap((_) => setNotificationChannels$.pipe(tap(console.log.bind(window, 'setNotificationChannels$')))),
        mergeMap((_) => setPayer$.pipe(tap(console.log.bind(window, 'setPayer$')))),
        mergeMap((_) => setPaymentType$.pipe(tap(console.log.bind(window, 'setPaymentType$')))),
        mergeMap((_) => setDestination$.pipe(tap(console.log.bind(window, 'setDestination$'))))
      )
      .pipe(mergeMap((_) => completeOrder$.pipe(tap(console.log.bind(window, 'completeOrder$')))));
  }

  updateDestination({
    processId,
    destinationId,
    destination,
  }: {
    processId: number;
    destinationId: number;
    destination: DestinationDTO;
  }): Observable<DestinationDTO> {
    return this.getCheckout({ processId }).pipe(
      first(),
      mergeMap((checkout) =>
        this.storeCheckoutService
          .StoreCheckoutUpdateDestination({
            destinationId,
            checkoutId: checkout.id,
            destinationDTO: destination,
          })
          .pipe(
            map((response) => response.result),
            tap((destination) =>
              this.store.dispatch(
                DomainCheckoutActions.setCheckoutDestination({
                  processId,
                  destination,
                })
              )
            )
          )
      )
    );
  }

  //#endregion

  //#region Common

  canSetCustomer({
    processId,
    customerFeatures,
  }: {
    processId: number;
    customerFeatures?: { [key: string]: string };
  }): Observable<{ ok?: boolean; filter?: { [key: string]: string }; message?: string; create?: InputDTO }> {
    return this.getShoppingCart({ processId }).pipe(
      first(),
      mergeMap((shoppingCart) =>
        this.storeCheckoutService
          .StoreCheckoutCanAddBuyer({
            shoppingCartId: shoppingCart.id,
            payload: { customerFeatures },
          })
          .pipe(
            map((response) => ({
              ok: response.result.ok,
              filter: response.result.queryToken?.filter || {},
              message: response.message,
              create: response.result.create,
            }))
          )
      )
    );
  }

  setNotificationChannels({ processId, notificationChannels }: { processId: number; notificationChannels: NotificationChannel }): void {
    this.store.dispatch(DomainCheckoutActions.setNotificationChannels({ processId, notificationChannels }));
  }

  getNotificationChannels({ processId }: { processId: number }): Observable<NotificationChannel> {
    return this.store.select(DomainCheckoutSelectors.selectNotificationChannels, { processId });
  }

  setBuyerCommunicationDetails({ processId, mobile, email }: { processId: number; mobile?: string; email?: string }): void {
    this.store.dispatch(DomainCheckoutActions.setBuyerCommunicationDetails({ processId, mobile, email }));
  }

  getBuyerCommunicationDetails({ processId }: { processId: number }): Observable<CommunicationDetailsDTO> {
    return this.store.select(DomainCheckoutSelectors.selectBuyerCommunicationDetails, { processId });
  }

  getSetableCustomerTypes(processId: number): Observable<{ [key: string]: boolean }> {
    return this.canSetCustomer({ processId, customerFeatures: undefined }).pipe(
      map((res) => {
        let setableTypes: { [key: string]: boolean } = {
          store: true,
          guest: true,
          webshop: true,
          b2b: true,
        };

        if (Object.keys(res.filter).length === 0) {
          return setableTypes;
        }

        const customerTypes = res.filter?.customertype?.split(';') || [];
        const customerAttributes = res.filter?.customerattributes?.split(';') || [];

        const typesAndAttributes = [...customerTypes, ...customerAttributes];
        if (typesAndAttributes.includes('webshop') && !typesAndAttributes.includes('!guest')) {
          typesAndAttributes.push('guest');
        }

        for (const key in setableTypes) {
          if (Object.prototype.hasOwnProperty.call(setableTypes, key)) {
            if (typesAndAttributes.includes(key)) {
              setableTypes[key] = true;
            } else if (typesAndAttributes.includes(`!${key}`)) {
              setableTypes[key] = false;
            } else {
              setableTypes[key] = false;
            }
          }
        }

        return setableTypes;
      })
    );
  }

  @memorize()
  getBranches(): Observable<BranchDTO[]> {
    return this.storeCheckoutService
      .StoreCheckoutGetBranches({
        take: 999,
      })
      .pipe(
        map((r) => {
          return r.result.filter(
            (branch) => branch.status === 1 && branch.branchType === 1 && branch.isOnline === true && branch.isShippingEnabled === true
          );
        }),
        shareReplay()
      );
  }

  reorder(orderId: number, orderItemId: number, orderItemSubsetId: number, data: ReorderValues) {
    return this.orderCheckoutService
      .OrderCheckoutReorder({ orderId, orderItemId, orderItemSubsetId, data })
      .pipe(map((response) => response.result));
  }

  setCustomerFeatures({ processId, customerFeatures }: { processId: number; customerFeatures: { [key: string]: string } }) {
    this.store.dispatch(DomainCheckoutActions.setCustomerFeatures({ processId, customerFeatures }));
  }

  setOlaErrors({ processId, errorIds }: { processId: number; errorIds: number[] }) {
    this.store.dispatch(
      DomainCheckoutActions.setOlaError({
        processId,
        olaErrorIds: errorIds,
      })
    );
  }

  getCustomerFeatures({ processId }: { processId: number }): Observable<{ [key: string]: string }> {
    return this.store.select(DomainCheckoutSelectors.selectCustomerFeaturesByProcessId, { processId });
  }

  setShippingAddress({ processId, shippingAddress }: { processId: number; shippingAddress: ShippingAddressDTO }) {
    this.store.dispatch(DomainCheckoutActions.setShippingAddress({ processId, shippingAddress }));
  }

  getShippingAddress({ processId }: { processId: number }): Observable<ShippingAddressDTO> {
    return this.store.select(DomainCheckoutSelectors.selectShippingAddressByProcessId, { processId });
  }

  removeProcess({ processId }: { processId: number }) {
    this.store.dispatch(DomainCheckoutActions.removeProcess({ processId }));
  }

  setPayer({ processId, payer }: { processId: number; payer: PayerDTO }) {
    this.store.dispatch(DomainCheckoutActions.setPayer({ processId, payer }));
  }

  getPayer({ processId }: { processId: number }): Observable<PayerDTO> {
    return this.store.select(DomainCheckoutSelectors.selectPayerByProcessId, { processId });
  }

  setBuyer({ processId, buyer }: { processId: number; buyer: BuyerDTO }) {
    this.store.dispatch(DomainCheckoutActions.setBuyer({ processId, buyer }));
  }

  getBuyer({ processId }: { processId: number }): Observable<BuyerDTO> {
    return this.store.select(DomainCheckoutSelectors.selectBuyerByProcessId, { processId });
  }

  getOrders(): Observable<DisplayOrderDTO[]> {
    return this.store.select(DomainCheckoutSelectors.selectOrders);
  }

  removeAllOrders() {
    this.store.dispatch(DomainCheckoutActions.removeAllOrders());
  }

  setSpecialComment({ processId, agentComment }: { processId: number; agentComment: string }) {
    this.store.dispatch(DomainCheckoutActions.setSpecialComment({ processId, agentComment }));
  }

  getSpecialComment({ processId }: { processId: number }) {
    return this.store.select(DomainCheckoutSelectors.selectSpecialComment, { processId });
  }
  //#endregion

  //#region Common

  private updateProcessCount(processId: number, count: number) {
    this.applicationService.patchProcess(processId, { data: { count } });
  }

  //#endregion
}
