import { Component, EventEmitter, Input, Output, inject, OnInit } from '@angular/core';
import {
  AbstractControl,
  FormArray,
  FormBuilder,
  ReactiveFormsModule,
  ValidationErrors,
  Validators,
} from '@angular/forms';
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import {
  CreateRecipeInput,
  MealCategory,
  MEAL_CATEGORY_I18N_KEYS,
  RecipeDetail,
} from '../../core/models/recipe.models';
import { IngredientCatalogService } from '../../core/services/ingredient-catalog.service';
import { IngredientNutritionItem } from '../../core/models/ingredient-catalog.models';
import { LanguageService } from '../../core/services/language.service';
import {
  countUnlinkedMacroIngredients,
  estimateMacrosFromCatalog,
  hasNonMacroUnit,
  isUnlinkedMacroIngredient,
} from '../../core/utils/recipe-macros.util';

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

function minStepsArray(min: number) {
  return (control: AbstractControl): ValidationErrors | null => {
    const arr = control as FormArray;
    return arr.length >= min ? null : { minSteps: true };
  };
}

@Component({
  selector: 'app-recipe-form',
  standalone: true,
  imports: [ReactiveFormsModule, TranslatePipe],
  template: `
    <form [formGroup]="form" (ngSubmit)="submit()" class="space-y-8" novalidate>
      @if (error) {
        <div role="alert" class="rounded-xl bg-red-50 p-3 text-sm text-red-700 dark:bg-red-950/40 dark:text-red-300">
          {{ error }}
        </div>
      }

      <section class="card space-y-4 p-6">
        <h2 class="text-lg font-bold">{{ 'manage.basicInfo' | translate }}</h2>
        <div class="grid gap-4 sm:grid-cols-2">
          <div class="sm:col-span-2">
            <label class="mb-1.5 block text-sm font-medium">{{ 'manage.recipeName' | translate }}</label>
            <input class="input" formControlName="name" [placeholder]="'manage.recipeNamePlaceholder' | translate" />
            @if (form.controls.name.touched && form.controls.name.errors?.['required']) {
              <p class="mt-1 text-sm text-red-600">{{ 'validation.recipeNameRequired' | translate }}</p>
            }
          </div>
          <div>
            <label class="mb-1.5 block text-sm font-medium">{{ 'common.category' | translate }}</label>
            <select class="select" formControlName="mealCategory">
              @for (cat of mealCategories; track cat) {
                <option [ngValue]="cat">{{ categoryLabel(cat) | translate }}</option>
              }
            </select>
          </div>
          <div>
            <label class="mb-1.5 block text-sm font-medium">{{ 'manage.prepTimeMin' | translate }}</label>
            <input type="number" class="input" formControlName="prepTimeMinutes" />
          </div>
          <div>
            <label class="mb-1.5 block text-sm font-medium">{{ 'manage.caloriesKcal' | translate }}</label>
            <input type="number" class="input" formControlName="calories" />
          </div>
          <div>
            <label class="mb-1.5 block text-sm font-medium">{{ 'manage.proteinG' | translate }}</label>
            <input type="number" step="0.1" class="input" formControlName="protein" />
          </div>
          <div>
            <label class="mb-1.5 block text-sm font-medium">{{ 'manage.fatG' | translate }}</label>
            <input type="number" step="0.1" class="input" formControlName="fat" />
          </div>
          <div>
            <label class="mb-1.5 block text-sm font-medium">{{ 'manage.carbsG' | translate }}</label>
            <input type="number" step="0.1" class="input" formControlName="carbs" />
          </div>
        </div>
        @if (estimatedTotalMacros(); as macros) {
          <p class="text-sm text-slate-500">
            {{ 'manage.estimatedFromCatalog' | translate }}:
            {{ macros.calories }} kcal · P {{ macros.protein.toFixed(1) }}g · F {{ macros.fat.toFixed(1) }}g · C
            {{ macros.carbs.toFixed(1) }}g
          </p>
        }
        @if (unlinkedIngredientCount() > 0) {
          <p class="text-sm text-amber-700 dark:text-amber-300">
            {{ 'manage.unlinkedIngredientsWarning' | translate: { count: unlinkedIngredientCount() } }}
          </p>
        }
      </section>

      <section class="card space-y-4 p-6">
        <div class="flex items-center justify-between">
          <h2 class="text-lg font-bold">{{ 'recipes.ingredients' | translate }}</h2>
          <button type="button" class="btn-secondary" (click)="addIngredient()">
            + {{ 'common.add' | translate }}
          </button>
        </div>
        <div class="space-y-3" formArrayName="ingredients">
          @for (ctrl of ingredients.controls; track $index; let i = $index) {
            <div
              class="space-y-2 rounded-xl border p-3"
              [class.border-slate-200]="!isUnlinkedIngredient(i)"
              [class.dark:border-slate-700]="!isUnlinkedIngredient(i)"
              [class.border-amber-400]="isUnlinkedIngredient(i)"
              [class.bg-amber-50]="isUnlinkedIngredient(i)"
              [class.dark:bg-amber-950]="isUnlinkedIngredient(i)"
              [formGroupName]="i"
            >
              <div class="grid gap-2 sm:grid-cols-[1fr_100px_100px_auto]">
                <input class="input" formControlName="name" [placeholder]="'manage.ingredientName' | translate" />
                <input type="number" step="0.1" class="input" formControlName="amountGrams" [placeholder]="'manage.ingredientGrams' | translate" (input)="onAmountChange(i)" />
                <input class="input" formControlName="unit" [placeholder]="'manage.ingredientUnit' | translate" />
                <button type="button" class="btn-secondary !px-2.5" (click)="removeIngredient(i)" [attr.aria-label]="'manage.removeIngredient' | translate">
                  🗑
                </button>
              </div>
              <label class="block text-xs">
                <span class="font-medium text-slate-500">{{ 'manage.catalogLink' | translate }}</span>
                <select class="select mt-1 w-full" formControlName="catalogItemId" (change)="onCatalogChange(i)">
                  <option [ngValue]="null">{{ 'diet.noCatalogItem' | translate }}</option>
                  @for (item of catalogItems; track item.id) {
                    <option [ngValue]="item.id">{{ displayCatalogName(item) }}</option>
                  }
                </select>
              </label>
              @if (macroHint(i); as hint) {
                <p class="text-xs text-brand-700 dark:text-brand-300">{{ 'manage.macroHint' | translate: hint }}</p>
              }
            </div>
          }
        </div>
      </section>

      <section class="card space-y-4 p-6">
        <div class="flex items-center justify-between">
          <h2 class="text-lg font-bold">{{ 'manage.preparationSteps' | translate }}</h2>
          <button type="button" class="btn-secondary" (click)="addStep()">
            + {{ 'manage.addStep' | translate }}
          </button>
        </div>
        @if (steps.errors?.['minSteps']) {
          <p class="text-sm text-red-600">{{ 'validation.stepsMin' | translate }}</p>
        }
        <div class="space-y-3" formArrayName="steps">
          @for (ctrl of steps.controls; track $index; let i = $index) {
            <div class="flex gap-2" [formGroupName]="i">
              <input type="number" class="input w-20 shrink-0" formControlName="stepNumber" />
              <input class="input flex-1" formControlName="description" [placeholder]="'manage.stepPlaceholder' | translate" />
              <button type="button" class="btn-secondary !px-2.5" (click)="removeStep(i)" [attr.aria-label]="'manage.removeStep' | translate">
                🗑
              </button>
            </div>
          }
        </div>
      </section>

      <button type="submit" [disabled]="pending" class="btn-primary">
        @if (pending) {
          <span class="inline-block h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent"></span>
        }
        {{ pending ? ('manage.saving' | translate) : (editMode ? ('manage.updateRecipe' | translate) : ('manage.saveRecipe' | translate)) }}
      </button>
    </form>
  `,
})
export class RecipeFormComponent implements OnInit {
  private readonly fb = inject(FormBuilder);
  private readonly translate = inject(TranslateService);
  private readonly catalog = inject(IngredientCatalogService);
  private readonly language = inject(LanguageService);

  @Input() pending = false;
  @Input() error: string | null = null;
  @Input() editMode = false;
  @Input() recipeId: number | null = null;
  @Output() submitRecipe = new EventEmitter<CreateRecipeInput>();

  readonly mealCategories = MEAL_CATEGORIES;
  catalogItems: IngredientNutritionItem[] = [];

  readonly form = this.fb.nonNullable.group({
    name: ['', [Validators.required, Validators.maxLength(255)]],
    mealCategory: [MealCategory.Breakfast],
    calories: this.fb.control<number | null>(null),
    protein: this.fb.control<number | null>(null),
    fat: this.fb.control<number | null>(null),
    carbs: this.fb.control<number | null>(null),
    prepTimeMinutes: this.fb.control<number | null>(null),
    ingredients: this.fb.array([this.createIngredientGroup()]),
    steps: this.fb.array([this.createStepGroup(1)], { validators: minStepsArray(1) }),
  });

  ngOnInit(): void {
    this.catalog.getItems().subscribe({
      next: (items) => (this.catalogItems = items),
      error: () => (this.catalogItems = []),
    });
  }

  get ingredients(): FormArray {
    return this.form.controls.ingredients;
  }

  get steps(): FormArray {
    return this.form.controls.steps;
  }

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

  displayCatalogName(item: IngredientNutritionItem): string {
    if (this.language.currentLanguage() === 'pl' && item.namePl) return item.namePl;
    return item.name;
  }

  createIngredientGroup(catalogItemId: number | null = null) {
    return this.fb.nonNullable.group({
      name: ['', Validators.required],
      amountGrams: this.fb.control<number | null>(null),
      unit: [''],
      sortOrder: [0],
      catalogItemId: this.fb.control<number | null>(catalogItemId),
    });
  }

  createStepGroup(stepNumber: number) {
    return this.fb.nonNullable.group({
      stepNumber: [stepNumber, [Validators.required, Validators.min(1)]],
      description: ['', Validators.required],
    });
  }

  addIngredient(): void {
    this.ingredients.push(
      this.fb.nonNullable.group({
        name: ['', Validators.required],
        amountGrams: this.fb.control<number | null>(null),
        unit: [''],
        sortOrder: [this.ingredients.length],
        catalogItemId: this.fb.control<number | null>(null),
      }),
    );
  }

  removeIngredient(index: number): void {
    this.ingredients.removeAt(index);
  }

  addStep(): void {
    this.steps.push(this.createStepGroup(this.steps.length + 1));
  }

  removeStep(index: number): void {
    this.steps.removeAt(index);
  }

  onCatalogChange(index: number): void {
    const group = this.ingredients.at(index);
    const catalogItemId = group.get('catalogItemId')?.value as number | null;
    if (!catalogItemId) return;
    const item = this.catalogItems.find((c) => c.id === catalogItemId);
    if (item && !group.get('name')?.value) {
      group.patchValue({ name: this.displayCatalogName(item) });
    }
  }

  onAmountChange(_index: number): void {
    // triggers macroHint recomputation in template
  }

  estimatedTotalMacros() {
    const values = this.ingredients.getRawValue();
    return estimateMacrosFromCatalog(values, this.catalogItems);
  }

  unlinkedIngredientCount(): number {
    return countUnlinkedMacroIngredients(this.ingredients.getRawValue());
  }

  isUnlinkedIngredient(index: number): boolean {
    const values = this.ingredients.at(index).getRawValue();
    return isUnlinkedMacroIngredient(values);
  }

  macroHint(index: number): { calories: string; protein: string; fat: string; carbs: string } | null {
    const group = this.ingredients.at(index);
    const catalogItemId = group.get('catalogItemId')?.value as number | null;
    const grams = group.get('amountGrams')?.value as number | null;
    const unit = group.get('unit')?.value as string;
    if (!catalogItemId || !grams || grams <= 0 || hasNonMacroUnit(unit)) return null;
    const item = this.catalogItems.find((c) => c.id === catalogItemId);
    if (!item) return null;
    const factor = grams / 100;
    return {
      calories: (item.caloriesPer100G * factor).toFixed(0),
      protein: (item.proteinGPer100G * factor).toFixed(1),
      fat: (item.fatGPer100G * factor).toFixed(1),
      carbs: (item.carbsGPer100G * factor).toFixed(1),
    };
  }

  loadRecipe(recipe: RecipeDetail): void {
    this.editMode = true;
    this.recipeId = recipe.id;
    this.form.patchValue({
      name: recipe.name,
      mealCategory: recipe.mealCategory,
      calories: recipe.calories,
      protein: recipe.protein,
      fat: recipe.fat,
      carbs: recipe.carbs,
      prepTimeMinutes: recipe.prepTimeMinutes,
    });
    this.ingredients.clear();
    for (const ing of recipe.ingredients) {
      this.ingredients.push(
        this.fb.nonNullable.group({
          name: [ing.name, Validators.required],
          amountGrams: this.fb.control<number | null>(ing.amountGrams),
          unit: [ing.unit ?? ''],
          sortOrder: [ing.sortOrder],
          catalogItemId: this.fb.control<number | null>(ing.catalogItemId ?? null),
        }),
      );
    }
    if (this.ingredients.length === 0) {
      this.ingredients.push(this.createIngredientGroup());
    }
    this.steps.clear();
    for (const step of recipe.steps) {
      this.steps.push(this.createStepGroup(step.stepNumber));
      this.steps.at(this.steps.length - 1).patchValue({
        stepNumber: step.stepNumber,
        description: step.description,
      });
    }
    if (this.steps.length === 0) {
      this.steps.push(this.createStepGroup(1));
    }
  }

  submit(): void {
    this.form.markAllAsTouched();
    if (this.form.invalid) return;

    const values = this.form.getRawValue();
    this.submitRecipe.emit({
      name: values.name,
      mealCategory: values.mealCategory,
      calories: values.calories ?? null,
      protein: values.protein ?? null,
      fat: values.fat ?? null,
      carbs: values.carbs ?? null,
      prepTimeMinutes: values.prepTimeMinutes ?? null,
      ingredients: values.ingredients.map((i, index) => ({
        name: i.name,
        amountGrams: i.amountGrams ?? null,
        unit: i.unit ?? '',
        sortOrder: i.sortOrder ?? index,
        catalogItemId: i.catalogItemId ?? null,
      })),
      steps: values.steps,
    });
  }

  reset(): void {
    this.editMode = false;
    this.recipeId = null;
    this.form.reset({
      name: '',
      mealCategory: MealCategory.Breakfast,
      calories: null,
      protein: null,
      fat: null,
      carbs: null,
      prepTimeMinutes: null,
    });
    this.ingredients.clear();
    this.ingredients.push(this.createIngredientGroup());
    this.steps.clear();
    this.steps.push(this.createStepGroup(1));
  }
}
