import { Component, EventEmitter, Input, OnChanges, Output, computed, inject, signal } from '@angular/core';
import { NgClass } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { RecipeManagementService } from '../../core/services/recipe-management.service';
import { RecipeDetail, RecalculateRecipeCaloriesResult } from '../../core/models/recipe.models';
import { CategoryBadgeComponent } from '../../shared/components/category-badge.component';
import { extractApiError } from '../../core/utils/api-error.util';
import { nextSort, sortBy, sortIndicator, type SortDirection } from '../../core/utils/sort.util';

type IngredientSortColumn = 'name' | 'amount';

@Component({
  selector: 'app-recipe-detail-view',
  standalone: true,
  imports: [NgClass, FormsModule, TranslatePipe, CategoryBadgeComponent],
  template: `
    <article class="space-y-8">
      <header class="space-y-3">
        <app-category-badge [category]="displayRecipe().mealCategory" />
        <h1 class="text-3xl font-extrabold tracking-tight">{{ displayRecipe().name }}</h1>
        @if (displayRecipe().prepTimeMinutes != null) {
          <p class="inline-flex items-center gap-1.5 text-sm text-slate-500 dark:text-slate-400">
            <span aria-hidden="true">⏱</span>
            {{ 'recipes.prepTime' | translate: { count: displayRecipe().prepTimeMinutes } }}
          </p>
        }
      </header>

      <section class="grid grid-cols-2 gap-3 sm:grid-cols-4">
        @for (macro of macroCards(); track macro.label) {
          <div
            class="rounded-2xl border px-4 py-3 text-center"
            [ngClass]="
              macro.highlight
                ? 'border-brand-300 bg-brand-50 dark:border-brand-700 dark:bg-brand-950/30'
                : 'border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900'
            "
          >
            <p class="text-2xl font-extrabold" [class]="macro.accent">
              {{ macro.value ?? '—' }}
              @if (macro.value != null) {
                <span class="text-sm font-semibold"> {{ macro.suffix }}</span>
              }
            </p>
            <p class="mt-0.5 text-xs font-medium uppercase tracking-wide text-slate-500 dark:text-slate-400">
              {{ macro.label | translate }}
            </p>
          </div>
        }
      </section>

      <section class="card space-y-3 p-4">
        <h2 class="text-sm font-bold">{{ 'recipes.recalculateCaloriesTitle' | translate }}</h2>
        <p class="text-sm text-slate-500 dark:text-slate-400">{{ 'recipes.recalculateCaloriesHint' | translate }}</p>
        <div class="flex flex-wrap items-end gap-3">
          <label class="block text-sm">
            {{ 'recipes.targetCalories' | translate }}
            <input type="number" min="50" max="10000" class="input mt-1 w-36" [(ngModel)]="targetCalories" (ngModelChange)="preview.set(null)" />
          </label>
          <button type="button" class="btn-secondary" [disabled]="pending() || applyPending()" (click)="runRecalculate(false)">
            ↻ {{ 'recipes.recalculate' | translate }}
          </button>
          @if (showingPreview()) {
            <button type="button" class="btn-primary" [disabled]="pending() || applyPending()" (click)="runRecalculate(true)">
              {{ 'recipes.applyRecalculation' | translate }}
            </button>
          }
        </div>
        @if (showingPreview() && preview()) {
          <p class="text-sm text-brand-700 dark:text-brand-300">
            {{
              'recipes.recalculatePreview'
                | translate: {
                    previous: preview()!.previousCalories ?? '—',
                    next: preview()!.newCalories ?? '—',
                    target: preview()!.targetCalories,
                  }
            }}
            @if (preview()!.unlinkedIngredientCount > 0) {
              {{ 'recipes.recalculateUnlinkedHint' | translate: { count: preview()!.unlinkedIngredientCount } }}
            }
          </p>
        }
        @if (error()) {
          <p role="alert" class="text-sm text-red-600 dark:text-red-400">{{ error() }}</p>
        }
      </section>

      <div class="grid gap-8 lg:grid-cols-[1fr_1.4fr]">
        <section>
          <h2 class="text-lg font-bold">{{ 'recipes.ingredients' | translate }}</h2>
          @if (displayRecipe().ingredients.length === 0) {
            <p class="mt-3 text-sm text-slate-500">{{ 'recipes.noIngredients' | translate }}</p>
          } @else {
            <div class="mt-4 overflow-hidden rounded-2xl border border-slate-200 dark:border-slate-800">
              <div class="grid grid-cols-[1fr_auto] border-b border-slate-200 bg-slate-50 px-4 py-2 text-xs font-semibold uppercase tracking-wide text-slate-500 dark:border-slate-800 dark:bg-slate-800/50">
                <button type="button" class="text-left hover:text-slate-700 dark:hover:text-slate-200" (click)="toggleIngredientSort('name')">
                  {{ 'ingredientCatalog.columns.name' | translate }}{{ ingredientIndicator('name') }}
                </button>
                <button type="button" class="text-right hover:text-slate-700 dark:hover:text-slate-200" (click)="toggleIngredientSort('amount')">
                  {{ 'diet.grams' | translate }}{{ ingredientIndicator('amount') }}
                </button>
              </div>
              <ul class="divide-y divide-slate-200 dark:divide-slate-800">
                @for (ing of sortedIngredients(); track ing.id) {
                  <li class="grid grid-cols-[1fr_auto] items-center px-4 py-3 text-sm">
                    <span>{{ ing.name }}</span>
                    <span
                      class="font-medium"
                      [ngClass]="
                        amountChanged(ing.id, ing.amountGrams)
                          ? 'text-brand-700 dark:text-brand-300'
                          : 'text-slate-500 dark:text-slate-400'
                      "
                    >
                      {{ formatAmount(ing.amountGrams, ing.unit) }}
                      @if (amountChanged(ing.id, ing.amountGrams)) {
                        <span class="ml-2 text-xs text-slate-400 line-through">
                          {{ formatAmount(originalAmounts().get(ing.id) ?? null, ing.unit) }}
                        </span>
                      }
                    </span>
                  </li>
                }
              </ul>
            </div>
          }
        </section>

        <section>
          <h2 class="text-lg font-bold">{{ 'recipes.preparation' | translate }}</h2>
          @if (displayRecipe().steps.length === 0) {
            <p class="mt-3 text-sm text-slate-500">{{ 'recipes.noSteps' | translate }}</p>
          } @else {
            <ol class="mt-4 space-y-4">
              @for (step of displayRecipe().steps; track step.id) {
                <li class="flex gap-4">
                  <span class="grid h-8 w-8 shrink-0 place-items-center rounded-full bg-brand-600 text-sm font-bold text-white">
                    {{ step.stepNumber }}
                  </span>
                  <p class="pt-1 text-sm leading-relaxed text-slate-700 dark:text-slate-300">
                    {{ step.description }}
                  </p>
                </li>
              }
            </ol>
          }
        </section>
      </div>
    </article>
  `,
})
export class RecipeDetailViewComponent implements OnChanges {
  private readonly management = inject(RecipeManagementService);
  private readonly translate = inject(TranslateService);

  @Input({ required: true }) recipe!: RecipeDetail;
  @Output() recipeUpdated = new EventEmitter<RecipeDetail>();

  readonly preview = signal<RecalculateRecipeCaloriesResult | null>(null);
  readonly pending = signal(false);
  readonly applyPending = signal(false);
  readonly error = signal<string | null>(null);
  targetCalories = '';

  readonly ingredientSort = signal<{ column: IngredientSortColumn; direction: SortDirection }>({
    column: 'name',
    direction: 'asc',
  });

  readonly originalAmounts = computed(
    () => new Map(this.recipe.ingredients.map((ing) => [ing.id, ing.amountGrams])),
  );

  readonly displayRecipe = computed(() => {
    const preview = this.preview();
    if (preview?.applied && preview.recipe) return preview.recipe;
    if (preview) {
      return {
        ...this.recipe,
        calories: preview.newCalories ?? this.recipe.calories,
        protein: preview.newProtein ?? this.recipe.protein,
        fat: preview.newFat ?? this.recipe.fat,
        carbs: preview.newCarbs ?? this.recipe.carbs,
        ingredients: preview.ingredients,
      };
    }
    return this.recipe;
  });

  readonly showingPreview = computed(() => {
    const preview = this.preview();
    return preview != null && !preview.applied;
  });

  readonly macroCards = computed(() => {
    const recipe = this.displayRecipe();
    const highlight = this.showingPreview();
    return [
      { label: 'recipes.calories', value: recipe.calories, suffix: 'kcal', accent: 'text-orange-500', highlight },
      { label: 'recipes.protein', value: recipe.protein, suffix: 'g', accent: 'text-brand-600', highlight },
      { label: 'recipes.fat', value: recipe.fat, suffix: 'g', accent: 'text-amber-500', highlight },
      { label: 'recipes.carbs', value: recipe.carbs, suffix: 'g', accent: 'text-sky-500', highlight },
    ];
  });

  readonly sortedIngredients = computed(() => {
    const { column, direction } = this.ingredientSort();
    return sortBy(this.displayRecipe().ingredients, direction, (ing) =>
      column === 'name' ? ing.name : ing.amountGrams,
    );
  });

  ngOnChanges(): void {
    if (this.recipe && !this.targetCalories) {
      this.targetCalories = this.recipe.calories != null ? String(this.recipe.calories) : '';
    }
  }

  formatAmount(amount: number | null, unit: string | null): string {
    if (amount == null && !unit) return '';
    if (amount == null) return unit ?? '';
    const display = Number.isInteger(amount) ? amount.toString() : amount.toString();
    return unit ? `${display} ${unit}` : `${display} g`;
  }

  amountChanged(id: number, amount: number | null): boolean {
    if (!this.showingPreview()) return false;
    const previous = this.originalAmounts().get(id);
    return previous != null && amount != null && previous !== amount;
  }

  toggleIngredientSort(column: IngredientSortColumn): void {
    this.ingredientSort.update((current) => nextSort(current, column));
  }

  ingredientIndicator(column: IngredientSortColumn): string {
    return sortIndicator(this.ingredientSort(), column);
  }

  runRecalculate(apply: boolean): void {
    const target = Number(this.targetCalories);
    if (!Number.isFinite(target) || target < 50) {
      this.error.set(this.translate.instant('recipes.recalculateCaloriesInvalid'));
      return;
    }

    this.error.set(null);
    if (apply) this.applyPending.set(true);
    else this.pending.set(true);

    this.management.recalculateRecipeCalories(this.recipe.id, target, apply).subscribe({
      next: (result) => {
        this.preview.set(result);
        if (result.applied && result.recipe) {
          this.recipe = result.recipe;
          this.recipeUpdated.emit(result.recipe);
          this.preview.set(null);
        }
        this.pending.set(false);
        this.applyPending.set(false);
      },
      error: (err) => {
        this.error.set(extractApiError(err, this.translate.instant('recipes.recalculateCaloriesFailed')));
        this.pending.set(false);
        this.applyPending.set(false);
      },
    });
  }
}
