import {useTimeInSeconds} from "./date";
import {useCart, useCartItem} from "../public/cart";
import {useSortedDateStrings} from "./helper";
import {settings} from "../../../plugins/settings";
import {checkLimitPerEmployee, sortForEmployeeSelection} from "./employee";
import {useEntityTax, usePercentageAmount, useRoundAmount, useTaxAmount, useTaxedAmount} from "./pricing";

function useEmployeeService (store, serviceId, employeeId) {
  let service = store.getters['entities/getService'](serviceId)

  let employeeService = employeeId ? store.getters['entities/getEmployeeService'](employeeId, serviceId) : service

  return Object.assign(
    {},
    service,
    {
      price: employeeService.price,
      minCapacity: employeeService.minCapacity,
      maxCapacity: employeeService.maxCapacity,
      customPricing: employeeService.customPricing,
    }
  )
}

function useChangedBookingPrice (appointment, savedAppointment, booking, service) {
  let isChangedBookingService =
    savedAppointment &&
    savedAppointment.serviceId !== appointment.serviceId

  let savedBooking = savedAppointment && booking.id
    ? savedAppointment.bookings.find(i => i.id === booking.id)
    : null

  let isChangedBookingPersons =
    savedBooking &&
    savedBooking.persons !== booking.persons &&
    service.customPricing.enabled === 'person'

  let isChangedBookingDuration =
    savedBooking &&
    (booking.duration === null ? service.duration : booking.duration) !== savedBooking.duration &&
    service.customPricing.enabled === 'duration'

  return isChangedBookingService || isChangedBookingPersons || isChangedBookingDuration
}

function useAppointmentServicePrice (service, persons, duration, price = null) {
  if (service.customPricing.enabled === 'duration' &&
    (duration in service.customPricing.durations)
  ) {
    return service.customPricing.durations[duration].price
  } else if (service.customPricing.enabled === 'person') {
    let lastRange = Object.keys(service.customPricing.persons)[Object.keys(service.customPricing.persons).length - 1]

    for (let range in service.customPricing.persons) {
      if (persons >= service.customPricing.persons[range].from && (range !== lastRange ? persons <= parseInt(range) : true)) {
        return service.customPricing.persons[range].price
      }
    }
  } else if (price !== null && service.customPricing.enabled === 'period') {
    return price
  }

  return service.price
}

function useAppointmentServiceAmount (employeeService, persons, duration, price) {
  return useAppointmentServicePrice(employeeService, persons, duration, price) * (employeeService.aggregatedPrice ? persons : 1)
}

function useAppointmentAmountData (store, item, coupon, couponLimit) {
  let instantTotalAmount = 0

  let instantTotalServiceAmount = 0

  let instantTotalExtrasAmount = 0

  let instantDiscountAmount = 0

  let instantTaxAmount = 0

  let instantDepositAmount = 0

  let totalAmount = 0

  let totalServiceAmount = 0

  let totalExtrasAmount = 0

  let discountAmount = 0

  let taxAmount = 0

  let depositAmount = 0

  let useFullAmount = store.getters['booking/getPaymentDeposit']

  let service = store.getters['entities/getService'](
    item.serviceId
  )

  let applyCoupon = couponLimit > 0 && coupon.servicesIds.indexOf(service.id) !== -1

  let instantPaymentCount = 1

  if (service.recurringPayment) {
    instantPaymentCount = service.recurringPayment > item.services[item.serviceId].list.length
      ? item.services[item.serviceId].list.length : service.recurringPayment
  }

  let appliedCoupon = false

  let servicesPrices = {}

  let prepaidCount = 0

  let totalCount = 0

  item.services[item.serviceId].list.forEach((appointment, index) => {
    let employeeService = useEmployeeService(store, item.serviceId, appointment.providerId)

    let amountData = useAppointmentBookingAmountData(
      store,
      {
        price: useAppointmentServicePrice(employeeService, appointment.persons, appointment.duration, appointment.price),
        persons: appointment.persons,
        aggregatedPrice: service.aggregatedPrice,
        extras: appointment.extras,
        serviceId: item.serviceId,
        coupon: applyCoupon && couponLimit > 0 ? coupon : null
      },
      true
    )

    if (applyCoupon && couponLimit > 0) {
      appliedCoupon = true

      couponLimit--
    }

    let appointmentTotalAmount = amountData.total

    let appointmentDiscountAmount = amountData.discount

    let appointmentTaxAmount = amountData.tax

    totalExtrasAmount += amountData.total - amountData.bookable

    totalServiceAmount += amountData.bookable

    let appointmentDepositAmount = 0

    let servicePrice = useAppointmentServicePrice(employeeService, appointment.persons, appointment.duration, appointment.price)

    servicesPrices[servicePrice] = !(servicePrice in servicesPrices) ? 1 : servicesPrices[servicePrice] + 1

    if (service.depositPayment !== 'disabled' && (useFullAmount ? !service.fullPayment : true)) {
      switch (service.depositPayment) {
        case ('fixed'):
          appointmentDepositAmount = (service.depositPerPerson && service.aggregatedPrice ? appointment.persons : 1) * service.deposit

          break

        case 'percentage':
          appointmentDepositAmount = useRoundAmount(
            usePercentageAmount(
              appointmentTotalAmount - appointmentDiscountAmount + appointmentTaxAmount,
              service.deposit
            )
          )

          break
      }
    }

    totalAmount += appointmentTotalAmount

    discountAmount += appointmentDiscountAmount

    taxAmount += appointmentTaxAmount

    depositAmount += appointmentDepositAmount

    totalCount++

    if (index < instantPaymentCount) {
      instantTotalAmount = totalAmount

      instantTotalServiceAmount = totalServiceAmount

      instantTotalExtrasAmount = totalExtrasAmount

      instantDiscountAmount = discountAmount

      instantTaxAmount = taxAmount

      instantDepositAmount = depositAmount

      prepaidCount++
    }
  })

  return {
    serviceId: service.id,
    postpaid: {
      totalAmount: totalAmount - instantTotalAmount,
      totalServiceAmount: totalServiceAmount - instantTotalServiceAmount,
      totalExtrasAmount: totalExtrasAmount - instantTotalExtrasAmount,
      discountAmount: discountAmount - instantDiscountAmount,
      taxAmount: taxAmount - instantTaxAmount,
      depositAmount: 0,
      count: totalCount - prepaidCount,
    },
    prepaid: {
      totalAmount: instantTotalAmount,
      totalServiceAmount: instantTotalServiceAmount,
      totalExtrasAmount: instantTotalExtrasAmount,
      discountAmount: instantDiscountAmount,
      taxAmount: instantTaxAmount,
      depositAmount: instantDepositAmount,
      count: prepaidCount,
    },
    appliedCoupon: appliedCoupon,
    couponLimit: couponLimit,
    servicesPrices: servicesPrices,
  }
}

function useAppointmentBookingAmountData (store, appointment, includedTaxInTotal) {
  let serviceTax = null

  let excludedServiceTax = settings.payments.taxes.excluded

  let enabledServiceTax = settings.payments.taxes.enabled

  if ('tax' in appointment) {
    serviceTax = appointment.tax && appointment.tax.length ? appointment.tax[0] : null

    excludedServiceTax = serviceTax ? serviceTax.excluded : excludedServiceTax

    enabledServiceTax = serviceTax !== null
  } else if (enabledServiceTax) {
    serviceTax = useEntityTax(store, appointment.serviceId, 'service')
  }

  let serviceAmount = (appointment.aggregatedPrice ? appointment.persons : 1) * appointment.price

  let appointmentServiceAmount = 0

  let appointmentTotalAmount = 0

  let appointmentDiscountAmount = 0

  let appointmentTaxAmount = 0

  if (appointment.coupon) {
    appointmentTotalAmount = serviceAmount

    appointmentServiceAmount = serviceAmount

    if (enabledServiceTax && serviceTax && !excludedServiceTax) {
      serviceAmount = useTaxedAmount(serviceAmount, serviceTax)
    }

    let serviceDiscountAmount = appointment.coupon.discount
      ? usePercentageAmount(serviceAmount, appointment.coupon.discount)
      : 0

    let serviceDiscountedAmount = serviceAmount - serviceDiscountAmount

    serviceAmount = serviceDiscountedAmount

    let deduction = appointment.coupon.deduction

    let serviceDeductionAmount = 0

    if (serviceDiscountedAmount > 0 && deduction > 0) {
      serviceDeductionAmount = serviceDiscountedAmount >= deduction ? deduction : serviceDiscountedAmount

      serviceAmount = serviceDiscountedAmount - serviceDeductionAmount

      deduction = serviceDiscountedAmount >= deduction ? 0 : deduction - serviceDiscountedAmount
    }

    if (enabledServiceTax && serviceTax && excludedServiceTax) {
      appointmentTaxAmount = useTaxAmount(serviceTax, serviceAmount)
    } else if (enabledServiceTax && serviceTax && !excludedServiceTax) {
      serviceAmount = useTaxedAmount(
        (appointment.aggregatedPrice ? appointment.persons : 1) * appointment.price,
        serviceTax
      )

      let serviceTaxAmount = useTaxAmount(serviceTax, serviceAmount - serviceDiscountAmount - serviceDeductionAmount)

      if (includedTaxInTotal) {
        appointmentTotalAmount = serviceAmount + serviceTaxAmount

        appointmentServiceAmount = serviceAmount + serviceTaxAmount
      } else {
        appointmentTotalAmount = serviceAmount

        appointmentServiceAmount = serviceAmount

        appointmentTaxAmount = serviceTaxAmount
      }
    }

    appointmentDiscountAmount = serviceDiscountAmount + serviceDeductionAmount

    appointment.extras.forEach((selectedExtra) => {
      let extraTax = null

      let excludedExtraTax = settings.payments.taxes.excluded

      let enabledExtraTax = settings.payments.taxes.enabled

      if ('tax' in selectedExtra) {
        extraTax = selectedExtra.tax && selectedExtra.tax.length ? selectedExtra.tax[0] : null

        excludedExtraTax = extraTax ? extraTax.excluded : excludedExtraTax

        enabledExtraTax = extraTax !== null
      } else if (enabledExtraTax) {
        extraTax = useEntityTax(store, selectedExtra.extraId, 'extra')
      }

      let extraAggregatedPrice = selectedExtra.aggregatedPrice === null ? appointment.aggregatedPrice : selectedExtra.aggregatedPrice

      let extraAmount = useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons)

      let extraTotalAmount = extraAmount

      if (enabledExtraTax && extraTax && !excludedExtraTax) {
        extraAmount = useTaxedAmount(extraAmount, extraTax)
      }

      let extraDiscountAmount = appointment.coupon.discount
        ? usePercentageAmount(extraAmount, appointment.coupon.discount)
        : 0

      let extraDiscountedAmount = extraAmount - extraDiscountAmount

      extraAmount = extraDiscountedAmount

      let extraDeductionAmount = 0

      if (extraDiscountedAmount > 0 && deduction > 0) {
        extraDeductionAmount = extraDiscountedAmount >= deduction ? deduction : extraDiscountedAmount

        extraAmount = extraDiscountedAmount - extraDeductionAmount

        deduction = extraDiscountedAmount >= deduction ? 0 : deduction - extraDiscountedAmount
      }

      if (enabledExtraTax && extraTax && excludedExtraTax) {
        appointmentTaxAmount += useTaxAmount(extraTax, extraAmount)
      } else if (enabledExtraTax && extraTax && !excludedExtraTax) {
        extraAmount = useTaxedAmount(
          useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons),
          extraTax
        )

        let extraTaxAmount = useTaxAmount(extraTax, extraAmount - extraDiscountAmount - extraDeductionAmount)

        if (includedTaxInTotal) {
          extraTotalAmount = extraAmount + extraTaxAmount
        } else {
          extraTotalAmount = extraAmount

          appointmentTaxAmount += extraTaxAmount
        }
      } else if (enabledExtraTax && !extraTax && !excludedExtraTax) {
        extraTotalAmount = useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons)
      }

      appointmentTotalAmount += extraTotalAmount

      appointmentDiscountAmount += extraDiscountAmount + extraDeductionAmount
    })
  } else {
    if (enabledServiceTax && serviceTax && excludedServiceTax) {
      appointmentTaxAmount = useTaxAmount(serviceTax, serviceAmount)
    } else if (enabledServiceTax && serviceTax && !excludedServiceTax && !includedTaxInTotal) {
      serviceAmount = useTaxedAmount(
        (appointment.aggregatedPrice ? appointment.persons : 1) * appointment.price,
        serviceTax
      )

      appointmentTaxAmount = useTaxAmount(serviceTax, serviceAmount)
    }

    appointmentTotalAmount = serviceAmount

    appointmentServiceAmount = serviceAmount

    appointment.extras.forEach((selectedExtra) => {
      let extraAggregatedPrice = selectedExtra.aggregatedPrice === null ? appointment.aggregatedPrice : selectedExtra.aggregatedPrice

      let extraAmount = useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons)

      let extraTax = null

      let excludedExtraTax = settings.payments.taxes.excluded

      let enabledExtraTax = settings.payments.taxes.enabled

      if ('tax' in selectedExtra) {
        extraTax = selectedExtra.tax && selectedExtra.tax.length ? selectedExtra.tax[0] : null

        excludedExtraTax = extraTax ? extraTax.excluded : excludedExtraTax

        enabledExtraTax = extraTax !== null
      } else if (enabledExtraTax) {
        extraTax = useEntityTax(store, selectedExtra.extraId, 'extra')
      }

      if (enabledExtraTax && extraTax && excludedExtraTax) {
        appointmentTaxAmount += useTaxAmount(extraTax, extraAmount)
      } else if (enabledExtraTax && extraTax && !excludedExtraTax && !includedTaxInTotal) {
        extraAmount = useTaxedAmount(
          useExtraAmount(selectedExtra, extraAggregatedPrice, appointment.persons),
          extraTax
        )

        appointmentTaxAmount += useTaxAmount(extraTax, extraAmount)
      }

      appointmentTotalAmount += extraAmount
    })
  }

  return {
    total: appointmentTotalAmount,
    bookable: appointmentServiceAmount,
    discount: appointmentDiscountAmount,
    tax: appointmentTaxAmount,
    wcTax: appointment.wcTax,
  }
}

function useAppointmentsAmountInfo (store) {
  let amountsInfo = []

  let coupon = store.getters['booking/getCoupon']

  let couponLimit = coupon && coupon.limit ? coupon.limit : 0

  useCart(store).forEach((item) => {
    let amountData = useAppointmentAmountData(store, item, coupon, couponLimit)

    couponLimit = amountData.couponLimit

    delete (amountData.couponLimit)

    amountsInfo.push(amountData)
  })

  return amountsInfo
}

function useCapacity (employeeServices) {
  let options = {
    availability: false,
    min: 0,
    max: 0
  }

  let serviceMinCapacity = 0

  if (employeeServices.length && employeeServices.length > 1) {
    employeeServices.forEach(service => {
      serviceMinCapacity = service.minCapacity

      options.availability = service.bringingAnyone && service.maxCapacity > 1 && (service.maxExtraPeople === null || service.maxExtraPeople > 0)

      if (service.maxCapacity > options.max || options.max === 0) {
        options.max = service.maxExtraPeople !== null ? (service.maxExtraPeople + 1) : service.maxCapacity
      }

      if (options.min < service.minCapacity) {
        options.min = settings.appointments.allowBookingIfNotMin ? 1 : service.minCapacity
      }
    })

  } else if (employeeServices.length && employeeServices.length === 1) {
    let service = employeeServices[0]

    serviceMinCapacity = service.minCapacity

    options.availability = service.bringingAnyone && service.maxCapacity > 1 && (service.maxExtraPeople === null || service.maxExtraPeople > 0)
    options.min = settings.appointments.allowBookingIfNotMin ? 1 : service.minCapacity
    options.max = service.maxExtraPeople !== null && service.maxExtraPeople < service.maxCapacity ? (service.maxExtraPeople + 1) : service.maxCapacity
  }

  if (settings.appointments.openedBookingAfterMin) {
    options.min = serviceMinCapacity
  }

  let additionalPeople = settings.appointments.bringingAnyoneLogic === 'additional'

  options.max = options.max > 1 ? (options.max - (additionalPeople ? 1 : 0)) : options.max
  options.min = options.min > 0 ? (options.min - (additionalPeople ? 1 : 0)) : options.min

  return options
}

function usePaymentError (store, message) {
  store.commit('booking/setError', message)
}

function usePrepaidPrice (store) {
  let type = store.getters['bookableType/getType'] ? store.getters['bookableType/getType'] : store.getters['booking/getBookableType']

  let entity = null

  switch (type) {
    case 'appointment':
      let appointmentPrice = 0

      useCart(store).forEach((item) => {
        if ((item.serviceId && (item.serviceId in item.services)) || item.packageId) {
          entity = store.getters['entities/getService'](
            item.serviceId
          )

          appointmentPrice += useAppointmentsTotalAmount(
            store,
            entity,
            useAppointmentsPayments(
              store,
              item.serviceId,
              item.services[item.serviceId].list
            ).prepaid
          )
        }
      })

      return appointmentPrice
    case 'package':
      let price = 0

      if (useCart(store)[0].packageId) {
        entity = store.getters['entities/getPackage'](
          useCart(store)[0].packageId
        )

        price = entity.price
      }

      return price
    case 'event':
      entity = store.getters['eventEntities/getEvent'](store.getters['eventBooking/getSelectedEventId'])

      let eventPrice = 0

      if (entity.customPricing) {
        let tickets = store.getters['tickets/getTicketsData']

        tickets.forEach(t => {
          eventPrice += t.price * t.persons
        })

        return eventPrice
      }

      let persons = store.getters['persons/getPersons']

      return entity.price * persons
  }
}

function useDuration (serviceDuration, extras) {
  let duration = serviceDuration

  extras.forEach((extra) => {
    duration += (extra.duration * extra.quantity)
  })

  return duration
}

function useCalendarEvents (slots, waitingSlots) {
  let calendarSlotsValues = []

  let dates = Object.keys(slots)

  if (waitingSlots) {
    dates = [...new Set([...dates, ...Object.keys(waitingSlots)])]
  }

  useSortedDateStrings(dates).forEach((date) => {
    calendarSlotsValues.push({
      title  : 'e',
      start  : date,
      display: 'background',
      extendedProps: {
        slotsTotal: 100,
        slotsAvailable: 1,
        slots: date in slots ? slots[date] : {}
      }
    })
  })

  return calendarSlotsValues
}

function useServices (store) {
  let type = store.getters['booking/getBookableType']

  let services = null

  switch (type) {
    case ('appointment'): {
      services = store.getters['entities/getServices']

      break
    }

    case ('package'): {
      let packages = store.getters['entities/getPackages']

      let packagesServices = {}

      packages.forEach((pack) => {
        pack.bookable.forEach((book) => {
          packagesServices[book.service.id] = book.service
        })
      })

      services = Object.values(packagesServices)

      break
    }
  }

  return services
}

function useBusySlots (store) {
  let cartItem = useCartItem(store)

  let activeAppointment = cartItem.services[cartItem.serviceId].list[cartItem.index]

  let busySlots = store.getters['booking/getMultipleAppointmentsOccupied']

  return busySlots[activeAppointment.date] ? Object.keys(busySlots[activeAppointment.date]) : []
}

function isSlotOccupiedBySelection (inspectedStart, inspectedEnd, occupiedStart, occupiedEnd) {
  return !(inspectedEnd <= occupiedStart || inspectedStart >= occupiedEnd)
}

function useAvailableSlots (store) {
  let cart = store.getters['booking/getAllMultipleAppointments']

  let cartIndex = store.getters['booking/getCartItemIndex']

  let activeAppointment = cart[cartIndex].services[cart[cartIndex].serviceId].list[cart[cartIndex].index]

  let services = useServices(store)

  if (activeAppointment.date) {
    let selectedSlots = []

    cart.forEach((cartItem, selectedCartIndex) => {
      for (let serviceId in cartItem.services) {
        let service = services.find(i => i.id === parseInt(serviceId))

        cartItem.services[serviceId].list.forEach((i, selectedListIndex) => {
          let isCurrentAppointment = selectedCartIndex === parseInt(cartIndex) &&
              selectedListIndex === parseInt(cart[cartIndex].index) &&
              (cartItem.packageId ? parseInt(cartItem.serviceId) === parseInt(serviceId) : true)

          if (i.date && i.date === activeAppointment.date && i.time && !isCurrentAppointment) {
            selectedSlots.push({
              providerId: i.providerId,
              time: i.time,
              duration: i.duration + i.extras.filter(e => e.quantity && e.duration).map(e => e.duration).reduce((fullDuration, duration) => fullDuration + duration, 0),
              timeAfter: service.timeAfter,
              timeBefore: service.timeBefore
            })
          }
        })
      }
    })

    let targetService = services.find(i => i.id === cart[cartIndex].serviceId)

    let targetDuration = activeAppointment.duration + activeAppointment.extras
      .filter(e => e.quantity && e.duration)
      .map(e => e.duration)
      .reduce((fullDuration, duration) => fullDuration + duration, 0)

    // Date may only exist in waiting list slots (not in regular slots) — guard against undefined
    if (!(activeAppointment.date in cart[cartIndex].services[cart[cartIndex].serviceId].slots)) {
      return []
    }

    let defaultSlots = Object.keys(cart[cartIndex].services[cart[cartIndex].serviceId].slots[activeAppointment.date])

    let availableSlots = {}

    for (let i = 0; i < defaultSlots.length; i++) {
      let slotEmployeesIds = cart[cartIndex].services[
          cart[cartIndex].serviceId
        ].slots[activeAppointment.date][defaultSlots[i]].map((j) => parseInt(j.e))

      let targetSlotSeconds = useTimeInSeconds(defaultSlots[i])

      let occupiedSlotsCounter = 0

      let occupiedSlotsEmployeesIds = []

      for (let j = 0; j < selectedSlots.length; j++) {
        let selectedSlotSeconds = useTimeInSeconds(selectedSlots[j].time)

        if (isSlotOccupiedBySelection(
          targetSlotSeconds - targetService.timeBefore,
          targetSlotSeconds + targetDuration + targetService.timeAfter,
          selectedSlotSeconds - selectedSlots[j].timeBefore,
          selectedSlotSeconds + selectedSlots[j].duration + selectedSlots[j].timeAfter
        )) {
          occupiedSlotsCounter++

          if (selectedSlots[j].providerId) {
            occupiedSlotsEmployeesIds.push(selectedSlots[j].providerId)
          }
        }
      }

      if (
        occupiedSlotsEmployeesIds.length
          ? slotEmployeesIds.some(
            (value) => !occupiedSlotsEmployeesIds.includes(value)
            )
          : occupiedSlotsCounter < cart[cartIndex].services[cart[cartIndex].serviceId].slots[activeAppointment.date][defaultSlots[i]].length
      ) {
        availableSlots[defaultSlots[i]] = targetSlotSeconds
      }
    }

    return useSortedDateStrings(Object.keys(availableSlots))
  }

  return 'slots' in activeAppointment ? activeAppointment.slots : []
}

function useFillAppointments (store) {
  let cartItem = useCartItem(store)

  if (!cartItem.packageId &&
    Object.keys(cartItem.services).length === 1 &&
    cartItem.services[cartItem.serviceId].list.length === 1
  ) {
    let booking = cartItem.services[cartItem.serviceId].list[0]

    if (!booking.providerId && booking.date && booking.time) {
      // Check if waiting list slot with pre-selected provider
      let isWaitingListSlot = store.getters['appointmentWaitingListOptions/getIsWaitingListSlot']
      let waitingListOptions = store.getters['appointmentWaitingListOptions/getWaitingListOptions']

      if (isWaitingListSlot && waitingListOptions.selectedProviderId) {
        // Use the provider ID that was stored when the waiting list slot was selected
        booking.providerId = waitingListOptions.selectedProviderId
      } else {
        // Derive employeesIds. If a waiting list slot is selected (not present in regular slots),
        // pull provider data from occupied structure instead of slots.
        let slotDataArray = null

        if (
          isWaitingListSlot &&
          booking.date && booking.time &&
          (
            !(booking.date in cartItem.services[cartItem.serviceId].slots) ||
            !(booking.time in cartItem.services[cartItem.serviceId].slots[booking.date])
          ) &&
          booking.date in cartItem.services[cartItem.serviceId].occupied &&
          booking.time in cartItem.services[cartItem.serviceId].occupied[booking.date]
        ) {
          // Waiting list slot: use occupied data (fully booked original appointment rows)
          const serviceId = parseInt(cartItem.serviceId)
          slotDataArray = cartItem.services[cartItem.serviceId].occupied[booking.date][booking.time]
            .filter(p => p.s === serviceId)
        } else if (
          booking.date && booking.time &&
          booking.date in cartItem.services[cartItem.serviceId].slots &&
          booking.time in cartItem.services[cartItem.serviceId].slots[booking.date]
        ) {
          // Regular slot
          slotDataArray = cartItem.services[cartItem.serviceId].slots[booking.date][booking.time]
        } else {
          slotDataArray = []
        }

        let occupiedSlotEmployeesIds = !isWaitingListSlot
          ? getOccupiedSlotEmployeesIds(store, cartItem.serviceId, booking)
          : []

        let employeesIds = slotDataArray.map(i => i.e).filter(
            (v, i, a) => a.indexOf(v) === i && !occupiedSlotEmployeesIds.includes(v)
        )

        if (!employeesIds.length) {
          employeesIds = slotDataArray.map(i => i.e).filter((v, i, a) => a.indexOf(v) === i)
        }

        if (settings.roles.limitPerEmployee.enabled) {
          let chosenEmployees = store.getters['booking/getAllMultipleAppointments'].map(a => Object.values(a.services)[0].list[0])
          let appCount = store.getters['booking/getMultipleAppointmentsAppCount'](cartItem.serviceId);
          let result = checkLimitPerEmployee(employeesIds, 0, [], booking, appCount, chosenEmployees, cartItem.serviceId)
          if (result.bookingFailed !== null) {
            return {booking: result.bookingFailed, serviceId: parseInt(cartItem.serviceId)}
          }
          employeesIds = result.employeeIds
        }

        if (settings.appointments.employeeSelection === 'random') {
          booking.providerId = employeesIds[Math.floor(Math.random() * (employeesIds.length) + 1) - 1]
        } else {
          employeesIds = sortForEmployeeSelection(store, employeesIds, cartItem.serviceId)
          booking.providerId = employeesIds[0]
        }
      }
    }

    if (!booking.locationId && booking.date && booking.time) {
      let isWaitingListSlot = store.getters['appointmentWaitingListOptions/getIsWaitingListSlot']

      let slotRows = null
      if (
        isWaitingListSlot &&
        (
          !(booking.date in cartItem.services[cartItem.serviceId].slots) ||
          !(booking.time in cartItem.services[cartItem.serviceId].slots[booking.date])
        ) &&
        booking.date in cartItem.services[cartItem.serviceId].occupied &&
        booking.time in cartItem.services[cartItem.serviceId].occupied[booking.date]
      ) {
        // Waiting list slot: derive possible locations from occupied data
        const serviceId = parseInt(cartItem.serviceId)
        slotRows = cartItem.services[cartItem.serviceId].occupied[booking.date][booking.time]
          .filter(p => p.s === serviceId)
      } else if (
        booking.date in cartItem.services[cartItem.serviceId].slots &&
        booking.time in cartItem.services[cartItem.serviceId].slots[booking.date]
      ) {
        slotRows = cartItem.services[cartItem.serviceId].slots[booking.date][booking.time]
      } else {
        slotRows = []
      }

      let locationsIds = slotRows.filter(r => r.e === booking.providerId).map(r => r.l)

      booking.locationId = locationsIds.length ? getPreferredEntityId(
        cartItem.services[cartItem.serviceId].slots[booking.date] || {},
        booking.date in cartItem.services[cartItem.serviceId].occupied
          ? cartItem.services[cartItem.serviceId].occupied[booking.date] : {},
        booking.time,
        booking.providerId,
        locationsIds,
        'l'
      ) : null
    }

    let slots = store.getters['booking/getMultipleAppointmentsSlots']

    let existingApp = booking.date in slots && booking.time in slots[booking.date] && slots[booking.date][booking.time].length > 0 ?
      slots[booking.date][booking.time].find(s => s.e === booking.providerId) : null

    store.commit('booking/setMultipleAppointmentsExistingApp', existingApp && existingApp.c && existingApp.c > 0)

    booking.price = existingApp && 'p' in existingApp ? existingApp.p : null
  } else {
    let chosenEmployees = []

    for (let serviceId of Object.keys(cartItem.services)) {
      if (cartItem.services[serviceId].list.length &&
          cartItem.services[serviceId].list.filter(i => i.date && i.time).length
      ) {
        let bookingFailed = setPreferredEntitiesData(cartItem.services[serviceId], store, serviceId, chosenEmployees)

        if (bookingFailed !== null) {
          return {booking: bookingFailed, serviceId: parseInt(serviceId)}
        }
        chosenEmployees = chosenEmployees.concat(cartItem.services[serviceId].list.map((l) => { return { date: l.date, providerId: l.providerId, serviceId: serviceId, existingApp: 'existingApp' in l && l.existingApp, price: l.price} }))
      }
    }
  }

  let activeItemServices = cartItem.services

  Object.keys(activeItemServices).forEach((serviceId) => {
    if (activeItemServices[serviceId].list.filter(i => i.date && i.time).length) {
      activeItemServices[serviceId].list.forEach((booking) => {
        if (booking.date && booking.time) {
          setProviderServicePrice(store, booking.providerId, serviceId, booking.persons)
        }
      })
    }
  })

  return null
}

function getOccupiedSlotEmployeesIds (store, serviceId, booking) {
  let cart = useCart(store)

  let services = useServices(store)

  let targetService = services.find(i => i.id === parseInt(serviceId))

  let targetSlotSeconds = useTimeInSeconds(booking.time)

  let targetDuration = booking.duration + booking.extras.filter(e => e.quantity && e.duration).map(e => e.duration).reduce((fullDuration, duration) => fullDuration + duration, 0)

  let occupiedSlotEmployeesIds = []

  cart.forEach((item) => {
    item.services[item.serviceId].list.forEach((slot) => {
      if (slot.providerId && slot.date && slot.time && booking.date === slot.date) {
        let selectedService = services.find(i => i.id === item.serviceId)

        let selectedSlotSeconds = useTimeInSeconds(slot.time)

        let selectedDuration = slot.duration + slot.extras.filter(e => e.quantity && e.duration).map(e => e.duration).reduce((fullDuration, duration) => fullDuration + duration, 0)

        if (isSlotOccupiedBySelection(
          targetSlotSeconds - targetService.timeBefore,
          targetSlotSeconds + targetDuration + targetService.timeAfter,
          selectedSlotSeconds - selectedService.timeBefore,
          selectedSlotSeconds + selectedDuration + selectedService.timeAfter
        )) {
          occupiedSlotEmployeesIds.push(slot.providerId)
        }
      }
    })
  })

  return [...new Set(occupiedSlotEmployeesIds)].map(i => parseInt(i))
}

function setProviderServicePrice (store, employeeId, serviceId, persons) {
  let employee = store.getters['entities/getUnfilteredEmployee'](employeeId)

  let service = employee.serviceList.find(i => i.id === parseInt(serviceId))

  service.duration = store.getters['booking/getDuration']

  service.price = useAppointmentServicePrice(
    service,
    persons,
    store.getters['booking/getDuration']
  )
}

function useAppointmentsAmount (store, service, appointments) {
  let amount = 0

  appointments.forEach((appointment) => {
    amount += useAppointmentServiceAmount(
      useEmployeeService(store, service.id, appointment.providerId),
      appointment.persons,
      appointment.duration,
      appointment.price
    )
  })

  return amount
}

function useExtraAmount (extra, serviceAggregatedPrice, persons) {
  let extraAggregatedPrice = extra.aggregatedPrice === null ? serviceAggregatedPrice : extra.aggregatedPrice

  return extra.price * extra.quantity * (extraAggregatedPrice ? persons : 1)
}

function useAppointmentExtraAmount (service, selectedExtra, persons) {
  let extra = service.extras.find(item => item.id === parseInt(selectedExtra.extraId))

  if (extra) {
    let extraAggregatedPrice = extra.aggregatedPrice === null ? service.aggregatedPrice : extra.aggregatedPrice

    return extra.price * selectedExtra.quantity * (extraAggregatedPrice ? persons : 1)
  }

  return 0
}

function useAppointmentExtrasAmount (service, appointments) {
  let amount = 0

  appointments.forEach((appointment) => {
    if (appointment.extras) {
      appointment.extras.forEach((selectedExtra) => {
        amount += useAppointmentExtraAmount(service, selectedExtra, appointment.persons)
      })
    }
  })

  return amount
}

function useAppointmentsTotalAmount (store, service, appointments) {
  return useAppointmentsAmount(store, service, appointments) + useAppointmentExtrasAmount(service, appointments)
}

function useAppointmentsPayments (store, serviceId, appointments) {
  let service = store.getters['entities/getService'](
    serviceId
  )

  let prepaidCount = 1

  if (service.recurringPayment) {
    prepaidCount = service.recurringPayment > appointments.length
      ? appointments.length : service.recurringPayment
  }

  return {
    prepaid: appointments.slice(0, prepaidCount),
    postpaid: appointments.slice(prepaidCount),
  }
}

function setPreferredEntitiesData (bookings, store, serviceId, chosenEmployees) {
  let allEmployeesIds = getAllEntitiesIds(bookings, 'e')

  let locationsIds = getAllEntitiesIds(bookings, 'l')

  let isSingleEmployee = allEmployeesIds.length === 1

  let isSingleLocation = locationsIds.length === 1

  let appCount = store.getters['booking/getMultipleAppointmentsAppCount'](serviceId);

  for (let bookingIndex = 0; bookingIndex < bookings.list.length; bookingIndex++) {
    let booking = bookings.list[bookingIndex]

    if (booking.date && booking.time) {
      let occupiedSlotEmployeesIds = !booking.providerId
        ? getOccupiedSlotEmployeesIds(store, serviceId, booking)
        : []

      let employeesIds = sortForEmployeeSelection(
          store,
          allEmployeesIds.filter(x => !occupiedSlotEmployeesIds.includes(x)),
          serviceId
      )

      if (!employeesIds.length) {
        employeesIds = sortForEmployeeSelection(store, allEmployeesIds, serviceId)
      }

      if (settings.roles.limitPerEmployee.enabled) {
        let result = checkLimitPerEmployee(employeesIds, bookingIndex, bookings.list, booking, appCount, chosenEmployees, serviceId)
        if (result.bookingFailed !== null) {
          return result.bookingFailed
        }
        employeesIds = result.employeeIds
      }

      if (!locationsIds.length && isSingleEmployee) {
        booking.providerId = employeesIds[0]

        booking.locationId = null
      } else if (!locationsIds.length && !isSingleEmployee) {
        booking.locationId = null

        for (let i = 0; i < employeesIds.length; i++) {
          for (let j = 0; j < bookings.slots[booking.date][booking.time].length; j++) {
            if (bookings.slots[booking.date][booking.time][j].e === employeesIds[i]) {
              booking.providerId = employeesIds[i]

              break
            }
          }
        }
      } else if (isSingleLocation && isSingleEmployee) {
        booking.providerId = employeesIds[0]

        booking.locationId = locationsIds[0]
      } else if (!isSingleLocation && isSingleEmployee) {
        booking.providerId = employeesIds[0]

        booking.locationId = getPreferredEntityId(
          bookings.slots[booking.date],
          booking.date in bookings.occupied ? bookings.occupied[booking.date] : {},
          booking.time,
          booking.providerId,
          locationsIds,
          'l'
        )
      } else if (isSingleLocation && !isSingleEmployee) {
        booking.locationId = locationsIds[0]

        const preferredProviderId = getPreferredEntityId(
          bookings.slots[booking.date],
          booking.date in bookings.occupied ? bookings.occupied[booking.date] : {},
          booking.time,
          booking.locationId,
          employeesIds,
          'e'
        )

        booking.providerId = employeesIds.includes(preferredProviderId) ? preferredProviderId : employeesIds[0]
      } else {
        let setEntities = false
        outsideLoop: for (let j = 0; j < employeesIds.length; j++) {
          for (let i = 0; i < locationsIds.length; i++) {
            let isPreferred = isPreferredLocationAndEmployee(
              bookings.slots[booking.date],
              booking.date in bookings.occupied ? bookings.occupied[booking.date] : {},
              booking.time,
              locationsIds[i],
              employeesIds[j]
            )

            if (isPreferred) {
              booking.providerId = employeesIds[j]

              booking.locationId = locationsIds[i]

              setEntities = true

              break outsideLoop
            }
          }
        }

        if (!setEntities) {
          outsideLoop2: for (let j = 0; j < employeesIds.length; j++) {
            for (let i = 0; i < locationsIds.length; i++) {
              for (let k = 0; k < bookings.slots[booking.date][booking.time].length; k++) {
                if (bookings.slots[booking.date][booking.time][k].e === employeesIds[j] &&
                    bookings.slots[booking.date][booking.time][k].l === locationsIds[i]
                ) {
                  booking.providerId = employeesIds[j]

                  booking.locationId = locationsIds[i]

                  break outsideLoop2
                }
              }
            }
          }
        }
      }

      store.commit('booking/setMultipleAppointmentsServiceProvider', booking.providerId)

      let slots = store.getters['booking/getMultipleAppointmentsSlots']

      let existingApp = booking.date in slots && booking.time in slots[booking.date] &&  slots[booking.date][booking.time].length > 0 ?
          slots[booking.date][booking.time].find(s => s.e === booking.providerId) : null

      bookings.list[bookingIndex].existingApp = existingApp && existingApp.c && existingApp.c > 0

      store.commit('booking/setLastBookedProviderId', {providerId: booking.providerId, fromBackend: false})

      bookings.list[bookingIndex].price = existingApp && 'p' in existingApp ? existingApp.p : null
    }
  }

  return null
}

function getAllEntitiesIds (bookings, index) {
  let ids = {}

  for (let i = 0; i < bookings.list.length; i++) {
    if (bookings.list[i].date && bookings.list[i].time) {
      bookings.slots[bookings.list[i].date][bookings.list[i].time].forEach((slotData) => {
        if (slotData[index]) {
          if (!(slotData[index] in ids)) {
            ids[slotData[index]] = 0
          }

          ids[slotData[index]]++
        }
      })
    }
  }

  let sortedEntitiesIds = []

  Object.keys(ids).forEach((id) => {
    sortedEntitiesIds.push({id: parseInt(id), quantity: ids[id]})
  })

  sortedEntitiesIds.sort((a, b) => b.quantity - a.quantity)

  return sortedEntitiesIds.map(entity => entity.id)
}

function getPreferredEntityId (availableSlots, occupiedSlots, timeString, selectedId, allIds, targetIndex) {
  let searchIndex = targetIndex === 'e' ? 'l' : 'e'

  let appointmentsStarts = {}

  Object.keys(occupiedSlots).forEach((time) => {
    occupiedSlots[time].forEach((slotData) => {
      if (slotData[searchIndex] === selectedId) {
        appointmentsStarts[useTimeInSeconds(time)] = slotData[targetIndex]
      }
    })
  })

  Object.keys(availableSlots).forEach((time) => {
    availableSlots[time].forEach((slotData) => {
      if (Object.keys(slotData).length >= 3 && slotData[searchIndex] === selectedId) {
        appointmentsStarts[useTimeInSeconds(time)] = slotData[targetIndex]
      }
    })
  })

  let availableIds = []

  // Collect ids from available slots for the specific time; if time not in availableSlots (e.g., waiting list slot),
  // fall back to occupied slots data to derive potential ids.
  let timeRows = (timeString in availableSlots && Array.isArray(availableSlots[timeString])) ? availableSlots[timeString] : []

  if (!timeRows.length && (timeString in occupiedSlots) && Array.isArray(occupiedSlots[timeString])) {
    timeRows = occupiedSlots[timeString]
  }

  timeRows.forEach((slotData) => {
    if (slotData[searchIndex] === selectedId) {
      availableIds.push(slotData[targetIndex])
    }
  })

  if (Object.keys(appointmentsStarts).length) {
    let timeInSeconds = useTimeInSeconds(timeString)

    let closestSlot = Object.keys(appointmentsStarts).reduce((a, b) => {
      return Math.abs(b - timeInSeconds) < Math.abs(a - timeInSeconds) ? b : a
    })

    if (availableIds.indexOf(appointmentsStarts[closestSlot]) !== -1) {
      return appointmentsStarts[closestSlot]
    }
  }

  for (let i = 0; i < allIds.length; i++) {
    for (let j = 0; j < timeRows.length; j++) {
      if (timeRows[j][searchIndex] === selectedId &&
        allIds[i] === timeRows[j][targetIndex]
      ) {
        return timeRows[j][targetIndex]
      }
    }
  }

  return targetIndex === 'e' ? availableSlots[timeString][0][targetIndex] : null
}

function isPreferredLocationAndEmployee (slotsData, occupiedData, timeString, locationId, employeeId) {
  let isEmployeeLocation = false

  slotsData[timeString].forEach((slotData) => {
    if (slotData.e === employeeId && slotData.l === locationId) {
      isEmployeeLocation = true
    }
  })

  // inspect if employee is available on proposed location
  if (!isEmployeeLocation) {
    return false
  }

  let appointmentStarts = {
    onLocation: {},
    offLocation: {}
  }

  Object.keys(occupiedData).forEach((time) => {
    occupiedData[time].forEach((slotData) => {
      if (slotData.e === employeeId && slotData.l === locationId) {
        appointmentStarts.onLocation[useTimeInSeconds(time)] = slotData.l
      } else if (slotData.e === employeeId) {
        appointmentStarts.offLocation[useTimeInSeconds(time)] = slotData.l
      }
    })
  })

  Object.keys(slotsData).forEach((time) => {
    slotsData[time].forEach((slotData) => {
      if ('p' in slotData && slotData.e === employeeId && slotData.l === locationId) {
        appointmentStarts.onLocation[useTimeInSeconds(time)] = slotData.l
      } else if ('p' in slotData && slotData.e === employeeId) {
        appointmentStarts.offLocation[useTimeInSeconds(time)] = slotData.l
      }
    })
  })

  // inspect if employee has appointments only on proposed location, or has no appointments in that day
  if (
    (!Object.keys(appointmentStarts.onLocation).length && !Object.keys(appointmentStarts.offLocation).length) ||
    (Object.keys(appointmentStarts.onLocation).length && !Object.keys(appointmentStarts.offLocation).length)
  ) {
    return true
  }

  let timeInSeconds = useTimeInSeconds(timeString)

  appointmentStarts = Object.assign(appointmentStarts.onLocation, appointmentStarts.offLocation)

  let closestTime = Object.keys(appointmentStarts).reduce((a, b) => {
    return Math.abs(b - timeInSeconds) < Math.abs(a - timeInSeconds) ? b : a
  })

  return locationId === appointmentStarts[closestTime]
}

function  useCheckingIfAllNotFree (store) {
  let preselected = store.getters['entities/getPreselected']

  if (preselected.show === 'packages') {
    let packages = store.getters['booking/getPackageId']
      ? [store.getters['entities/getPackage'](store.getters['booking/getPackageId'])]
      : store.getters['entities/getPackages']

    return packages.length > 0 && packages.filter(p => p.price > 0).length === packages.length
  }

  if (!store.getters['booking/getPackageId']) {
    let services = store.getters['booking/getServiceId']
      ? [store.getters['entities/getService'](store.getters['booking/getServiceId'])]
      : store.getters['entities/getServices']

    let nonFreeServices = 0
    for (let service of services) {
      let employees = store.getters['booking/getEmployeeId']
        ? (
          store.getters['entities/getEmployee'](store.getters['booking/getEmployeeId'])
            ? [store.getters['entities/getEmployee'](store.getters['booking/getEmployeeId'])]
            : []
        )
        : store.getters['entities/getEmployees']

      let duration = store.getters['booking/getBookingDuration']

      let persons = store.getters['booking/getBookingPersons']

      let providers = employees.filter(
        eS => eS.serviceList.find(s =>
          s.id === service.id &&
          (
            s.price > 0 ||
            (
              s.customPricing &&
              (
                (
                  s.customPricing.enabled === 'duration' &&
                  (
                    Object.values(s.customPricing.durations).length === Object.values(s.customPricing.durations).filter(cp => cp.price > 0).length ||
                    (
                      duration &&
                      s.customPricing.durations[duration].price > 0
                    )
                  )
                ) || (
                  s.customPricing.enabled === 'person' &&
                  (
                    s.customPricing.persons.length === s.customPricing.persons.filter(cp => cp.price > 0).length ||
                    (
                      persons &&
                      (
                        s.customPricing.persons.filter(cp => cp.range >= persons).length
                          ? s.customPricing.persons.filter(cp => cp.range >= persons)[0].price > 0
                          : s.customPricing.persons[s.customPricing.persons.length - 1].price > 0
                      )
                    )
                  )
                )
              )
            )
          )
        )
      )

      if (providers.length === employees.filter(eS => eS.serviceList.find(s => s.id === service.id)).length) {
        nonFreeServices++
      } else {
        let extras = store.getters['booking/getSelectedExtras'].length
          ? store.getters['booking/getSelectedExtras']
          : []

        if (extras.length > 0 && extras.reduce((partialSum, a) => partialSum + a.price, 0) > 0) {
          nonFreeServices++
        }
      }
    }

    return services.length > 0 && nonFreeServices === services.length
  }

  return store.getters['entities/getPackage'](store.getters['booking/getPackageId']).price > 0
}

export {
  useCapacity,
  useAvailableSlots,
  useBusySlots,
  useFillAppointments,
  useCalendarEvents,
  useAppointmentsPayments,
  useAppointmentsAmountInfo,
  useAppointmentServiceAmount,
  useAppointmentExtrasAmount,
  useAppointmentExtraAmount,
  useAppointmentsAmount,
  useAppointmentsTotalAmount,
  useDuration,
  usePrepaidPrice,
  usePaymentError,
  useServices,
  useEmployeeService,
  useAppointmentBookingAmountData,
  useCheckingIfAllNotFree,
  useAppointmentServicePrice,
  useChangedBookingPrice,
}
