import {
  ChangeDetectionStrategy,
  Component,
  inject,
  computed,
  effect,
  untracked,
  signal,
  linkedSignal,
} from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import {
  provideFilter,
  withQuerySettingsFactory,
  withQueryParamsSync,
  FilterControlsPanelComponent,
  FilterService,
  SearchTrigger,
} from '@isa/shared/filter';
import {
  injectRestoreScrollPosition,
  ScrollTopButtonComponent,
} from '@isa/utils/scroll-position';
import { RemissionStartCardComponent } from './remission-start-card/remission-start-card.component';
import { RemissionListSelectComponent } from './remission-list-select/remission-list-select.component';
import {
  createRemissionInStockResource,
  createRemissionListResource,
  createRemissionProductGroupResource,
} from './resources';
import { injectRemissionListType } from './injects/inject-remission-list-type';
import { RemissionListItemComponent } from './remission-list-item/remission-list-item.component';
import {
  IconButtonComponent,
  StatefulButtonComponent,
  StatefulButtonState,
} from '@isa/ui/buttons';
import {
  ReturnItem,
  StockInfo,
  ReturnSuggestion,
  RemissionStore,
  RemissionItem,
  calculateAvailableStock,
  RemissionReturnReceiptService,
  getStockToRemit,
  RemissionListType,
  RemissionResponseArgsErrorMessage,
  UpdateItem,
  orderByListItems,
  getItemType,
} from '@isa/remission/data-access';
import { injectDialog, injectFeedbackErrorDialog } from '@isa/ui/dialog';
import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog';
import { RemissionReturnCardComponent } from './remission-return-card/remission-return-card.component';
import { logger } from '@isa/core/logging';
import { RemissionListDepartmentElementsComponent } from './remission-list-department-elements/remission-list-department-elements.component';
import { injectTabId } from '@isa/core/tabs';
import { RemissionListEmptyStateComponent } from './remission-list-empty-state/remission-list-empty-state.component';
import { firstValueFrom } from 'rxjs';

function querySettingsFactory() {
  return inject(ActivatedRoute).snapshot.data['querySettings'];
}

/**
 * RemissionListComponent
 *
 * Displays and manages a list of remission items with filtering and stock information.
 * Implements local state using Angular signals and computed properties.
 * Follows SOLID and Clean Code principles for maintainability and testability.
 *
 * @remarks
 * - Uses OnPush change detection for performance.
 * - All state is managed locally via signals.
 * - Filtering is handled via FilterService.
 * - Stock information is dynamically loaded for visible items.
 *
 * @see {@link https://angular.dev/style-guide} for Angular best practices.
 */
@Component({
  selector: 'remi-feature-remission-list',
  templateUrl: './remission-list.component.html',
  styleUrl: './remission-list.component.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    provideFilter(
      withQuerySettingsFactory(querySettingsFactory),
      withQueryParamsSync(),
    ),
  ],
  imports: [
    RemissionStartCardComponent,
    RemissionReturnCardComponent,
    FilterControlsPanelComponent,
    RemissionListSelectComponent,
    RemissionListItemComponent,
    IconButtonComponent,
    StatefulButtonComponent,
    RemissionListDepartmentElementsComponent,
    RemissionListEmptyStateComponent,
    ScrollTopButtonComponent,
  ],
  host: {
    '[class]':
      '"w-full flex flex-col gap-4 mt-5 isa-desktop:mt-6 overflow-x-hidden"',
  },
})
export class RemissionListComponent {
  searchTrigger = signal<SearchTrigger | 'reload' | 'initial'>('initial');

  /**
   * Activated route instance for accessing route data and params.
   */
  route = inject(ActivatedRoute);

  /**
   * Router instance for navigation.
   */
  router = inject(Router);

  /**
   * Injects the current activated tab ID as a signal.
   * This is used to determine if the current remission matches the active tab.
   */
  activatedTabId = injectTabId();

  searchItemToRemitDialog = injectDialog(SearchItemToRemitDialogComponent);
  errorDialog = injectFeedbackErrorDialog();

  /**
   * FilterService instance for managing filter state and queries.
   * @private
   */
  #filterService = inject(FilterService);

  /**
   * RemissionSelectionStore instance for managing remission selection state.
   * @private
   */
  #store = inject(RemissionStore);

  /**
   * RemissionReturnReceiptService instance for handling return receipt operations.
   * @private
   */
  #remissionReturnReceiptService = inject(RemissionReturnReceiptService);

  /**
   * Logger instance for logging component events and errors.
   * @private
   */
  #logger = logger(() => ({
    component: 'RemissionListComponent',
  }));

  /**
   * Restores scroll position when navigating back to this component.
   */
  restoreScrollPosition = injectRestoreScrollPosition();

  /**
   * Signal representing the currently selected remission list type.
   */
  selectedRemissionListType = injectRemissionListType();

  /**
   * Computed signal indicating whether the remission list type is 'Abteilung'.
   * @returns True if the selected type is Abteilung, false otherwise.
   */
  isDepartment = computed(() => {
    return this.selectedRemissionListType() === RemissionListType.Abteilung;
  });

  /**
   * Signal indicating whether a remission list item deletion is in progress.
   * Used to disable actions while deletion is happening.
   */
  removeItemInProgress = signal(false);

  /**
   * Computed signal for the current search term from the filter service.
   * @returns The current search term string or undefined if not set.
   */
  searchTerm = computed<string | undefined>(() => {
    return this.#filterService.query()?.input['qs'] ?? '';
  });

  /**
   * Computed signal indicating whether there is a valid search term.
   * A valid search term is defined as a non-empty string.
   * @returns True if there is a valid search term, false otherwise.
   */
  hasValidSearchTerm = computed(() => {
    const searchTerm = this.searchTerm();
    return !!searchTerm && searchTerm.length > 0;
  });

  /**
   * Resource signal for fetching the remission list based on current filters.
   * @returns Remission list resource state.
   */
  remissionResource = createRemissionListResource(() => {
    return {
      remissionListType: this.selectedRemissionListType(),
      queryToken: this.#filterService.query(),
      searchTrigger: this.searchTrigger(),
    };
  });

  /**
   * Resource signal for fetching stock information for the current remission items.
   * Updates when the list of items changes.
   * @returns Stock info resource state.
   */
  inStockResource = createRemissionInStockResource(() => {
    return {
      itemIds: this.items()
        .map((item) => item?.product?.catalogProductNumber)
        .filter(
          (catalogProductNumber): catalogProductNumber is string =>
            typeof catalogProductNumber === 'string',
        ),
    };
  });

  /**
   * Resource signal for fetching product group information based on current remission items.
   * Updates when the remission list changes.
   * @returns Product group resource state.
   */
  productGroupResource = createRemissionProductGroupResource();

  /**
   * Computed signal for the current remission list response.
   * @returns The latest remission list response or undefined.
   */
  listResponseValue = computed(() => this.remissionResource.value());

  /**
   * Computed signal indicating whether the remission list resource is currently fetching data.
   * @returns True if fetching, false otherwise.
   */
  listFetching = computed(() => this.remissionResource.status() === 'loading');

  /**
   * Computed signal for the current in-stock response.
   * @returns Array of StockInfo or undefined.
   */
  inStockResponseValue = computed(() => this.inStockResource.value());

  /**
   * Computed signal indicating whether the in-stock resource is currently fetching data.
   * @returns True if fetching, false otherwise.
   */
  inStockFetching = computed(() => this.inStockResource.status() === 'loading');

  /**
   * Computed signal for the product group response.
   * @returns Array of KeyValueStringAndString or undefined.
   */
  productGroupResponseValue = computed(() => this.productGroupResource.value());

  /**
   * Computed signal for the remission items to display.
   * @returns Array of ReturnItem or ReturnSuggestion.
   */
  items = linkedSignal(() => {
    const value = this.listResponseValue();
    return value?.result ? value.result : [];
  });

  /**
   * Computed signal for the total number of hits in the remission list.
   * @returns Number of hits, or 0 if unavailable.
   */
  hits = computed(() => {
    const value = this.listResponseValue();
    return value?.hits ? value.hits : 0;
  });

  /**
   * Computed signal mapping item IDs to their StockInfo.
   * @returns Map of itemId to StockInfo.
   */
  stockInfoMap = computed(() => {
    const infos = this.inStockResponseValue() ?? [];
    return new Map(infos.map((info) => [info.itemId, info]));
  });

  /**
   * Computed signal for the current remission list type (Abteilung or Pflicht).
   * @returns The current RemissionListType.
   */
  remissionStarted = computed(() => this.#store.remissionStarted());

  /**
   * Computed signal indicating whether there are selected items in the remission store.
   * @returns True if there are selected items, false otherwise.
   */
  hasSelectedItems = computed(() => {
    return Object.keys(this.#store.selectedItems()).length > 0;
  });

  /**
   * Signal for the current remission list type.
   * @returns The current RemissionListType.
   */
  remitItemsState = signal<StatefulButtonState>('default');

  /**
   * Signal for any error messages related to remission items.
   * @returns Error message string or null if no error.
   */
  remitItemsError = signal<string | null>(null);

  /**
   * Signal indicating whether remission items are currently being processed.
   * @returns True if in progress, false otherwise.
   */
  remitItemsInProgress = signal(false);

  /**
   * Commits the current filter state and triggers a new search.
   *
   * @param trigger - The type of search trigger that initiated this search.
   *                  Used to track user interaction patterns and optimize search behavior.
   */
  search(trigger: SearchTrigger): void {
    this.#filterService.commit();
    this.searchTrigger.set(trigger);
  }

  /**
   * Retrieves the StockInfo for a given item.
   * @param item - The ReturnItem or ReturnSuggestion to look up.
   * @returns The StockInfo for the item, or undefined if not found.
   */
  getStockForItem(item: RemissionItem): StockInfo | undefined {
    return this.stockInfoMap().get(Number(item?.product?.catalogProductNumber));
  }

  /**
   * Retrieves the available stock for a given item.
   * @param item - The ReturnItem or ReturnSuggestion to look up.
   * @returns The available stock as a number, or 0 if not found.
   */
  getAvailableStockForItem(item: RemissionItem): number {
    const stockInfo = this.stockInfoMap().get(
      Number(item?.product?.catalogProductNumber),
    );
    return calculateAvailableStock({
      stock: stockInfo?.inStock,
      removedFromStock: stockInfo?.removedFromStock,
    });
  }

  /**
   * Retrieves the product group value for a given item.
   * @param item - The ReturnItem or ReturnSuggestion to look up.
   * @returns The product group value as a string, or undefined if not found.
   */
  getProductGroupValueForItem(
    item: ReturnItem | ReturnSuggestion,
  ): string | undefined {
    const productGroup = this.productGroupResponseValue()?.find(
      (group) => group.key === item?.product?.productGroup,
    );
    return productGroup ? productGroup.value : '';
  }

  /**
   * Handles the removal or update of an item from the remission list.
   * Updates the local items signal and the remission store accordingly.
   * Items with impediments are automatically moved to the end of the list and sorted by attempt count.
   * @param param0 - Object containing inProgress state, itemId, and optional impediment.
   */
  onRemoveOrUpdateItem({ inProgress, itemId, impediment }: UpdateItem) {
    this.removeItemInProgress.set(inProgress);
    if (!inProgress && itemId) {
      if (!impediment || (impediment.attempts && impediment.attempts >= 4)) {
        this.items.set(this.items().filter((item) => item.id !== itemId)); // Filter Item if no impediment or attempts >= 4 (#5361)
      } else {
        // Update Item
        this.items.update((items) => {
          const updatedItems = items.map((item) =>
            item.id === itemId ? { ...item, impediment } : item,
          );
          orderByListItems(updatedItems);
          return updatedItems;
        });
      }

      // Always Unselect Item
      this.#store.removeItem(itemId);
    }
  }

  /**
   * Computed signal that determines if the current search was triggered by user interaction.
   * Returns true for user-initiated actions (input, filter changes, sort changes, scanning)
   * and false for automatic/system-initiated searches (reload, initial load).
   *
   * @returns True if search was user-initiated, false for system-initiated searches
   */
  searchTriggeredByUser = computed(() => {
    const trigger = this.searchTrigger();
    return (
      trigger === 'input' ||
      trigger === 'filter' ||
      trigger === 'order-by' ||
      trigger === 'scan'
    );
  });

  /**
   * Effect that handles scenarios where a search yields no results.
   * If the search was user-initiated and returned no hits, it opens a dialog
   * to allow the user to add a new item to remit.
   * If only one hit is found and a remission is started, it selects that item automatically.
   * This effect runs whenever the remission or stock resource status changes,
   * or when the search term changes.
   * It ensures that the user is prompted appropriately based on their actions and the current state of the remission process.
   * It also checks if the remission is started or if the list type is 'Abteilung' to determine navigation behavior.
   * @see {@link
   * https://angular.dev/guide/effects} for more information on Angular effects.
   * @remarks This effect uses `untracked` to avoid unnecessary re-evaluations
   * when accessing certain signals.
   */
  emptySearchResultEffect = effect(() => {
    const status = this.remissionResource.status();
    const stockStatus = this.inStockResource.status();
    const searchTerm: string | undefined = this.searchTerm();

    if (status !== 'resolved' || stockStatus !== 'resolved') {
      return;
    }

    untracked(() => {
      const hits = this.hits();

      // #5338 - Select item automatically if only one hit after search
      if (
        !!hits ||
        !searchTerm ||
        !this.hasValidSearchTerm() ||
        !this.searchTriggeredByUser()
      ) {
        if (hits === 1 && this.remissionStarted()) {
          this.#store.clearSelectedItems();
          this.preselectRemissionItem(this.items()[0]);
        }

        return;
      }

      this.searchItemToRemitDialog({
        data: {
          searchTerm,
        },
      }).closed.subscribe(async (result) => {
        this.#store.clearSelectedItems();
        if (result) {
          if (this.remissionStarted()) {
            for (const item of result) {
              if (item?.id) {
                this.#store.selectRemissionItem(item.id, item);
              }
            }
            await this.remitItems({ addItemFlow: true });
          } else if (this.isDepartment()) {
            return await this.navigateToDefaultRemissionList();
          }
        }
        this.reloadListAndReturnData();
      });
    });
  });

  // TODO: Improvement - In Separate Komponente zusammen mit Remi-Button Auslagern
  /**
   * Initiates the process to remit selected items.
   * If remission is already started, items are added directly to the remission.
   * If not, navigates to the default remission list.
   * @param options - Options for remitting items, including whether it's part of an add-item flow.
   * @returns A promise that resolves when the operation is complete.
   */
  async remitItems(options: { addItemFlow: boolean } = { addItemFlow: false }) {
    if (this.remitItemsInProgress()) {
      return;
    }
    this.remitItemsInProgress.set(true);

    try {
      // #5273, #5280 Fix - Bei gestarteter Remission dürfen Items die über den AddItemDialog hinzugefügt und direkt remittiert werden, nur als ReturnItem (statt ReturnSuggestion) zum WBS hinzugefügt werden
      const remissionListType = options.addItemFlow
        ? RemissionListType.Pflicht
        : this.selectedRemissionListType();

      const selected = this.#store.selectedItems();
      const quantities = this.#store.selectedQuantity();

      for (const [remissionItemId, item] of Object.entries(selected)) {
        const returnId = this.#store.returnId();
        const receiptId = this.#store.receiptId();
        const remissionItemIdNumber = Number(remissionItemId);
        const quantity = quantities[remissionItemIdNumber];
        const inStock = this.getAvailableStockForItem(item);
        const stockToRemit = getStockToRemit({
          remissionItem: item,
          remissionListType,
          availableStock: inStock,
        });
        const quantityToRemit = quantity ?? stockToRemit;

        if (returnId && receiptId) {
          await this.#remissionReturnReceiptService.remitItem({
            itemId: remissionItemIdNumber,
            addItem: {
              returnId,
              receiptId,
              quantity: quantityToRemit,
              inStock,
              impedimentComment: stockToRemit > quantity ? 'Restmenge' : '',
              remainingQuantity:
                isNaN(quantity) || inStock - quantity <= 0
                  ? undefined
                  : inStock - quantity,
            },
            type: getItemType(item, remissionListType),
          });
        }
      }
      this.remitItemsState.set('success');
      this.reloadListAndReturnData();
    } catch (error) {
      await this.handleRemitItemsError(error);
    }

    this.#store.clearSelectedItems();
    this.remitItemsInProgress.set(false);
  }

  /**
   * Reloads the remission list and return data.
   * This method is used to refresh the displayed data after changes.
   */
  reloadListAndReturnData() {
    this.searchTrigger.set('reload');
    this.remissionResource.reload();
    this.#store.reloadReturn();
  }

  /**
   * Pre-Selects a remission item if it has available stock and can be remitted.
   * Updates the remission store with the selected item.
   * @param item - The ReturnItem or ReturnSuggestion to select.
   * @returns void
   */
  preselectRemissionItem(item: RemissionItem) {
    if (!!item && item.id) {
      const inStock = this.getAvailableStockForItem(item);
      const stockToRemit = getStockToRemit({
        remissionItem: item,
        remissionListType: this.selectedRemissionListType(),
        availableStock: inStock,
      });

      if (inStock > 0 && stockToRemit > 0) {
        this.#store.selectRemissionItem(item.id, item);
      }
    }
  }

  /**
   *  Handles errors that occur during the remission of items.
   * Logs the error, displays an error dialog, and reloads the list and return data.
   * If the error indicates that the remission is already completed, it clears the remission state.
   * Sets the stateful button to 'error' to indicate the failure.
   * @param error - The error object caught during the remission process.
   * @returns A promise that resolves when the error handling is complete.
   */
  async handleRemitItemsError(error: any) {
    this.#logger.error('Failed to remit items', error);

    const errorMessage =
      error?.error?.message ??
      error?.message ??
      'Artikel konnten nicht remittiert werden';

    this.remitItemsError.set(errorMessage);

    await firstValueFrom(
      this.errorDialog({
        data: {
          errorMessage,
        },
      }).closed,
    );

    if (errorMessage === RemissionResponseArgsErrorMessage.AlreadyCompleted) {
      this.#store.clearState();
    }

    this.reloadListAndReturnData();

    this.remitItemsState.set('error'); // Stateful-Button auf Error setzen
  }

  /**
   * Navigates to the default remission list based on the current activated tab ID.
   * This method is used to redirect the user to the remission list after completing or starting a remission.
   * @returns {Promise<void>} A promise that resolves when navigation is complete
   */
  async navigateToDefaultRemissionList() {
    await this.router.navigate(['/', this.activatedTabId(), 'remission']);
  }
}
