import { patchState, signalStore, withMethods } from '@ngrx/signals';
import { withEntities, updateEntity, addEntity } from '@ngrx/signals/entities';
import { inject } from '@angular/core';
import { ReceiptItemTaskListItem } from '../models';
import { AsyncResult, AsyncResultStatus } from '@isa/common/data-access';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { ReturnTaskListService } from '../services';
import { pipe, switchMap, tap } from 'rxjs';
import { tapResponse } from '@ngrx/operators';
import { logger } from '@isa/core/logging';
import { QueryTokenInput } from '../schemas';

/**
 * Represents the structure of a return task list item in the store.
 * Extends the AsyncResult type with an ID field to uniquely identify each entity.
 */
export type ReturnTaskListItems = AsyncResult<
  ReceiptItemTaskListItem[] | undefined
> & {
  /** The unique identifier for the entity, matching the processId */
  id: number;
};

/**
 * Default initial state for a new ReturnTaskListItems entity.
 * Sets up the entity with undefined data and an Idle status.
 */
const initialEntity: Omit<ReturnTaskListItems, 'id'> = {
  data: undefined,
  status: AsyncResultStatus.Idle,
};

/**
 * Signal store for managing return task list items.
 * Uses NgRx Signals with entity support to track the async state
 * of receipt item tasks for different processes.
 */
export const ReturnTaskListStore = signalStore(
  { providedIn: 'root' },
  withEntities<ReturnTaskListItems>(),
  withMethods((store, logService = logger()) => ({
    /**
     * Prepares the store for a data fetch operation for a specific process.
     * Creates a new entity if one doesn't exist, or updates an existing entity's status to Pending.
     *
     * @param id - The process ID to prepare for fetching
     */
    beforeFetch(id: number) {
      let entity: ReturnTaskListItems | undefined = store.entityMap()?.[id];
      if (!entity) {
        entity = {
          ...initialEntity,
          id,
          status: AsyncResultStatus.Pending,
        };
        patchState(store, addEntity(entity));
      } else {
        patchState(
          store,
          updateEntity({
            id,
            changes: { status: AsyncResultStatus.Pending },
          }),
        );
      }
    },

    /**
     * Updates the store with successfully fetched task list items.
     * Sets the entity status to Success and stores the retrieved data.
     *
     * @param id - The process ID to update
     * @param data - The fetched receipt item task list items
     */
    fetchSuccess(id: number, data: ReceiptItemTaskListItem[]) {
      patchState(
        store,
        updateEntity({
          id,
          changes: { data, status: AsyncResultStatus.Success },
        }),
      );
    },

    /**
     * Updates the store with error information when fetching fails.
     * Sets the entity status to Error and stores the error for reference.
     *
     * @param id - The process ID that encountered an error
     * @param error - The error that occurred during the fetch operation
     */
    fetchError(id: number, error: unknown) {
      logService.error('Error fetching task list items', error as Error);
      patchState(
        store,
        updateEntity({
          id,
          changes: { error, status: AsyncResultStatus.Error },
        }),
      );
    },
  })),
  withMethods(
    (store, returnTaskListService = inject(ReturnTaskListService)) => ({
      /**
       * Updates a single task list item within a process entity while preserving other properties.
       * This method finds the specific item by ID within the entity data array, merges the new
       * properties with the existing item, and updates the store state immutably.
       *
       * @param params - Object containing the parameters for the update
       * @param params.processId - The unique identifier for the process entity to update
       * @param params.taskListItem - The partial task list item with updated properties that
       *                              should be merged with the existing item. Must include an
       *                              `id` property to identify the target item.
       * @returns void - Returns early if the entity doesn't exist or the item isn't found
       *
       * @example
       * // Update the status of a specific task
       * returnTaskListStore.updateTaskListItem({
       *   processId: 123,
       *   taskListItem: { id: 456, status: 'completed' }
       * });
       */
      updateTaskListItem({
        processId,
        taskListItem,
      }: {
        processId: number;
        taskListItem: ReceiptItemTaskListItem;
      }) {
        const entity = store.entityMap()?.[processId];
        if (!entity?.data) return;

        const itemIndex = entity.data.findIndex(
          (item) => item.id === taskListItem.id,
        );
        if (itemIndex === -1) return;

        // Create a new data array with the target item's properties merged
        const updatedData = [...entity.data];
        updatedData[itemIndex] = {
          ...updatedData[itemIndex],
          ...taskListItem,
        };

        patchState(
          store,
          updateEntity({
            id: processId,
            changes: { data: updatedData },
          }),
        );
      },

      /**
       * Fetches task list items for a specific process ID.
       * This is an rxMethod that handles the entire fetch lifecycle:
       * - Updates store status to Pending before fetching
       * - Calls the ReturnTaskListService to query receipt item tasks
       * - Updates the store with either success or error status based on the result
       *
       * @param params - Object containing the processId to fetch tasks for
       * @returns An Observable that completes when the fetch operation is done
       */
      fetchTaskListItems: rxMethod<{
        processId: number;
        queryToken: QueryTokenInput;
      }>(
        pipe(
          tap(({ processId }) => store.beforeFetch(processId)),
          switchMap(({ processId, queryToken }) =>
            returnTaskListService.queryReceiptItemTasks(queryToken).pipe(
              tapResponse({
                next(value) {
                  store.fetchSuccess(processId, value);
                },
                error(error) {
                  store.fetchError(processId, error);
                },
              }),
            ),
          ),
        ),
      ),
    }),
  ),
);
