import {
  patchState,
  signalStore,
  withComputed,
  withMethods,
  withProps,
  withState,
} from '@ngrx/signals';
import { ReturnItem, ReturnSuggestion } from '../models';
import { computed, inject, resource } from '@angular/core';
import { UserStorageProvider, withStorage } from '@isa/core/storage';
import { RemissionReturnReceiptService } from '../services';

/**
 * Union type representing items that can be selected for remission.
 * Can be either a ReturnItem or a ReturnSuggestion.
 */
export type RemissionItem = ReturnItem | ReturnSuggestion;

/**
 * Interface defining the state structure for the remission selection store.
 */
interface RemissionState {
  /** The unique identifier for the return process. Can only be set once. */
  returnId: number | undefined;
  /** The unique identifier for the receipt. Can only be set once. */
  receiptId: number | undefined;
  /** Map of selected remission items indexed by their ID */
  selectedItems: Record<number, RemissionItem>;
  /** Map of selected quantities for each remission item indexed by their ID */
  selectedQuantity: Record<number, number>;
}

/**
 * Initial state for the remission selection store.
 * All values are undefined or empty objects.
 */
const initialState: RemissionState = {
  returnId: undefined,
  receiptId: undefined,
  selectedItems: {},
  selectedQuantity: {},
};

/**
 * NgRx Signal Store for managing remission selection state.
 * Provides methods to start remission processes, select items, update quantities,
 * and manage the overall selection state.
 *
 * @example
 * ```typescript
 * // Inject the store in a component
 * readonly remissionStore = inject(RemissionStore);
 *
 * // Start a remission process
 * this.remissionStore.startRemission(123, 456);
 *
 * // Select an item
 * this.remissionStore.selectRemissionItem(1, returnItem);
 *
 * // Update quantity
 * this.remissionStore.updateRemissionQuantity(1, returnItem, 5);
 * ```
 */
export const RemissionStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withStorage('remission-data-access.remission-store', UserStorageProvider),
  withProps(
    (
      store,
      remissionReturnReceiptService = inject(RemissionReturnReceiptService),
    ) => ({
      /**
       * Resource for fetching the receipt data based on the current receiptId.
       * This resource is automatically reloaded when the receiptId changes.
       * @returnId is undefined, the resource will not fetch any data.
       * @returnId is set, it fetches the receipt data from the service.
       */
      _fetchReturnResource: resource({
        params: () => ({
          returnId: store.returnId(),
        }),
        loader: async ({ params, abortSignal }) => {
          const { returnId } = params;

          if (!returnId) {
            return undefined;
          }

          const returnData = await remissionReturnReceiptService.fetchReturn(
            {
              returnId,
            },
            abortSignal,
          );
          return returnData;
        },
      }),
    }),
  ),
  withComputed((store) => ({
    remissionStarted: computed(
      () => store.returnId() !== undefined && store.receiptId() !== undefined,
    ),
    returnData: computed(() => store._fetchReturnResource.value()),
  })),
  withMethods((store) => ({
    /**
     * Initializes a remission process with the given return and receipt IDs.
     * Can only be called once - subsequent calls will throw an error.
     *
     * @param returnId - The unique identifier for the return process
     * @param receiptId - The unique identifier for the receipt
     * @throws {Error} When remission has already been started (returnId or receiptId already set)
     *
     * @example
     * ```typescript
     * remissionStore.startRemission(123, 456);
     * ```
     */
    startRemission({
      returnId,
      receiptId,
    }: {
      returnId: number;
      receiptId: number;
    }) {
      if (store.returnId() !== undefined || store.receiptId() !== undefined) {
        throw new Error(
          'Remission has already been started. returnId and receiptId can only be set once.',
        );
      }
      patchState(store, {
        returnId,
        receiptId,
      });
      store._fetchReturnResource.reload();
      store.saveToStorage();
    },

    /**
     * Reloads the return resource to fetch the latest data.
     * This is useful when the return data might have changed and needs to be refreshed.
     *
     * @example
     * ```typescript
     * remissionStore.reloadReturn();
     * ```
     */
    reloadReturn() {
      store._fetchReturnResource.reload();
    },

    /**
     * Checks if the current remission matches the provided returnId and receiptId.
     * This is useful for determining if the current remission is active in the context of a component.
     *
     * @param returnId - The return ID to check against the current remission
     * @param receiptId - The receipt ID to check against the current remission
     * @returns {boolean} True if the current remission matches the provided IDs, false otherwise
     *
     * @example
     * ```typescript
     * const isCurrent = remissionStore.isCurrentRemission(123, 456);
     * ```
     */
    isCurrentRemission({
      returnId,
      receiptId,
    }: {
      returnId: number | undefined;
      receiptId: number | undefined;
    }): boolean {
      return store.returnId() === returnId && store.receiptId() === receiptId;
    },

    /**
     * Selects a remission item and adds it to the selected items collection.
     * If the item is already selected, it will be replaced with the new item.
     *
     * @param remissionItemId - The unique identifier for the remission item
     * @param item - The remission item to select (ReturnItem or ReturnSuggestion)
     *
     * @example
     * ```typescript
     * const returnItem: ReturnItem = { id: 1, name: 'Product A' };
     * remissionStore.selectRemissionItem(1, returnItem);
     * ```
     */
    selectRemissionItem(remissionItemId: number, item: RemissionItem) {
      patchState(store, {
        selectedItems: { ...store.selectedItems(), [remissionItemId]: item },
      });
    },

    /**
     * Updates the quantity for a selected remission item.
     * Also ensures the item is in the selected items collection.
     *
     * @param remissionItemId - The unique identifier for the remission item
     * @param item - The remission item to update (ReturnItem or ReturnSuggestion)
     * @param quantity - The new quantity value
     *
     * @example
     * ```typescript
     * const returnItem: ReturnItem = { id: 1, name: 'Product A' };
     * remissionStore.updateRemissionQuantity(1, returnItem, 5);
     * ```
     */
    updateRemissionQuantity(
      remissionItemId: number,
      item: RemissionItem,
      quantity: number,
    ) {
      patchState(store, {
        selectedItems: { ...store.selectedItems(), [remissionItemId]: item },
        selectedQuantity: {
          ...store.selectedQuantity(),
          [remissionItemId]: quantity,
        },
      });
    },

    /**
     * Removes a remission item from the selected items collection.
     * Does not affect the selected quantities.
     *
     * @param remissionItemId - The unique identifier for the remission item to remove
     *
     * @example
     * ```typescript
     * remissionStore.removeItem(1);
     * ```
     */
    removeItem(remissionItemId: number) {
      const items = { ...store.selectedItems() };
      delete items[remissionItemId];
      patchState(store, {
        selectedItems: items,
      });
    },

    /**
     * Removes a remission item and its associated quantity from the store.
     * Updates both selected items and selected quantities collections.
     *
     * @param remissionItemId - The unique identifier for the remission item to remove
     *
     * @example
     * ```typescript
     * remissionStore.removeItemAndQuantity(1);
     * ```
     */
    removeItemAndQuantity(remissionItemId: number) {
      const items = { ...store.selectedItems() };
      const quantities = { ...store.selectedQuantity() };
      delete items[remissionItemId];
      delete quantities[remissionItemId];
      patchState(store, {
        selectedItems: items,
        selectedQuantity: quantities,
      });
    },

    /**
     * Clears all selected remission items.
     * Resets the remission state to its initial values.
     *
     * @example
     * ```typescript
     * remissionStore.clearSelectedItems();
     * ```
     */
    clearSelectedItems() {
      patchState(store, {
        selectedItems: {},
      });
    },

    /**
     * Clears the remission store state, resetting all values to their initial state.
     * This is useful for starting a new remission process or clearing the current state.
     *
     * @example
     * ```typescript
     * remissionStore.clearState();
     * ```
     */
    clearState() {
      patchState(store, initialState);
      store.saveToStorage();
    },
  })),
);
