import {
  INITIAL_CASH_FLOW,
  NUMBER_OF_DAYS,
  NUMBER_OF_HOURS_PER_DAY
} from '@/modules/heijkunka/heijunka-config'
import { ProductType } from '@/modules/heijkunka/types/product-type'
import { getMean, pickRandomElement } from '@/utils'
import { defineStore } from 'pinia'

type Order = {
  status: 'requested' | 'received'
  leadTime: number
  product: ProductType
}

type Inventory = {
  shirt: number
  jeans: number
  shoes: number
  hat: number
}

type HeijunkaState = {
  inventory: Inventory
  orders: Order[]
  meta: {
    currentHour: number
  }
}

const getInventoryByProduct = (
  product: ProductType,
  planning: ProductType[],
  currentDay: number
): number => {
  const inventory = planning.filter(
    (p, index) =>
      index >= (currentDay % NUMBER_OF_DAYS) * NUMBER_OF_HOURS_PER_DAY &&
      index < ((currentDay % NUMBER_OF_DAYS) + 1) * NUMBER_OF_HOURS_PER_DAY &&
      p === product
  ).length

  return inventory
}

const initialInventory: Inventory = {
  shirt: 0,
  jeans: 0,
  shoes: 0,
  hat: 0
}

export const useHeijunkaStore = defineStore('heijunka', {
  state: (): HeijunkaState => ({
    inventory: { ...initialInventory },
    orders: [],
    meta: {
      currentHour: 0
    }
  }),
  actions: {
    newHour(planning: ProductType[]) {
      this.meta.currentHour++

      // Add to inventory every day
      if (this.meta.currentHour % NUMBER_OF_HOURS_PER_DAY === 0) {
        this.inventory = {
          shirt:
            this.inventory.shirt +
            getInventoryByProduct('shirt', planning, this.currentDay),
          jeans:
            this.inventory.jeans +
            getInventoryByProduct('jeans', planning, this.currentDay),
          shoes:
            this.inventory.shoes +
            getInventoryByProduct('shoes', planning, this.currentDay),
          hat:
            this.inventory.hat +
            getInventoryByProduct('hat', planning, this.currentDay)
        }
      }

      // Each non received demand gains one hour of lead time
      this.orders = this.orders.map((order): Order => {
        return {
          ...order,
          leadTime:
            order.status === 'received' ? order.leadTime : order.leadTime + 1
        }
      })

      // New demand
      const newDemand: Order = {
        status: 'requested',
        leadTime: 0,
        product: pickRandomElement(
          [
            Array.from({ length: 5 }, (): ProductType => 'shirt'),
            Array.from({ length: 3 }, (): ProductType => 'jeans'),
            Array.from({ length: 2 }, (): ProductType => 'shoes'),
            Array.from({ length: 2 }, (): ProductType => 'hat')
          ].flat()
        )
      }

      this.orders.push(newDemand)

      // Use the inventory to deliver orders
      this.orders = this.orders.map((order, index): Order => {
        const productInventory = this.inventory[order.product]

        if (productInventory === 0) {
          return order
        }

        const offset = this.orders.filter(
          (o, i) => i < index && o.product !== order.product
        ).length

        const newStatus =
          index - offset < productInventory ? 'received' : 'requested'

        return {
          ...order,
          status: newStatus
        }
      })
    },
    reset() {
      this.meta.currentHour = 0
      this.orders = []
      this.inventory = { ...initialInventory }
    },
    simulateMonth(planning: ProductType[]) {
      for (let index = 0; index < 80; index++) {
        this.newHour(planning)
      }
    }
  },
  getters: {
    currentDay: (state) =>
      Math.ceil(state.meta.currentHour / NUMBER_OF_HOURS_PER_DAY),
    cashFlow: (state) =>
      INITIAL_CASH_FLOW -
      state.meta.currentHour * 1 +
      state.orders.filter((o) => o.status === 'received').length * 2,
    remainingInventory: (state): Inventory => ({
      shirt: Math.max(
        state.inventory.shirt -
          state.orders.filter((o) => o.product === 'shirt').length,
        0
      ),
      jeans: Math.max(
        state.inventory.jeans -
          state.orders.filter((o) => o.product === 'jeans').length,
        0
      ),
      shoes: Math.max(
        state.inventory.shoes -
          state.orders.filter((o) => o.product === 'shoes').length,
        0
      ),
      hat: Math.max(
        state.inventory.hat -
          state.orders.filter((o) => o.product === 'hat').length,
        0
      )
    }),
    // state.meta.currentHour >= NUMBER_OF_DAYS * NUMBER_OF_HOURS_PER_DAY,
    meanLeadTime: (state) => getMean(state.orders.map((o) => o.leadTime))
  }
})
