import { computed, inject, resource } from '@angular/core';
import {
  CanReturn,
  ProductCategory,
  Receipt,
  ReceiptItem,
  ReturnDetailsService,
} from '@isa/oms/data-access';
import {
  patchState,
  signalStore,
  type,
  withComputed,
  withMethods,
  withProps,
  withState,
} from '@ngrx/signals';
import { setEntity, withEntities, entityConfig } from '@ngrx/signals/entities';
import {
  canReturnReceiptItem,
  getReceiptItemQuantity,
  getReceiptItemProductCategory,
  receiptItemHasCategory,
  getReceiptItemReturnedQuantity,
} from '../helpers/return-process';
import { logger } from '@isa/core/logging';

interface ReturnDetailsState {
  _selectedItemIds: number[];
  selectedProductCategory: Record<number, ProductCategory>;
  selectedQuantity: Record<number, number>;
  canReturn: Record<string, CanReturn>;
}

const initialState: ReturnDetailsState = {
  _selectedItemIds: [],
  selectedProductCategory: {},
  selectedQuantity: {},
  canReturn: {},
};

export const receiptConfig = entityConfig({
  entity: type<Receipt>(),
  collection: 'receipts',
});

export const ReturnDetailsStore = signalStore(
  withState(initialState),
  withEntities(receiptConfig),
  withProps(() => ({
    _logger: logger(() => ({ store: 'ReturnDetailsStore' })),
    _returnDetailsService: inject(ReturnDetailsService),
  })),
  withComputed((store) => ({
    items: computed<Array<ReceiptItem>>(() =>
      store
        .receiptsEntities()
        .map((receipt) => receipt.items)
        .flat()
        .map((container) => container.data!),
    ),
  })),
  withComputed((store) => ({
    availableQuantityMap: computed(() => {
      const items = store.items();
      const availableQuantity: Record<number, number> = {};

      items.forEach((item) => {
        const itemId = item.id;
        const quantity = getReceiptItemQuantity(item);
        const returnedQuantity = getReceiptItemReturnedQuantity(item);
        availableQuantity[itemId] = quantity - returnedQuantity;
      });

      return availableQuantity;
    }),

    itemCategoryMap: computed(() => {
      const items = store.items();
      const categoryMap: Record<number, ProductCategory> = {};

      items.forEach((item) => {
        const itemId = item.id;
        const selectedCategory = store.selectedProductCategory()[itemId];
        const category = getReceiptItemProductCategory(item);
        categoryMap[itemId] = selectedCategory ?? category;
      });

      return categoryMap;
    }),
  })),

  withComputed((store) => ({
    selectedQuantityMap: computed(() => {
      const items = store.items();
      const selectedQuantity: Record<number, number> = {};

      items.forEach((item) => {
        const itemId = item.id;
        const quantity =
          store.selectedQuantity()[itemId] ||
          store.availableQuantityMap()[itemId];
        selectedQuantity[itemId] = quantity;
      });

      return selectedQuantity;
    }),
  })),

  withComputed((store) => ({
    selectedItemIds: computed(() => {
      const selectedIds = store._selectedItemIds();
      const canReturn = store.canReturn();

      return selectedIds.filter((id) => {
        const canReturnResult = canReturn[id]?.result;
        return typeof canReturnResult === 'boolean' ? canReturnResult : true;
      });
    }),
  })),
  withComputed((store) => ({
    returnableItems: computed(() => {
      const items = store.items();
      return items.filter(canReturnReceiptItem);
    }),
    selectableItems: computed(() => {
      const items = store.items();
      const selectedProductCategory = store.selectedProductCategory();
      return items.filter(canReturnReceiptItem).filter((item) => {
        const category = selectedProductCategory[item.id];
        return (
          category || !receiptItemHasCategory(item, ProductCategory.Unknown)
        );
      });
    }),
    selectedItems: computed(() => {
      const selectedIds = store.selectedItemIds();
      const items = store.items();
      return items.filter((item) => selectedIds.includes(item.id));
    }),
  })),
  withMethods((store) => ({
    receiptResource: (receiptId: () => number | undefined) =>
      resource({
        params: receiptId,
        loader: async ({ abortSignal, params }) => {
          if (!params) {
            return undefined;
          }
          const receipt = await store._returnDetailsService.fetchReturnDetails(
            { receiptId: params },
            abortSignal,
          );

          patchState(store, setEntity(receipt, receiptConfig));
          return receipt;
        },
      }),

    canReturnResource: (receiptItem: () => ReceiptItem | undefined) =>
      resource({
        params: () => {
          const item = receiptItem();

          if (!item) {
            return undefined;
          }

          const receiptItemId = item.id;
          const quantity = store.selectedQuantityMap()[receiptItemId];
          const category = store.itemCategoryMap()[receiptItemId];

          return {
            receiptItemId,
            quantity,
            category,
          };
        },
        loader: async ({ params, abortSignal }) => {
          if (params === undefined) {
            return undefined;
          }
          const key = `${params.receiptItemId}:${params.category}`;

          if (store.canReturn()[key]) {
            return store.canReturn()[key];
          }

          const res = await store._returnDetailsService.canReturn(
            params,
            abortSignal,
          );
          patchState(store, {
            canReturn: { ...store.canReturn(), [key]: res },
          });

          return res;
        },
      }),
    getReceipt: (receiptId: () => number) =>
      computed(() => {
        const id = receiptId();
        const entities = store.receiptsEntityMap();
        return entities[id];
      }),
    getItems: (receiptId: () => number) =>
      computed(() => {
        const items = store.items();
        const id = receiptId();
        return items.filter((item) => item.receipt?.id === id);
      }),
    getSelectableItems: (receiptId: () => number) =>
      computed(() =>
        store
          .selectableItems()
          .filter((item) => item.receipt?.id === receiptId()),
      ),
    getItemSelected: (item: () => ReceiptItem) =>
      computed(() => {
        const selectedIds = store.selectedItemIds();
        return selectedIds.includes(item().id);
      }),
    isSelectable: (receiptItem: () => ReceiptItem) =>
      computed(() => {
        const item = receiptItem();
        const selectableItems = store.selectableItems();
        return selectableItems.some((i) => i.id === item.id);
      }),
    getCanReturn: (item: () => ReceiptItem) =>
      computed<CanReturn | undefined>(() => {
        const itemData = item();
        return store.canReturn()[itemData.id];
      }),
  })),

  withMethods((store) => ({
    addSelectedItems(itemIds: number[]) {
      const currentIds = store.selectedItemIds();
      const newIds = Array.from(new Set([...currentIds, ...itemIds]));
      patchState(store, { _selectedItemIds: newIds });
    },
    removeSelectedItems(itemIds: number[]) {
      const currentIds = store.selectedItemIds();
      const newIds = currentIds.filter((id) => !itemIds.includes(id));
      patchState(store, { _selectedItemIds: newIds });
    },
    async setProductCategory(itemId: number, category: ProductCategory) {
      const currentCategory = store.selectedProductCategory();
      const newCategory = { ...currentCategory, [itemId]: category };
      patchState(store, { selectedProductCategory: newCategory });
    },
    setQuantity(itemId: number, quantity: number) {
      const currentQuantity = store.selectedQuantity();
      const newQuantity = { ...currentQuantity, [itemId]: quantity };
      patchState(store, { selectedQuantity: newQuantity });
    },
  })),
);
