import { DecimalPipe } from '@angular/common';
import { Component, OnInit, computed, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { Subject, debounceTime, distinctUntilChanged, of, switchMap, takeUntil } from 'rxjs';
import { MealPlanService } from '../../core/services/meal-plan.service';
import { RecipeService } from '../../core/services/recipe.service';
import { DietService } from '../../core/services/diet.service';
import { MealPlanEntry } from '../../core/models/meal-plan.models';
import { MealCategory, MEAL_CATEGORY_I18N_KEYS, RecipeListItem } from '../../core/models/recipe.models';
import {
  computeDayMealPlanning,
  estimateEntryCalories,
  rankRecipeSuggestions,
} from '../../core/utils/meal-planner-suggestions.util';
import {
  addDaysIso,
  endOfWeekIso,
  startOfWeekIso,
  todayIso,
} from '../../core/models/diet.models';
import { ErrorStateComponent } from '../../shared/components/error-state.component';
import { SkeletonComponent } from '../../shared/components/skeleton.component';
import { MealPlanDayPdfExportComponent } from './meal-plan-day-pdf-export.component';
import { extractApiError } from '../../core/utils/api-error.util';

const PLANNER_CATEGORIES = [
  MealCategory.Breakfast,
  MealCategory.SecondBreakfast,
  MealCategory.Lunch,
  MealCategory.Dinner,
] as const;

@Component({
  selector: 'app-meal-planner',
  standalone: true,
  imports: [DecimalPipe, FormsModule, TranslatePipe, ErrorStateComponent, SkeletonComponent, MealPlanDayPdfExportComponent],
  template: `
    <div class="space-y-6">
      <header>
        <h1 class="text-2xl font-extrabold tracking-tight sm:text-3xl">{{ 'mealPlanner.title' | translate }}</h1>
        <p class="mt-1 text-slate-500 dark:text-slate-400">{{ 'mealPlanner.subtitle' | translate }}</p>
      </header>

      <div class="flex flex-wrap items-center gap-2">
        <button type="button" class="btn-secondary !py-1.5 !text-sm" (click)="prevWeek()">←</button>
        <button type="button" class="btn-secondary !py-1.5 !text-sm" (click)="goToday()">{{ 'mealPlanner.today' | translate }}</button>
        <button type="button" class="btn-secondary !py-1.5 !text-sm" (click)="nextWeek()">→</button>
        <span class="text-sm font-medium text-slate-600 dark:text-slate-300">{{ weekLabel() }}</span>
      </div>

      @if (loading()) {
        <app-skeleton [className]="'h-64 w-full'" />
      } @else if (error()) {
        <app-error-state [message]="error()!" [showRetry]="true" (retry)="loadEntries()" />
      } @else {
        <div class="overflow-x-auto">
          <table class="w-full min-w-[640px] border-collapse text-sm">
            <thead>
              <tr>
                <th class="p-2 text-left font-semibold text-slate-500">{{ 'common.category' | translate }}</th>
                @for (day of weekDays(); track day.iso) {
                  <th class="p-2 text-left font-semibold" [class.text-brand-600]="day.iso === todayIso()">
                    <div class="flex flex-col gap-1">
                      <span>{{ day.label }}</span>
                      <app-meal-plan-day-pdf-export [date]="day.iso" [entries]="entries()" />
                    </div>
                  </th>
                }
              </tr>
            </thead>
            <tbody>
              @for (cat of categories; track cat) {
                <tr class="border-t border-slate-200 dark:border-slate-800">
                  <td class="p-2 font-medium">{{ categoryLabel(cat) | translate }}</td>
                  @for (day of weekDays(); track day.iso) {
                    <td class="p-2 align-top">
                      @for (entry of entriesFor(day.iso, cat); track entry.id) {
                        <div class="mb-1 flex items-start justify-between gap-1 rounded-lg bg-slate-50 px-2 py-1 dark:bg-slate-800/50">
                          <div>
                            <p class="font-medium">{{ entry.recipeName }}</p>
                            <p class="text-xs text-slate-400">{{ entry.portions | number: '1.0-1' }}× · {{ entry.calories ?? '?' }} kcal</p>
                          </div>
                          <button type="button" class="text-slate-400 hover:text-red-500" (click)="removeEntry(entry.id)">✕</button>
                        </div>
                      }
                      <button type="button" class="mt-1 text-xs font-semibold text-brand-600 hover:underline" (click)="openAdd(day.iso, cat)">
                        + {{ 'mealPlanner.addRecipe' | translate }}
                      </button>
                    </td>
                  }
                </tr>
              }
            </tbody>
          </table>
        </div>
      }

      @if (adding()) {
        <div class="card space-y-4 p-5">
          <h2 class="text-lg font-bold">{{ 'mealPlanner.addRecipe' | translate }}</h2>
          <p class="text-sm text-slate-500">{{ addingLabel() }}</p>
          <label class="block text-sm">
            <span class="font-medium">{{ 'mealPlanner.dailyTarget' | translate }}</span>
            <input
              type="number"
              min="0"
              step="50"
              class="input mt-1 w-full"
              [ngModel]="dailyTarget()"
              (ngModelChange)="setDailyTarget($event)"
            />
          </label>
          @if (dayPlanning() && dailyTarget() > 0) {
            <div class="rounded-xl bg-slate-50 px-3 py-2 text-sm text-slate-600 dark:bg-slate-800/50 dark:text-slate-300">
              <p>{{ 'mealPlanner.daySummary' | translate: { logged: dayPlanning()!.logged, target: dayPlanning()!.dailyTarget } }}</p>
              <p class="text-xs text-slate-500 dark:text-slate-400">
                {{ 'mealPlanner.remaining' | translate: { count: remainingCalories() } }}
                @if (dayPlanning()!.slotTarget > 0) {
                  · {{ 'mealPlanner.slotHint' | translate: { count: dayPlanning()!.slotTarget } }}
                }
                @if (dayPlanning()!.pendingCalories > 0) {
                  · {{ 'mealPlanner.includingSelection' | translate: { count: dayPlanning()!.pendingCalories } }}
                }
              </p>
            </div>
          } @else {
            <p class="text-xs text-slate-500">{{ 'mealPlanner.noSuggestions' | translate }}</p>
          }
          @if (suggestions().length > 0) {
            <div>
              <p class="mb-2 text-sm font-medium">{{ 'mealPlanner.suggestionsTitle' | translate }}</p>
              <ul class="max-h-48 space-y-1 overflow-y-auto rounded-xl border border-brand-200 bg-brand-50/50 p-2 dark:border-brand-900 dark:bg-brand-950/30">
                @for (item of suggestions(); track item.recipe.id) {
                  <li>
                    <button
                      type="button"
                      (click)="selectFromSuggestion(item.recipe)"
                      class="w-full rounded-lg px-3 py-2 text-left text-sm"
                      [class.ring-2]="selectedRecipe()?.id === item.recipe.id"
                      [class.ring-brand-600]="selectedRecipe()?.id === item.recipe.id"
                      [class.ring-inset]="selectedRecipe()?.id === item.recipe.id"
                    >
                      <span class="font-medium">{{ item.recipe.name }}</span>
                      <span class="ml-2 text-xs text-slate-500 dark:text-slate-400">
                        {{ item.recipe.calories }} kcal
                        @if (item.diff > 0) {
                          · ±{{ item.diff }}
                        }
                      </span>
                    </button>
                  </li>
                }
              </ul>
            </div>
          }
          <label class="block text-sm">
            <span class="font-medium">{{ 'diet.searchRecipe' | translate }}</span>
            <input class="input mt-1 w-full" [(ngModel)]="recipeSearch" (ngModelChange)="onRecipeSearch($event)" />
          </label>
          @if (recipeSearch.trim().length >= 2 && !selectedRecipe() && recipeResults().length > 0) {
            <ul class="max-h-40 space-y-1 overflow-y-auto rounded-xl border border-slate-200 p-2 dark:border-slate-700">
              @for (r of recipeResults(); track r.id) {
                <li>
                  <button
                    type="button"
                    (click)="selectFromSearch(r)"
                    class="w-full rounded-lg px-3 py-2 text-left text-sm hover:bg-slate-100 dark:hover:bg-slate-800"
                  >
                    {{ r.name }}
                    @if (r.calories != null) {
                      <span class="ml-2 text-xs text-slate-500">{{ r.calories }} kcal</span>
                    }
                  </button>
                </li>
              }
            </ul>
          } @else if (recipeSearch.trim().length >= 2 && !selectedRecipe() && recipeResults().length === 0) {
            <p class="text-xs text-slate-500">{{ 'recipes.noMatchingTitle' | translate }}</p>
          }
          @if (selectedRecipe()) {
            <div class="flex items-center justify-between gap-2 rounded-xl border border-brand-300 bg-brand-50 px-3 py-2 text-sm dark:border-brand-800 dark:bg-brand-950/40">
              <span class="font-medium text-brand-900 dark:text-brand-100">
                {{ 'mealPlanner.selectedRecipe' | translate: { name: selectedRecipe()!.name } }}
                @if (estimatedMealCalories() != null) {
                  · {{ estimatedMealCalories() }} kcal
                } @else if (selectedRecipe()!.calories != null) {
                  · {{ selectedRecipe()!.calories }} kcal
                }
              </span>
              <button type="button" class="text-slate-400 hover:text-red-500" (click)="clearSelection()" [attr.aria-label]="'mealPlanner.clearSelection' | translate">✕</button>
            </div>
          }
          <label class="block text-sm">
            <span class="font-medium">{{ 'diet.portions' | translate }}</span>
            <input type="number" min="0.25" step="0.25" class="input mt-1 w-full" [(ngModel)]="portions" />
          </label>
          @if (estimatedMealCalories() != null) {
            <p class="text-sm text-slate-500">{{ 'mealPlanner.estimatedMeal' | translate: { count: estimatedMealCalories() } }}</p>
          }
          <div class="flex gap-2">
            <button type="button" class="btn-secondary" [disabled]="!selectedRecipe() || busy()" (click)="saveEntry()">{{ 'common.save' | translate }}</button>
            <button type="button" class="btn-secondary" (click)="cancelAdd()">{{ 'common.cancel' | translate }}</button>
          </div>
        </div>
      }
    </div>
  `,
})
export class MealPlannerComponent implements OnInit {
  private readonly mealPlan = inject(MealPlanService);
  private readonly recipes = inject(RecipeService);
  private readonly diet = inject(DietService);
  private readonly translate = inject(TranslateService);
  private readonly destroy$ = new Subject<void>();
  private readonly recipeSearch$ = new Subject<string>();

  readonly categories = PLANNER_CATEGORIES;
  readonly todayIso = todayIso;

  weekStart = signal(startOfWeekIso(todayIso()));
  entries = signal<MealPlanEntry[]>([]);
  loading = signal(false);
  error = signal<string | null>(null);
  busy = signal(false);
  adding = signal<{ date: string; category: MealCategory } | null>(null);
  recipeSearch = '';
  selectedRecipe = signal<RecipeListItem | null>(null);
  recipeResults = signal<RecipeListItem[]>([]);
  categoryRecipes = signal<RecipeListItem[]>([]);
  calorieGoal = signal(0);
  dailyTargets = signal<Record<string, number>>({});
  portions = 1;

  readonly dailyTarget = computed(() => {
    const ctx = this.adding();
    if (!ctx) return 0;
    return this.dailyTargets()[ctx.date] ?? this.calorieGoal();
  });

  readonly estimatedMealCalories = computed(() =>
    estimateEntryCalories(this.selectedRecipe(), this.portions),
  );

  readonly dayPlanning = computed(() => {
    const ctx = this.adding();
    if (!ctx) return null;
    const pendingCalories = this.estimatedMealCalories();
    const pending =
      pendingCalories != null && pendingCalories > 0
        ? { category: ctx.category, calories: pendingCalories }
        : null;
    return computeDayMealPlanning(ctx.date, ctx.category, this.entries(), this.dailyTarget(), pending);
  });

  readonly remainingCalories = computed(() => {
    const planning = this.dayPlanning();
    return planning ? Math.max(0, planning.remaining) : 0;
  });

  readonly suggestions = computed(() => {
    const ctx = this.adding();
    const planning = this.dayPlanning();
    if (!ctx || !planning) return [];
    return rankRecipeSuggestions(this.categoryRecipes(), ctx.category, planning.slotTarget);
  });

  readonly weekDays = computed(() => {
    const start = this.weekStart();
    return Array.from({ length: 7 }, (_, i) => {
      const iso = addDaysIso(start, i);
      const d = new Date(`${iso}T12:00:00`);
      return {
        iso,
        label: d.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' }),
      };
    });
  });

  readonly weekLabel = computed(() => {
    const start = this.weekStart();
    const end = endOfWeekIso(start);
    return `${start} — ${end}`;
  });

  ngOnInit(): void {
    this.loadEntries();
    this.diet.getGoals().subscribe((g) => this.calorieGoal.set(g.calorieGoal ?? 0));
    this.recipeSearch$
      .pipe(
        debounceTime(300),
        distinctUntilChanged(),
        switchMap((q) => {
          if (q.trim().length >= 2) {
            return this.recipes.getRecipes({ search: q });
          }
          return of([]);
        }),
        takeUntil(this.destroy$),
      )
      .subscribe((items) => this.recipeResults.set(items));
  }

  categoryLabel(cat: MealCategory): string {
    return MEAL_CATEGORY_I18N_KEYS[cat];
  }

  entriesFor(date: string, category: MealCategory): MealPlanEntry[] {
    return this.entries().filter((e) => e.planDate === date && e.mealCategory === category);
  }

  loadEntries(): void {
    const from = this.weekStart();
    const to = endOfWeekIso(from);
    this.loading.set(true);
    this.error.set(null);
    this.mealPlan.getEntries(from, to).subscribe({
      next: (items) => {
        this.entries.set(items);
        this.loading.set(false);
      },
      error: (err) => {
        this.error.set(extractApiError(err, this.translate.instant('errors.loadMealPlanFailed')));
        this.loading.set(false);
      },
    });
  }

  prevWeek(): void {
    this.weekStart.update((s) => addDaysIso(s, -7));
    this.loadEntries();
  }

  nextWeek(): void {
    this.weekStart.update((s) => addDaysIso(s, 7));
    this.loadEntries();
  }

  goToday(): void {
    this.weekStart.set(startOfWeekIso(todayIso()));
    this.loadEntries();
  }

  openAdd(date: string, category: MealCategory): void {
    this.adding.set({ date, category });
    this.recipeSearch = '';
    this.selectedRecipe.set(null);
    this.recipeResults.set([]);
    this.portions = 1;
    this.recipes.getRecipes({ category }).subscribe((items) => this.categoryRecipes.set(items));
  }

  setDailyTarget(value: number): void {
    const ctx = this.adding();
    if (!ctx) return;
    const parsed = Number.isFinite(value) ? value : 0;
    this.dailyTargets.update((prev) => ({ ...prev, [ctx.date]: parsed }));
  }

  cancelAdd(): void {
    this.adding.set(null);
  }

  addingLabel(): string {
    const ctx = this.adding();
    if (!ctx) return '';
    return `${ctx.date} · ${this.translate.instant(this.categoryLabel(ctx.category))}`;
  }

  onRecipeSearch(value: string): void {
    this.recipeSearch = value;
    this.recipeSearch$.next(value);
  }

  selectFromSuggestion(r: RecipeListItem): void {
    this.selectedRecipe.set(r);
  }

  selectFromSearch(r: RecipeListItem): void {
    this.selectedRecipe.set(r);
    this.recipeSearch = '';
    this.recipeResults.set([]);
  }

  clearSelection(): void {
    this.selectedRecipe.set(null);
  }

  saveEntry(): void {
    const ctx = this.adding();
    const recipe = this.selectedRecipe();
    if (!ctx || !recipe) return;
    this.busy.set(true);
    this.mealPlan
      .upsertEntry({
        planDate: ctx.date,
        mealCategory: ctx.category,
        recipeId: recipe.id,
        portions: this.portions,
      })
      .subscribe({
        next: () => {
          this.busy.set(false);
          this.adding.set(null);
          this.loadEntries();
        },
        error: () => this.busy.set(false),
      });
  }

  removeEntry(id: number): void {
    this.mealPlan.deleteEntry(id).subscribe(() => this.loadEntries());
  }
}
