import { ChangeDetectionStrategy, Component, DestroyRef, HostListener, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { A11yModule } from '@angular/cdk/a11y';
import { combineLatest } from 'rxjs';
import { Subscription } from 'rxjs';
import { Category } from '../../../../core/categories/models/category-domain.model';
import { CatalogLayoutMode } from '../../../../core/products/models/catalog-experience.model';
import { Product } from '../../../../core/products/models/product-domain.model';
import { ConfigService } from '../../../../core/config/config.service';
import { FeatureConfigService } from '../../../../core/config/feature-config.service';
import { CategoryFacade } from '../../../../facades/platform/category.facade';
import { ProductFacade } from '../../../../facades/platform/product.facade';
import { SearchFacade } from '../../../../facades/platform/search.facade';
import { UserExperienceFacade } from '../../../../facades/platform/user-experience.facade';
import { CartService } from '../../../../services';
import { LanguageService } from '../../../../services/language.service';
import { PrefetchService } from '../../../../services/prefetch.service';
import { LocalStorageService } from '../../../../core/storage/local-storage.service';
import { TranslatePipe } from '../../../../i18n/translate.pipe';
import { TranslateService } from '../../../../i18n/translate.service';
import { DEFAULT_CATALOG_CONFIG, DEFAULT_USER_EXPERIENCE_CONFIG } from '../../../../shared/models/config';
import { FilterGroup, SearchFilterState, SearchQuery, SearchResult, SearchSuggestion, SortOption } from '../../../../core/search/models/search.model';
import { SearchState } from '../../../../core/search/models/search-state.model';
import { CatalogCategoryGridComponent } from '../components/category-grid/category-grid.component';
import { CatalogEmptyStateComponent } from '../components/catalog-empty-state/catalog-empty-state.component';
import { CatalogFiltersPanelComponent } from '../components/filters-panel/filters-panel.component';
import { CatalogLayoutSwitcherComponent } from '../components/layout-switcher/layout-switcher.component';
import { CatalogSearchBoxComponent } from '../components/search-box/search-box.component';
import { CatalogSearchResultsComponent } from '../components/search-results/search-results.component';
import { CatalogSortingControlComponent } from '../components/sorting-control/sorting-control.component';
import { EmptyStateComponent } from '../../../../shared/ui/empty-state/empty-state.component';
import { SkeletonComponent } from '../../../../shared/ui/skeleton/skeleton.component';
import { ButtonComponent } from '../../../../shared/ui/button/button.component';
import { IconComponent } from '../../../../shared/ui/icon/icon.component';
import { QuickViewDialogComponent } from '../../product/components/quick-view-dialog/quick-view-dialog.component';
import { BreadcrumbComponent, BreadcrumbItem } from '../../../../shared/ui/breadcrumb/breadcrumb.component';
import { CatalogState, createInitialCatalogState } from '../models/catalog-state.model';
import { ProductShareService } from '../../user-experience/services/product-share.service';
import { UserNotificationService } from '../../user-experience/services/user-notification.service';
import { SearchSuggestion as SearchSuggestionItem } from '../../../../features/search/models/search.model';

type CatalogViewMode = 'categories' | 'products';
type CatalogLoadingStrategy = 'pagination' | 'loadMore' | 'infiniteScroll';

@Component({
  selector: 'app-catalog-container',
  standalone: true,
  imports: [
    A11yModule,
    TranslatePipe,
    CatalogEmptyStateComponent,
    CatalogSearchBoxComponent,
    CatalogFiltersPanelComponent,
    CatalogSortingControlComponent,
    CatalogLayoutSwitcherComponent,
    CatalogSearchResultsComponent,
    CatalogCategoryGridComponent,
    EmptyStateComponent,
    SkeletonComponent,
    ButtonComponent,
    IconComponent,
    QuickViewDialogComponent,
    BreadcrumbComponent
  ],
  templateUrl: './catalog-container.component.html',
  styleUrls: ['./catalog-container.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CatalogContainerComponent {
  private readonly route = inject(ActivatedRoute);
  private readonly router = inject(Router);
  private readonly destroyRef = inject(DestroyRef);
  private readonly configService = inject(ConfigService);
  private readonly categoryFacade = inject(CategoryFacade);
  private readonly productFacade = inject(ProductFacade);
  private readonly searchFacade = inject(SearchFacade);
  private readonly featureConfig = inject(FeatureConfigService);
  private readonly cartService = inject(CartService);
  private readonly prefetchService = inject(PrefetchService);
  private readonly languageService = inject(LanguageService);
  private readonly uxFacade = inject(UserExperienceFacade);
  private readonly shareService = inject(ProductShareService);
  private readonly notifications = inject(UserNotificationService);
  private readonly translate = inject(TranslateService);
  private readonly storage = inject(LocalStorageService);

  readonly catalogConfig = signal(this.resolveCatalogConfig());
  readonly userExperienceConfig = signal(this.resolveUserExperienceConfig());
  readonly features = this.featureConfig.features;

  readonly state = signal<CatalogState>(createInitialCatalogState());
  readonly categories = signal<Category[]>([]);
  readonly rawProducts = signal<Product[]>([]);
  readonly products = signal<Product[]>([]);
  readonly subcategoryChips = signal<Category[]>([]);
  readonly breadcrumb = signal<Category[]>([]);
  readonly searchSummary = signal('');
  readonly searchResult = signal<SearchResult<Product> | null>(null);
  readonly filterDefinitions = signal<FilterGroup[]>([]);
  readonly filterState = signal<SearchFilterState>({ values: {}, ranges: {}, toggles: {} });
  readonly searchSuggestions = signal<SearchSuggestion[]>([]);
  readonly recentSearches = signal<string[]>([]);
  readonly popularSearches = signal<string[]>([]);
  readonly searchHistory = signal<string[]>([]);
  readonly savedSearches = this.uxFacade.savedSearches;
  readonly viewMode = signal<CatalogViewMode>('categories');
  readonly loading = signal(true);
  readonly loadingProducts = signal(false);
  readonly error = signal<string | null>(null);
  readonly online = signal(typeof navigator === 'undefined' ? true : navigator.onLine);
  readonly viewportWidth = signal(typeof window !== 'undefined' ? window.innerWidth : 1280);
  readonly filterDrawerOpen = signal(false);
  readonly sortSheetOpen = signal(false);
  readonly gridSheetOpen = signal(false);
  readonly noResults = computed(() => !this.loadingProducts() && this.viewMode() === 'products' && this.products().length === 0);
  readonly minSearchLength = 3;
  readonly isQueryTooShort = computed(() => {
    const length = this.state().search.trim().length;
    return length > 0 && length < this.minSearchLength;
  });
  readonly offlineState = computed(() => !this.online());
  readonly isDrawerLayout = computed(() => this.viewportWidth() <= 1024);
  readonly isMobile = computed(() => this.viewportWidth() <= 767);
  readonly hasRawProducts = computed(() => this.rawProducts().length > 0);
  // A blank search query with zero raw results is a "nothing found" search result, not an
  // empty category — showing category-navigation copy ("browse categories" / "go to parent
  // category") here is misleading when the emptiness is caused by the user's search term.
  readonly isEmptyCategoryState = computed(() => this.viewMode() === 'products' && !this.loadingProducts() && this.rawProducts().length === 0 && this.subcategoryChips().length === 0 && !this.state().search.trim());
  readonly isFilteredEmptyState = computed(() => this.viewMode() === 'products' && !this.loadingProducts() && this.rawProducts().length > 0 && this.products().length === 0);
  readonly showDesktopFilters = computed(() => this.viewMode() === 'products' && !this.isEmptyCategoryState() && !this.isDrawerLayout() && !this.offlineState());
  readonly showMobileToolbar = computed(() => this.viewMode() === 'products' && !this.isEmptyCategoryState() && this.isMobile() && !this.offlineState());
  readonly showCatalogTools = computed(() => this.viewMode() === 'products' && this.products().length > 0 && !this.loadingProducts() && !this.offlineState());
  readonly loadingStrategy = computed<CatalogLoadingStrategy>(() => this.catalogConfig().loadingStrategy ?? 'pagination');
  readonly currentColumnsLabel = computed(() => this.layoutLabelKey(this.state().layout));
  readonly popularCategorySuggestions = computed(() => {
    const sources = [...this.subcategoryChips(), ...this.categories()]
      .slice(0, 8)
      .map(category => ({ id: category.id, label: category.title }));

    const byId = new Map<string | number, { id: string | number; label: string }>();
    for (const entry of sources) {
      if (!byId.has(entry.id)) {
        byId.set(entry.id, entry);
      }
    }

    return [...byId.values()];
  });
  readonly recommendedProductsForEmpty = computed(() => this.rawProducts().slice(0, 4));
  readonly mobileSortOptions = computed(() => {
    const allowed = new Set(['relevance', 'latest', 'price_asc', 'price_desc', 'rating', 'popular']);
    const options = this.sortDefinitions().filter(option => allowed.has(option.id));
    return options.length > 0 ? options : this.sortDefinitions();
  });
  readonly favoriteIds = computed(() => this.uxFacade.wishlist().map(item => item.product.itemID));
  readonly comparedIds = computed(() => this.uxFacade.comparedProducts().map(item => item.product.itemID));

  readonly sortDefinitions = computed<SortOption[]>(() => this.searchFacade.createSortOptions(this.catalogConfig().availableSorts));

  readonly availableLayouts: CatalogLayoutMode[] = ['grid-2', 'grid-3', 'grid-4', 'list', 'compact'];

  readonly skeletonSlots = Array.from({ length: 8 });

  private dataSubscription?: Subscription;
  private readonly backendFetchSize = 200;
  private pendingScrollY: number | null = null;
  private restoringScrollFromUrlSync = false;
  private syncingUrl = false;

  constructor() {
    effect(() => {
      this.searchSuggestions.set(this.searchFacade.state().suggestions);
    });

    effect(() => {
      const saved = this.storage.getItem('catalog.layout.preference');
      if (!saved) {
        return;
      }

      this.state.update(current => ({ ...current, layout: this.normalizeLayout(saved as CatalogLayoutMode) }));
    });

    this.destroyRef.onDestroy(() => this.dataSubscription?.unsubscribe());
    if (this.features().searchHistory) {
      const snapshot = this.searchFacade.getSearchHistory();
      this.searchHistory.set(snapshot.items);
      this.recentSearches.set(snapshot.recent);
      this.popularSearches.set(snapshot.popular);
    } else {
      this.popularSearches.set(this.searchFacade.popularSearches.map(item => item.title));
    }

    combineLatest([this.route.paramMap, this.route.queryParamMap])
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(([params, queryParams]) => {
        if (this.syncingUrl) {
          return;
        }

        const categoryToken = params.get('id');
        const queryPatch = this.searchFacade.fromQueryParams(queryParams);
        const hasQueryState = this.hasSearchState(queryPatch);

        this.resolveCategoryId(categoryToken).then(categoryId => {
          this.enterCategory(categoryId, queryPatch, hasQueryState);
        });
      });
  }

  enterCategory(categoryId: number | null, queryPatch?: Partial<SearchState>, forceProductsMode = false): void {
    this.dataSubscription?.unsubscribe();
    this.loading.set(true);
    this.loadingProducts.set(false);
    this.error.set(null);
    this.categories.set([]);
    this.rawProducts.set([]);
    this.products.set([]);
    this.breadcrumb.set([]);
    this.subcategoryChips.set([]);
    this.searchSummary.set('');
    this.searchResult.set(null);
    this.filterDefinitions.set([]);
    this.filterState.set({ values: {}, ranges: {}, toggles: {} });
    this.searchSuggestions.set([]);
    this.categoryFacade.selectCategory(categoryId);
    this.state.set({
      ...createInitialCatalogState(),
      sort: this.catalogConfig().defaultSort,
      layout: this.resolveInitialLayout(),
      filters: {
        ...createInitialCatalogState().filters,
        categoryIds: categoryId == null ? [] : [categoryId],
      },
    });

    this.applySearchStatePatch(queryPatch);

    if (categoryId == null && !forceProductsMode) {
      this.dataSubscription = this.categoryFacade.getRootCategories()
        .subscribe({
          next: categories => this.renderCategories(null, categories, []),
          error: () => this.setError('catalog.error'),
        });
      return;
    }

    if (categoryId == null && forceProductsMode) {
      this.viewMode.set('products');
      this.loadCatalog();
      return;
    }

    const selectedCategoryId = categoryId as number;

    this.dataSubscription = combineLatest([
      this.categoryFacade.getCategoryById(selectedCategoryId),
      this.categoryFacade.getChildren(selectedCategoryId),
      this.categoryFacade.getBreadcrumb(selectedCategoryId),
    ]).subscribe({
        next: ([category, children, breadcrumb]) => {
          this.breadcrumb.set(breadcrumb);
          this.subcategoryChips.set(children);
          this.state.update(current => ({ ...current, category: category ?? null }));

          if (children.length > 0) {
            this.renderCategories(category ?? null, children, breadcrumb);
            return;
          }

          this.loadProducts(selectedCategoryId);
        },
        error: () => this.setError('catalog.error'),
      });
  }

  readonly breadcrumbItems = computed<BreadcrumbItem[]>(() =>
    this.breadcrumb().map(category => ({ id: category.id, label: category.title }))
  );

  onBreadcrumbItemClick(item: BreadcrumbItem): void {
    const category = this.breadcrumb().find(c => c.id === item.id);
    if (category) {
      this.selectCategory(category);
    }
  }

  selectCategory(category: Category): void {
    this.router.navigate([`/${this.languageService.currentLanguage()}/catalog`, category.id]);
  }

  selectProduct(product: Product): void {
    this.router.navigate([`/${this.languageService.currentLanguage()}/product`, product.itemID]);
  }

  addToCart(payload: { product: Product; event: Event }): void {
    payload.event.preventDefault();
    payload.event.stopPropagation();
    this.cartService.addItem(payload.product.itemID);
  }

  previewProduct(productId: number): void {
    this.prefetchService.prefetchItem(productId);
  }

  readonly quickViewProduct = signal<Product | null>(null);
  readonly quickViewLoading = signal(false);

  private quickViewRequestId = 0;

  openQuickView(productId: number): void {
    this.quickViewProduct.set(null);
    this.quickViewLoading.set(true);
    const requestId = ++this.quickViewRequestId;
    this.productFacade.getProduct(productId).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
      next: product => {
        // A faster later click may have already resolved and changed
        // quickViewRequestId - discard this response if so, rather than
        // showing the wrong product in the still-open dialog.
        if (requestId !== this.quickViewRequestId) return;
        this.quickViewProduct.set(product);
        this.quickViewLoading.set(false);
      },
      error: () => {
        if (requestId !== this.quickViewRequestId) return;
        this.quickViewLoading.set(false);
      }
    });
  }

  closeQuickView(): void {
    this.quickViewProduct.set(null);
    this.quickViewLoading.set(false);
  }

  addQuickViewToCart(product: Product): void {
    this.cartService.addItem(product.itemID);
    this.closeQuickView();
  }

  retry(): void {
    this.enterCategory(this.state().category?.id ?? null);
  }

  onSearchQueryChange(query: string): void {
    this.state.update(current => ({ ...current, search: query }));
    this.persistContinueBrowsing();

    if (!this.features().searchHistory) {
      this.searchSuggestions.set([]);
      return;
    }

    this.searchFacade.autocomplete(query, this.rawProducts());
  }

  submitSearch(query: string): void {
    const normalized = query.trim();
    this.viewMode.set('products');
    this.state.update(current => ({
      ...current,
      search: normalized,
      pagination: {
        ...current.pagination,
        page: 1,
        skip: 0
      }
    }));

    if (this.features().searchHistory && normalized.length > 0) {
      const snapshot = this.searchFacade.pushSearchHistory(normalized);
      this.searchHistory.set(snapshot.items);
      this.recentSearches.set(snapshot.recent);
      this.popularSearches.set(snapshot.popular);
    }

    this.syncUrlFromState();
    this.persistContinueBrowsing();
    this.loadCatalog();
  }

  useSuggestion(suggestion: SearchSuggestionItem): void {
    // Prefer the canonical query text (e.g. popularSearches' target.query.q,
    // always the stable English term the backend index matches against) over
    // the display title, which may be translated for locales other than the
    // one the backend expects.
    const canonicalQuery = suggestion.target?.query?.['q'];
    const value = typeof canonicalQuery === 'string' ? canonicalQuery : suggestion.title;
    this.onSearchQueryChange(value);
    this.submitSearch(value);
  }

  useRecentSearch(value: string): void {
    this.onSearchQueryChange(value);
    this.submitSearch(value);
  }

  usePopularCategory(categoryId: string | number): void {
    const numeric = Number(categoryId);
    if (!Number.isFinite(numeric)) {
      return;
    }

    this.router.navigate([`/${this.languageService.currentLanguage()}/catalog`, numeric]);
  }

  clearSearchHistory(): void {
    const snapshot = this.searchFacade.clearSearchHistory();
    this.searchHistory.set(snapshot.items);
    this.recentSearches.set(snapshot.recent);
    this.popularSearches.set(snapshot.popular);
  }

  addRecommendedToCart(payload: { product: Product; event: Event }): void {
    this.addToCart(payload);
  }

  onFavoriteToggled(product: Product): void {
    const result = this.uxFacade.toggleWishlist(product);
    this.notifications.show(
      result.added ? this.translate.t('ux.wishlistAdded') : this.translate.t('ux.wishlistRemoved'),
      result.added ? 'success' : 'info'
    );
  }

  onCompareToggled(product: Product): void {
    if (this.uxFacade.isInCompare(product.itemID)) {
      this.uxFacade.removeFromCompare(product.itemID);
      this.notifications.show(this.translate.t('ux.compareRemoved'), 'info');
      return;
    }

    const maxItems = this.userExperienceConfig().compare.maxItems;
    const result = this.uxFacade.addToCompare(product, maxItems);
    if (result.reason === 'limit') {
      this.notifications.show(this.translate.t('ux.compareLimitReached', { maxItems }), 'warning');
      return;
    }

    this.notifications.show(this.translate.t('ux.compareAdded'), 'success');
  }

  async onShareRequested(product: Product): Promise<void> {
    if (!this.userExperienceConfig().share.enabled) {
      return;
    }

    const lang = this.languageService.currentLanguage();
    const path = `/${lang}/product/${product.itemID}`;
    const productUrl = typeof window !== 'undefined'
      ? new URL(path, window.location.origin).toString()
      : path;

    const result = await this.shareService.shareProduct(product, productUrl);
    if (result === 'native') {
      this.notifications.show(this.translate.t('ux.productShared'), 'success');
      return;
    }

    if (result === 'copied') {
      this.notifications.show(this.translate.t('ux.productLinkCopied'), 'success');
      return;
    }

    this.notifications.show(this.translate.t('ux.sharingUnsupported'), 'warning');
  }

  saveCurrentSearch(): void {
    if (!this.userExperienceConfig().savedSearches.enabled) {
      return;
    }

    const query = this.state().search.trim();
    if (!query.length) {
      this.notifications.show(this.translate.t('ux.enterQueryBeforeSave'), 'warning');
      return;
    }

    const categoryId = this.state().filters.categoryIds[0];
    const saved = this.uxFacade.saveSearch({
      name: query,
      query,
      sort: this.state().sort,
      categoryId,
      filters: this.filterState()
    }, this.userExperienceConfig().savedSearches.maxItems);

    this.notifications.show(this.translate.t('ux.savedSearchNamed', { name: saved.name }), 'success');
  }

  useSavedSearch(id: string): void {
    const target = this.savedSearches().find(item => item.id === id);
    if (!target) {
      return;
    }

    this.viewMode.set('products');
    this.state.update(current => ({
      ...current,
      search: target.query,
      sort: target.sort as CatalogState['sort'],
      pagination: {
        ...current.pagination,
        page: 1,
        skip: 0
      },
      filters: {
        ...current.filters,
        categoryIds: target.categoryId == null ? [] : [target.categoryId],
      }
    }));
    this.filterState.set(target.filters);
    this.syncUrlFromState();
    this.loadCatalog(target.categoryId);
  }

  removeSavedSearch(id: string): void {
    this.uxFacade.removeSavedSearch(id);
  }

  @HostListener('window:scroll')
  onWindowScroll(): void {
    this.persistContinueBrowsing();

    if (this.loadingStrategy() !== 'infiniteScroll' || this.loadingProducts() || !this.state().pagination.hasMore || this.viewMode() !== 'products') {
      return;
    }

    const scrollPosition = window.innerHeight + window.scrollY;
    const bottomPosition = document.documentElement.scrollHeight - 180;
    if (scrollPosition >= bottomPosition) {
      this.loadNextPage();
    }
  }

  @HostListener('window:resize')
  onWindowResize(): void {
    this.viewportWidth.set(typeof window !== 'undefined' ? window.innerWidth : 1280);
  }

  @HostListener('document:keydown.escape')
  onEscapeKey(): void {
    if (this.gridSheetOpen()) {
      this.closeGridSheet();
    }

    if (this.sortSheetOpen()) {
      this.closeSortSheet();
    }

    if (this.filterDrawerOpen()) {
      this.closeFilterDrawer();
    }
  }

  onFilterStateChange(next: SearchFilterState): void {
    this.filterState.set(next);
    this.state.update(current => ({
      ...current,
      pagination: {
        ...current.pagination,
        page: 1,
        skip: 0
      }
    }));

    this.recomputeResults();
    this.syncUrlFromState();
    this.persistContinueBrowsing();
  }

  resetFilters(): void {
    this.filterState.set({ values: {}, ranges: {}, toggles: {} });
    this.recomputeResults();
    this.syncUrlFromState();
    this.persistContinueBrowsing();
  }

  changeSort(sortId: string): void {
    this.state.update(current => ({
      ...current,
      sort: sortId as CatalogState['sort'],
      pagination: {
        ...current.pagination,
        page: 1,
        skip: 0
      }
    }));

    this.recomputeResults();
    this.syncUrlFromState();
    this.persistContinueBrowsing();
  }

  changeLayout(layout: CatalogLayoutMode): void {
    const normalized = this.normalizeLayout(layout);
    this.state.update(current => ({ ...current, layout: normalized }));
    this.storage.setItem('catalog.layout.preference', normalized);
    this.syncUrlFromState();
    this.persistContinueBrowsing();
  }

  cycleLayout(): void {
    const layouts = this.availableLayouts;
    const current = this.state().layout;
    const currentIndex = Math.max(0, layouts.indexOf(current));
    const nextLayout = layouts[(currentIndex + 1) % layouts.length];
    this.changeLayout(nextLayout);
  }

  openFilterDrawer(): void {
    this.closeSortSheet();
    this.closeGridSheet();
    this.filterDrawerOpen.set(true);
  }

  closeFilterDrawer(): void {
    this.filterDrawerOpen.set(false);
  }

  applyFilterDrawer(): void {
    this.closeFilterDrawer();
  }

  openSortSheet(): void {
    this.closeFilterDrawer();
    this.closeGridSheet();
    this.sortSheetOpen.set(true);
  }

  closeSortSheet(): void {
    this.sortSheetOpen.set(false);
  }

  selectSortFromSheet(sortId: string): void {
    this.changeSort(sortId);
    this.closeSortSheet();
  }

  openGridSheet(): void {
    this.closeFilterDrawer();
    this.closeSortSheet();
    this.gridSheetOpen.set(true);
  }

  closeGridSheet(): void {
    this.gridSheetOpen.set(false);
  }

  selectLayoutFromSheet(layout: CatalogLayoutMode): void {
    this.changeLayout(layout);
    this.closeGridSheet();
  }

  layoutLabelKey(layout: CatalogLayoutMode): string {
    switch (layout) {
      case 'grid':
      case 'grid-4':
        return 'catalog.layoutGrid4';
      case 'grid-2':
        return 'catalog.layoutGrid2';
      case 'grid-3':
        return 'catalog.layoutGrid3';
      case 'large-grid':
        return 'catalog.layoutGrid2';
      case 'compact':
        return 'catalog.layoutCompact';
      case 'compact-grid':
        return 'catalog.layoutCompact';
      case 'list':
      default:
        return 'catalog.layoutList';
    }
  }

  browseCategories(): void {
    this.uxFacade.clearContinueBrowsing('catalog');
    this.router.navigate([`/${this.languageService.currentLanguage()}/catalog`]);
  }

  goToParentCategory(): void {
    const breadcrumb = this.breadcrumb();
    const parent = breadcrumb.length > 1 ? breadcrumb[breadcrumb.length - 2] : null;
    if (parent) {
      this.selectCategory(parent);
      return;
    }

    this.browseCategories();
  }

  onResultsPageChange(page: number): void {
    this.state.update(current => ({
      ...current,
      pagination: {
        ...current.pagination,
        page,
        skip: (page - 1) * current.pagination.count
      }
    }));

    this.recomputeResults();
    this.syncUrlFromState();
    this.persistContinueBrowsing();
  }

  loadNextPage(): void {
    const nextPage = this.state().pagination.page + 1;
    if (!this.state().pagination.hasMore) {
      return;
    }

    this.onResultsPageChange(nextPage);
  }

  private renderCategories(category: Category | null, categories: Category[], breadcrumb: Category[]): void {
    const ordered = [...categories].sort((a, b) => a.priority - b.priority || a.id - b.id);
    this.viewMode.set('categories');
    this.state.update(current => ({ ...current, category }));
    this.categories.set(ordered);
    this.products.set([]);
    this.breadcrumb.set(breadcrumb);
    this.loading.set(false);
  }

  private loadProducts(categoryId: number): void {
    this.viewMode.set('products');
    this.loadCatalog(categoryId);
  }

  private loadCatalog(categoryId?: number): void {
    this.loadingProducts.set(true);
    const categoryID = categoryId ?? this.state().category?.id ?? this.state().filters.categoryIds[0];
    const query: SearchQuery = {
      text: this.state().search,
      categoryIds: categoryID == null ? [] : [categoryID],
      subcategoryIds: [],
      filters: this.filterState(),
      sort: this.state().sort,
      page: 1,
      pageSize: this.backendFetchSize,
    };

    this.dataSubscription = this.searchFacade.loadCatalog(query)
      .subscribe({
        next: result => {
          this.rawProducts.set(result.items);
          this.filterDefinitions.set(this.searchFacade.buildFilterGroups(result.items, this.catalogConfig().enabledFilters));
          this.recomputeResults();
          this.loading.set(false);
          this.loadingProducts.set(false);
        },
        error: () => this.setError('catalog.error')
      });
  }

  private recomputeResults(): void {
    if (this.rawProducts().length === 0) {
      this.products.set([]);
      this.state.update(current => ({
        ...current,
        pagination: {
          ...current.pagination,
          total: 0,
          hasMore: false,
          skip: 0,
          page: 1
        }
      }));
      this.searchSummary.set(this.translate.t('catalog.noResults'));
      this.searchResult.set(null);
      this.searchSuggestions.set([]);
      return;
    }

    const filtered = this.searchFacade.applyFilters(this.rawProducts(), this.filterState(), this.state().search);
    const sorted = this.searchFacade.applySort(filtered, this.state().sort);
    const pagination = this.state().pagination;
    const page = Math.max(1, pagination.page);
    const pageSize = this.loadingStrategy() === 'loadMore' || this.loadingStrategy() === 'infiniteScroll'
      ? Math.max(1, pagination.page * pagination.count)
      : Math.max(1, pagination.count);
    const paged = this.searchFacade.paginateProducts(sorted, page, pageSize);

    this.products.set(paged.items);
    this.state.update(current => ({
      ...current,
      pagination: {
        ...current.pagination,
        total: sorted.length,
        skip: paged.skip,
        hasMore: paged.skip + paged.pageSize < sorted.length
      }
    }));

    const result = this.searchFacade.buildResult(this.toSearchState(), sorted.length, paged.items);

    this.searchResult.set(result);
    this.searchSummary.set(result.summary);
    if (this.features().searchHistory) {
      this.searchSuggestions.set(this.searchFacade.buildLiveSuggestions(this.state().search, sorted));
    }

    if (this.pendingScrollY != null && typeof window !== 'undefined') {
      const scrollY = this.pendingScrollY;
      this.pendingScrollY = null;
      setTimeout(() => window.scrollTo({ top: scrollY, behavior: 'auto' }), 0);
    }
  }

  private toSearchState(): SearchState {
    const current = this.state();

    return {
      text: current.search,
      sort: current.sort,
      layout: current.layout,
      page: current.pagination.page,
      pageSize: current.pagination.count,
      filters: this.filterState(),
    };
  }

  private applySearchStatePatch(patch?: Partial<SearchState>): void {
    if (!patch) {
      return;
    }

    if (patch.filters) {
      this.filterState.set(patch.filters);
    }

    this.state.update(current => ({
      ...current,
      search: patch.text ?? current.search,
      sort: patch.sort ?? current.sort,
      layout: patch.layout ?? current.layout,
      pagination: {
        ...current.pagination,
        page: patch.page ?? current.pagination.page,
        count: patch.pageSize ?? current.pagination.count,
        skip: Math.max(0, ((patch.page ?? current.pagination.page) - 1) * (patch.pageSize ?? current.pagination.count)),
      },
    }));
  }

  private hasSearchState(patch: Partial<SearchState>): boolean {
    return Boolean(
      patch.text?.length ||
      patch.sort ||
      patch.layout ||
      (patch.page && patch.page > 1) ||
      patch.filters
    );
  }

  private syncUrlFromState(): void {
    const queryParams = this.searchFacade.toQueryParams(this.toSearchState());
    const scrollY = typeof window !== 'undefined' ? window.scrollY : null;

    this.syncingUrl = true;
    void this.router.navigate([], {
      relativeTo: this.route,
      queryParams,
      replaceUrl: true,
    }).finally(() => {
      if (scrollY != null && typeof window !== 'undefined') {
        this.restoringScrollFromUrlSync = true;
        const target = Math.max(0, scrollY);
        requestAnimationFrame(() => {
          window.scrollTo({ top: target, behavior: 'auto' });
          this.restoringScrollFromUrlSync = false;
        });
      }

      this.syncingUrl = false;
    });
  }

  private resolveCatalogConfig() {
    const snapshot = this.configService.getBootstrapSnapshot() as any;
    const raw = snapshot?.catalog ?? {};

    return {
      ...DEFAULT_CATALOG_CONFIG,
      ...raw,
      layout: this.normalizeLayout(raw.layout ?? DEFAULT_CATALOG_CONFIG.layout),
      availableSorts: Array.isArray(raw.availableSorts) && raw.availableSorts.length > 0
        ? raw.availableSorts
        : DEFAULT_CATALOG_CONFIG.availableSorts,
      enabledFilters: Array.isArray(raw.enabledFilters) && raw.enabledFilters.length > 0
        ? raw.enabledFilters
        : DEFAULT_CATALOG_CONFIG.enabledFilters
    };
  }

  private resolveUserExperienceConfig() {
    const snapshot = this.configService.getBootstrapSnapshot() as any;
    const raw = snapshot?.userExperience ?? {};

    return {
      ...DEFAULT_USER_EXPERIENCE_CONFIG,
      ...raw,
      wishlist: {
        ...DEFAULT_USER_EXPERIENCE_CONFIG.wishlist,
        ...(raw.wishlist ?? {})
      },
      compare: {
        ...DEFAULT_USER_EXPERIENCE_CONFIG.compare,
        ...(raw.compare ?? {}),
        maxItems: Number.isFinite(raw.compare?.maxItems)
          ? Math.max(2, Number(raw.compare.maxItems))
          : DEFAULT_USER_EXPERIENCE_CONFIG.compare.maxItems
      },
      recentlyViewed: {
        ...DEFAULT_USER_EXPERIENCE_CONFIG.recentlyViewed,
        ...(raw.recentlyViewed ?? {})
      },
      share: {
        ...DEFAULT_USER_EXPERIENCE_CONFIG.share,
        ...(raw.share ?? {})
      },
      continueBrowsing: {
        ...DEFAULT_USER_EXPERIENCE_CONFIG.continueBrowsing,
        ...(raw.continueBrowsing ?? {})
      },
      savedSearches: {
        ...DEFAULT_USER_EXPERIENCE_CONFIG.savedSearches,
        ...(raw.savedSearches ?? {}),
        maxItems: Number.isFinite(raw.savedSearches?.maxItems)
          ? Math.max(1, Number(raw.savedSearches.maxItems))
          : DEFAULT_USER_EXPERIENCE_CONFIG.savedSearches.maxItems
      }
    };
  }

  private persistContinueBrowsing(): void {
    if (!this.userExperienceConfig().continueBrowsing.enabled || this.viewMode() !== 'products') {
      return;
    }

    const state = this.state();
    this.uxFacade.saveContinueBrowsing({
      routeKey: 'catalog',
      query: state.search,
      sort: state.sort,
      layout: state.layout,
      page: state.pagination.page,
      pageSize: state.pagination.count,
      categoryId: state.filters.categoryIds[0],
      filterState: this.filterState(),
      scrollY: typeof window !== 'undefined' ? window.scrollY : 0,
      updatedAt: new Date().toISOString()
    });
  }

  private restoreContinueBrowsing(): boolean {
    const saved = this.uxFacade.getContinueBrowsing('catalog');
    if (!saved) {
      return false;
    }

    this.viewMode.set('products');
    this.filterState.set(saved.filterState);
    this.state.update(current => ({
      ...current,
      search: saved.query,
      sort: saved.sort as CatalogState['sort'],
      layout: saved.layout as CatalogLayoutMode,
      pagination: {
        ...current.pagination,
        page: Math.max(1, saved.page),
        count: Math.max(1, saved.pageSize),
        skip: Math.max(0, (saved.page - 1) * saved.pageSize)
      },
      filters: {
        ...current.filters,
        categoryIds: saved.categoryId == null ? [] : [saved.categoryId]
      }
    }));

    this.pendingScrollY = Math.max(0, saved.scrollY || 0);
    this.loadCatalog(saved.categoryId);
    return true;
  }

  private normalizeLayout(layout: CatalogLayoutMode): CatalogLayoutMode {
    if (layout === 'grid') return 'grid-4';
    if (layout === 'large-grid') return 'grid-2';
    if (layout === 'compact-grid') return 'compact';
    return layout;
  }

  private resolveInitialLayout(): CatalogLayoutMode {
    const stored = this.storage.getItem('catalog.layout.preference');
    return this.normalizeLayout((stored as CatalogLayoutMode) || this.catalogConfig().layout);
  }

  private async resolveCategoryId(categoryToken: string | null): Promise<number | null> {
    if (!categoryToken) {
      return null;
    }

    const numeric = Number(categoryToken);
    if (Number.isFinite(numeric) && numeric > 0) {
      return numeric;
    }

    const categories = await new Promise<Category[]>(resolve =>
      this.categoryFacade.getAllCategories().subscribe({ next: value => resolve(value), error: () => resolve([]) })
    );
    const normalized = categoryToken.trim().toLowerCase();
    const matched = categories.find(category => category.title.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-') === normalized);
    return matched?.id ?? null;
  }

  private setError(message: string): void {
    this.error.set(message);
    this.loading.set(false);
    this.loadingProducts.set(false);
  }
}
