import { Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { Subject, debounceTime, distinctUntilChanged, takeUntil } from 'rxjs';
import { RecipeService } from '../../core/services/recipe.service';
import { RecipeListItem } from '../../core/models/recipe.models';
import { RecipeCardComponent } from '../recipes/recipe-card.component';
import { CategoryFilter, RecipeFiltersComponent } from '../recipes/recipe-filters.component';
import { PdfGeneratorComponent } from '../pdf/pdf-generator.component';
import { ErrorStateComponent } from '../../shared/components/error-state.component';
import { EmptyStateComponent } from '../../shared/components/empty-state.component';
import { GridSkeletonComponent } from '../../shared/components/skeleton.component';
import { translatePlural } from '../../core/utils/plural-translate.util';
import { extractApiError } from '../../core/utils/api-error.util';
import { nextSort, sortBy, sortIndicator, type SortDirection } from '../../core/utils/sort.util';

type RecipeSortColumn = 'name' | 'calories' | 'prepTime';

@Component({
  selector: 'app-all-meals',
  standalone: true,
  imports: [
    TranslatePipe,
    RecipeCardComponent,
    RecipeFiltersComponent,
    PdfGeneratorComponent,
    ErrorStateComponent,
    EmptyStateComponent,
    GridSkeletonComponent,
  ],
  template: `
    <div class="space-y-6">
      <header class="flex flex-wrap items-end justify-between gap-4">
        <div>
          <h1 class="text-2xl font-extrabold tracking-tight sm:text-3xl">{{ 'recipes.allMealsTitle' | translate }}</h1>
          <p class="mt-1 text-slate-500 dark:text-slate-400">{{ 'recipes.allMealsSubtitle' | translate }}</p>
        </div>
        <span
          class="inline-flex items-center gap-2 rounded-full bg-brand-100 px-3.5 py-1.5 text-sm font-semibold text-brand-800 dark:bg-brand-900/40 dark:text-brand-300"
        >
          {{ selectedCountLabel() }}
        </span>
      </header>

      <app-recipe-filters
        [active]="filter()"
        (onChange)="filter.set($event)"
        [search]="search()"
        (onSearchChange)="onSearchChange($event)"
        [searchByIngredient]="searchByIngredient()"
        (onSearchByIngredientChange)="onIngredientModeChange($event)"
      />

      <div class="flex flex-wrap items-center gap-3">
        <button type="button" (click)="selectAllVisible()" [disabled]="visible().length === 0" class="btn-secondary">
          ☑ {{ 'recipes.selectAllVisible' | translate }}
        </button>
        <button type="button" (click)="deselectAll()" [disabled]="selected().size === 0" class="btn-secondary">
          ☐ {{ 'recipes.deselectAll' | translate }}
        </button>
        <div class="ml-auto">
          <app-pdf-generator [selectedIds]="selectedIds()" />
        </div>
      </div>

      @if (loading()) {
        <app-grid-skeleton [count]="9" />
      }

      @if (error()) {
        <app-error-state [message]="error()!" [showRetry]="true" (retry)="load()" />
      }

      @if (!loading() && !error() && visible().length === 0) {
        <app-empty-state
          [title]="'recipes.noMatchingTitle' | translate"
          [description]="
            searchByIngredient()
              ? ('recipes.noMatchingIngredient' | translate)
              : ('recipes.noMatchingFilter' | translate)
          "
        />
      }

      @if (!loading() && !error() && visible().length > 0) {
        <div class="flex flex-wrap gap-2 text-sm">
          @for (col of recipeSortColumns; track col.id) {
            <button type="button" class="rounded-lg border border-slate-200 px-3 py-1.5 font-medium hover:bg-slate-50 dark:border-slate-700 dark:hover:bg-slate-800" (click)="toggleRecipeSort(col.id)">
              {{ col.labelKey | translate }}{{ recipeSortIndicator(col.id) }}
            </button>
          }
        </div>
        <div class="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
          @for (recipe of sortedVisible(); track recipe.id) {
            <app-recipe-card
              [recipe]="recipe"
              [selectable]="true"
              [showCategory]="true"
              [selected]="selected().has(recipe.id)"
              (toggleSelected)="toggleSelected($event)"
            />
          }
        </div>
      }
    </div>
  `,
})
export class AllMealsComponent implements OnInit, OnDestroy {
  private readonly recipeService = inject(RecipeService);
  private readonly translate = inject(TranslateService);
  private readonly destroy$ = new Subject<void>();
  private readonly searchDebounced$ = new Subject<string>();

  readonly filter = signal<CategoryFilter>('all');
  readonly search = signal('');
  readonly debouncedSearch = signal('');
  readonly searchByIngredient = signal(false);
  readonly selected = signal(new Set<number>());
  readonly recipes = signal<RecipeListItem[] | null>(null);
  readonly loading = signal(true);
  readonly error = signal<string | null>(null);
  readonly recipeSort = signal<{ column: RecipeSortColumn; direction: SortDirection }>({
    column: 'name',
    direction: 'asc',
  });
  readonly recipeSortColumns: { id: RecipeSortColumn; labelKey: string }[] = [
    { id: 'name', labelKey: 'manage.recipeName' },
    { id: 'calories', labelKey: 'recipes.calories' },
    { id: 'prepTime', labelKey: 'manage.prepTimeMin' },
  ];

  readonly visible = computed(() => {
    const list = this.recipes();
    if (!list) return [];
    const term = this.search().trim().toLowerCase();
    const activeFilter = this.filter();
    return list.filter((r) => {
      const matchesCategory = activeFilter === 'all' || r.mealCategory === activeFilter;
      const matchesSearch =
        this.searchByIngredient() || term === '' || r.name.toLowerCase().includes(term);
      return matchesCategory && matchesSearch;
    });
  });

  readonly sortedVisible = computed(() => {
    const { column, direction } = this.recipeSort();
    return sortBy(this.visible(), direction, (recipe) => {
      switch (column) {
        case 'name':
          return recipe.name;
        case 'calories':
          return recipe.calories;
        case 'prepTime':
          return recipe.prepTimeMinutes;
      }
    });
  });

  readonly selectedIds = computed(() => Array.from(this.selected()));

  ngOnInit(): void {
    this.searchDebounced$
      .pipe(debounceTime(350), distinctUntilChanged(), takeUntil(this.destroy$))
      .subscribe((value) => {
        this.debouncedSearch.set(value);
        this.load();
      });
    this.load();
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  onSearchChange(value: string): void {
    this.search.set(value);
    this.searchDebounced$.next(value.trim());
  }

  onIngredientModeChange(value: boolean): void {
    this.searchByIngredient.set(value);
    this.load();
  }

  load(): void {
    this.loading.set(true);
    this.error.set(null);
    const ingredient =
      this.searchByIngredient() && this.debouncedSearch() ? this.debouncedSearch() : undefined;
    this.recipeService.getRecipes({ ingredient }).subscribe({
      next: (data) => {
        this.recipes.set(data);
        this.loading.set(false);
      },
      error: (err) => {
        this.error.set(extractApiError(err, this.translate.instant('errors.loadRecipesFailed')));
        this.loading.set(false);
      },
    });
  }

  toggleSelected(id: number): void {
    this.selected.update((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }

  selectAllVisible(): void {
    this.selected.update((prev) => {
      const next = new Set(prev);
      this.visible().forEach((r) => next.add(r.id));
      return next;
    });
  }

  deselectAll(): void {
    this.selected.set(new Set());
  }

  selectedCountLabel(): string {
    return translatePlural(this.translate, 'common.selectedCount', this.selected().size);
  }

  toggleRecipeSort(column: RecipeSortColumn): void {
    this.recipeSort.update((current) => nextSort(current, column));
  }

  recipeSortIndicator(column: RecipeSortColumn): string {
    return sortIndicator(this.recipeSort(), column);
  }
}
