import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output, computed } from '@angular/core';
import { FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ZardFormModule } from '@shared/components/form/form.module';
import { ZardSelectComponent } from '@shared/components/select/select.component';
import { ZardSelectItemComponent } from '@shared/components/select/select-item.component';
import { Course, CourseType } from 'src/app/core/interfaces/course';
import { Resultat, ResultatStatut } from 'src/app/core/interfaces/resultat';

type PlaceRow = { picks: FormArray<FormControl<number | null>> };
type ResultatShape = { places: FormArray<FormGroup<PlaceRow>> };

@Component({
  standalone: true,
  selector: 'app-resultat-form',
  imports: [
    CommonModule,
    ReactiveFormsModule,
    ZardFormModule,
    ZardSelectComponent,
    ZardSelectItemComponent,
  ],
  templateUrl: './resultat-form.html',
})
export class ResultatForm {
  @Input() course!: Course;
  @Input() resultat?: Resultat | null;
  @Output() save = new EventEmitter<{
  horses: number[][];
  typesParisOuverts: string[];
}>();
  @Output() validate = new EventEmitter<void>();
  @Output() confirm = new EventEmitter<void>();
  @Output() cancel = new EventEmitter<void>();

  form = new FormGroup<ResultatShape>({
    places: new FormArray<FormGroup<PlaceRow>>([]),
  });

  getRequiredHorses = (types: string[]): number => {
    // Normalize types to uppercase strings for robust comparisons
    const normalized = (types || []).map((t) => String(t).toUpperCase());
    if (normalized.includes('QUINTE')) return 5;
    if (normalized.includes('QUATRO')) return 4;
    if (normalized.includes('TRIO') || normalized.includes('TRIO_ORDRE') || normalized.includes('TRIPLET')) return 3;
    if (
      normalized.includes('JUMELE_GAGNANT') ||
      normalized.includes('JUMELE_PLACE') ||
      normalized.includes('JUMELE_ORDRE')
    )
      return 2;

    if (normalized.includes('PLACE') || normalized.includes('GAGNANT')) return 1;
    
    return 3; // Default
  };

  reqLen = computed(() =>
    this.getRequiredHorses(this.course.typesParisOuverts)
  );
  maxNum = computed(() => this.course?.nombrePartants ?? 0);
  // Ensure non-partants are compared as strings to avoid type mismatches
  npSet = computed(() => new Set((this.course?.nonPartants ?? []).map((v) => String(v))));
  statut = computed((): ResultatStatut => {
  return this.resultat ? this.resultat.statut : ResultatStatut.EN_ATTENTE;
});

  canValidate(): boolean {
    return String(this.statut()) === "EN_ATTENTE";
  }

  canConfirm(): boolean {
    return String(this.statut()) === "PROVISOIRE";
  }

  // Helper methods for template
  hasHorsesAtPlace(i: number): boolean {
    return this.picksAt(i).controls.some((c) => typeof c.value === 'number');
  }

  getHorsesAtPlace(i: number): string {
    return this.picksAt(i)
      .controls.map((c) => c.value)
      .filter((v): v is number => typeof v === 'number')
      .join('=');
  }

  // Calculate all combinations when there are ex-aequo
  combinations = computed(() => {
    const places: number[][] = [];
    for (let i = 0; i < this.places.length; i++) {
      if (this.isPlaceSkipped(i)) continue;
      const placeHorses = this.picksAt(i)
        .controls.map((c) => c.value)
        .filter((v): v is number => typeof v === 'number');
      if (placeHorses.length > 0) {
        places.push(placeHorses);
      }
    }

    // Generate all combinations based on ex-aequo
    return this.generateCombinations(places);
  });

  // Generate combinations considering ex-aequo
  private generateCombinations(places: number[][]): number[][] {
    if (places.length === 0) return [];

    // If all horses are in first place (all ex-aequo), generate all permutations
    if (places.length === 1 && places[0].length === this.reqLen()) {
      return this.generatePermutations(places[0]);
    }

    // Otherwise, generate combinations based on ex-aequo in each place
    const result: number[][] = [];
    this.generateCombinationsRecursive(places, 0, [], result);
    return result;
  }

  private generateCombinationsRecursive(
    places: number[][],
    placeIndex: number,
    current: number[],
    result: number[][]
  ): void {
    if (placeIndex >= places.length) {
      if (current.length === this.reqLen()) {
        result.push([...current]);
      }
      return;
    }

    const placeHorses = places[placeIndex];
    if (placeHorses.length === 1) {
      // Single horse, add it and continue
      current.push(placeHorses[0]);
      this.generateCombinationsRecursive(places, placeIndex + 1, current, result);
      current.pop();
    } else {
      // Ex-aequo: generate all permutations of these horses
      const perms = this.generatePermutations(placeHorses);
      for (const perm of perms) {
        // Add all horses from this place
        current.push(...perm);
        this.generateCombinationsRecursive(places, placeIndex + 1, current, result);
        // Remove them
        current.splice(current.length - perm.length);
      }
    }
  }

  private isAutoPopulating = false;

  // Generate all permutations of an array
  private generatePermutations(arr: number[]): number[][] {
    if (arr.length <= 1) return [arr];
    const result: number[][] = [];
    for (let i = 0; i < arr.length; i++) {
      const rest = [...arr.slice(0, i), ...arr.slice(i + 1)];
      const perms = this.generatePermutations(rest);
      for (const perm of perms) {
        result.push([arr[i], ...perm]);
      }
    }
    return result;
  }

  // Check if a place has ex-aequo (multiple horses)
  hasExAequoAtPlace(i: number): boolean {
    const placeHorses = this.picksAt(i)
      .controls.map((c) => c.value)
      .filter((v): v is number => typeof v === 'number');
    return placeHorses.length > 1;
  }

  // Check if a place should be skipped
  // Simple logic: If previous places with horses have consumed position (i+1), skip it
  isPlaceSkipped(i: number): boolean {
    if (i === 0) return false; // First place is never skipped

    // Calculate total positions consumed by previous places (only places with horses)
    let totalConsumed = 0;
    for (let j = 0; j < i; j++) {
      const horses = this.picksAt(j)
        .controls.map((c) => c.value)
        .filter((v): v is number => typeof v === 'number');
      // Only places with horses consume positions
      if (horses.length > 0) {
        totalConsumed += horses.length;
      }
    }

    // The place at index i should be at position (i + 1) if no ex-aequo
    // If totalConsumed >= (i + 1), then position (i+1) is already taken, so skip
    // Example: If place 0 has 2 horses, totalConsumed = 2
    //   Place 1 (i=1) should be at position 2, but 2 >= 2, so skipped ✓
    //   Place 2 (i=2) should be at position 3, but 2 >= 3 is false, so active ✓
    return totalConsumed >= i + 1;
  }

  // Get the actual place number displayed to user
  getActualPlaceNumber(i: number): number {
    if (i === 0) return 1;

    // Count actual positions used by non-skipped places before this one
    let position = 1;
    for (let j = 0; j < i; j++) {
      if (!this.isPlaceSkipped(j)) {
        const horses = this.picksAt(j)
          .controls.map((c) => c.value)
          .filter((v): v is number => typeof v === 'number');
        position += horses.length > 0 ? horses.length : 1;
      }
    }
    return position;
  }

  // Count total horses selected across all places
  totalHorsesSelected = computed(() => {
    let total = 0;
    for (let i = 0; i < this.places.length; i++) {
      const placeHorses = this.picksAt(i)
        .controls.map((c) => c.value)
        .filter((v): v is number => typeof v === 'number');
      total += placeHorses.length;
    }
    return total;
  });

  ngOnInit() {
    this.seed();
    // Watch for changes to auto-populate places when ex-aequo is detected
    this.setupAutoPopulate();
  }
  ngOnChanges() {
    this.seed();
  }

  private setupAutoPopulate() {
    // Watch for changes in all places to clear skipped places
    this.places.controls.forEach((placeGroup) => {
      placeGroup.controls.picks.valueChanges.subscribe(() => {
        this.autoPopulatePlaces();
      });
    });
  }

  private autoPopulatePlaces() {
    if (this.isAutoPopulating) return; // Prevent infinite loops
    this.isAutoPopulating = true;

    // Clear places that should be skipped (when previous place has ex-aequo)
    for (let i = 1; i < this.places.length; i++) {
      if (this.isPlaceSkipped(i)) {
        const placePicks = this.picksAt(i);
        const currentHorses = placePicks.controls
          .map((c) => c.value)
          .filter((v): v is number => typeof v === 'number');

        // Clear this place if it has horses (it should be skipped)
        if (currentHorses.length > 0) {
          while (placePicks.length > 0) {
            placePicks.removeAt(0);
          }
          placePicks.push(this.makePickControl(null));
        }
      }
    }

    this.isAutoPopulating = false;
  }

  private seed() {
    if (!this.course) return;

    const placesFA = new FormArray<FormGroup<PlaceRow>>([]);
    const len = this.reqLen();

    // Extract existing places from resultat if available.
    // Be defensive: ordreArrivee may be provided as array or CSV string, normalize to number[]
    let existing: number[][] = [];
    if (this.resultat && this.resultat.ordreArrivee && (this.resultat.ordreArrivee as any).length > 0) {
      const rawOrdre = (this.resultat as any).ordreArrivee;
      const allHorses: number[] = ((): number[] => {
        if (Array.isArray(rawOrdre)) return rawOrdre.map(Number).filter((n) => !Number.isNaN(n));
        if (typeof rawOrdre === 'string') {
          return rawOrdre
            .split(/[^0-9]+/)
            .map((s) => Number(s))
            .filter((n) => !Number.isNaN(n));
        }
        const n = Number(rawOrdre);
        return Number.isNaN(n) ? [] : [n];
      })();

      const deadHeatSet = new Set(((this.resultat.chevauxDeadHeat ?? []) as any).map(Number));

      const allInDeadHeat = allHorses.every((num) => deadHeatSet.has(num)) && allHorses.length === len;

      if (allInDeadHeat) {
        // All horses are in first place (ex-aequo)
        existing = [allHorses]; // Put all in first place
        // Fill remaining places with empty arrays
        for (let i = 1; i < len; i++) {
          existing.push([]);
        }
      } else {
        // Group consecutive horses that are in dead heat together
        const groups: number[][] = [];
        let currentGroup: number[] = [];

        allHorses.forEach((num, index) => {
          const isInDeadHeat = deadHeatSet.has(num);
          const prevNum = index > 0 ? allHorses[index - 1] : null;
          const prevIsInDeadHeat = prevNum !== null && deadHeatSet.has(prevNum);

          if (isInDeadHeat && prevIsInDeadHeat && currentGroup.length > 0) {
            // Continue the current dead heat group
            currentGroup.push(num);
          } else {
            // Start a new group
            if (currentGroup.length > 0) {
              groups.push(currentGroup);
            }
            currentGroup = [num];
          }
        });

        // Don't forget the last group
        if (currentGroup.length > 0) {
          groups.push(currentGroup);
        }

        // Limit to required places
        existing = groups.slice(0, len);
      }
    }

    // Create form controls for each place
    for (let i = 0; i < len; i++) {
      const picksFA = new FormArray<FormControl<number | null>>([]);
      const row = existing[i] ?? [];
      if (row.length) {
        row.forEach((n) => picksFA.push(this.makePickControl(n)));
      } else {
        picksFA.push(this.makePickControl(null));
      }
      placesFA.push(new FormGroup<PlaceRow>({ picks: picksFA }));
    }
    this.form.setControl('places', placesFA);
  }

  private makePickControl(initial: number | null) {
    const ctrl = new FormControl<number | null>(initial, [Validators.required]);

    ctrl.valueChanges.subscribe((v) => {
      if (typeof v === 'string') {
        const n = Number(v);
        ctrl.setValue(Number.isNaN(n) ? null : (n as any), { emitEvent: false });
        return;
      }

      if (v === null) {
        const loc = this.locateControl(ctrl);
        if (!loc) return;
        const { placeIdx, pickIdx } = loc;
        const fa = this.picksAt(placeIdx);
        if (fa.length > 1) fa.removeAt(pickIdx);
        // Check if we need to clear skipped places after removal
        setTimeout(() => this.autoPopulatePlaces(), 0);
        return;
      }

      const loc = this.locateControl(ctrl);
      if (!loc) return;
      const { placeIdx, pickIdx } = loc;
      const fa = this.picksAt(placeIdx);
      const dup = fa.controls.some((c, idx) => idx !== pickIdx && c.value === v);
      if (dup) {
        ctrl.setValue(null, { emitEvent: false });
        return;
      }

      // After value change, check if we need to clear skipped places
      setTimeout(() => this.autoPopulatePlaces(), 0);
    });

    return ctrl;
  }

  private locateControl(
    ctrl: FormControl<number | null>
  ): { placeIdx: number; pickIdx: number } | null {
    for (let i = 0; i < this.places.length; i++) {
      const fa = this.picksAt(i);
      const idx = fa.controls.indexOf(ctrl);
      if (idx !== -1) return { placeIdx: i, pickIdx: idx };
    }
    return null;
  }

  get places(): FormArray<FormGroup<PlaceRow>> {
    return this.form.controls.places;
  }
  picksAt(i: number): FormArray<FormControl<number | null>> {
    return this.places.at(i).controls.picks;
  }

  private usedElsewhere(exceptPlace: number): Set<number> {
    const used = new Set<number>();
    this.places.controls.forEach((pg, idx) => {
      if (idx === exceptPlace) return;
      pg.controls.picks.controls.forEach((c) => {
        if (typeof c.value === 'number') used.add(c.value);
      });
    });
    return used;
  }

  optionsForPlace(i: number, j: number): number[] {
    const all = Array.from({ length: this.maxNum() }, (_, k) => k + 1);
    const np = this.npSet();

    // Count total horses already selected (excluding current control's value)
    const allUsed = new Set<number>();
    this.places.controls.forEach((pg) => {
      pg.controls.picks.controls.forEach((c) => {
        if (typeof c.value === 'number') allUsed.add(c.value);
      });
    });

    // Remove the current control's value from used set (so it can be re-selected)
    const currentValue = this.picksAt(i).at(j).value;
    if (typeof currentValue === 'number') {
      allUsed.delete(currentValue);
    }

    // Also remove other horses in the same place (for ex-aequo)
    const usedSamePlace = new Set(
      this.picksAt(i)
        .controls.map((c, idx) => (idx === j ? null : c.value))
        .filter((v): v is number => typeof v === 'number')
    );

    // CRITICAL: Count how many horses are already selected (excluding current control)
    const totalSelected = allUsed.size;
    const requiredHorses = this.reqLen();

    // CRITICAL: If we already have exactly reqLen() horses selected (excluding current control),
    // block ALL new selections (except keeping current value)
    if (totalSelected >= requiredHorses) {
      // Only allow keeping the current value if it exists
      if (typeof currentValue === 'number') {
        return [currentValue];
      }
      return [];
    }

    // For first place: can have multiple horses (ex-aequo), but not duplicates in same place
    // For other places: can only use horses not used anywhere
    let opts: number[];
    if (i === 0) {
      // First place: can have multiple horses (ex-aequo), but not duplicates in same place
      opts = all.filter((n) => !np.has(String(n)) && !usedSamePlace.has(n));
    } else {
      // Other places: can only use horses not used anywhere
      opts = all.filter((n) => !np.has(String(n)) && !allUsed.has(n) && !usedSamePlace.has(n));
    }

    // Always include current value if it exists (so user can keep their current selection)
    if (typeof currentValue === 'number' && !opts.includes(currentValue)) {
      opts.push(currentValue);
    }

    return opts.sort((a, b) => a - b);
  }

  addTie(i: number) {
    // Don't allow adding ex-aequo to skipped places
    if (this.isPlaceSkipped(i)) return;

    // Check if we can add another horse (ex-aequo)
    const totalSelected = this.totalHorsesSelected();
    const requiredHorses = this.reqLen();

    // Only allow adding ex-aequo if we haven't reached the required number of horses yet
    if (totalSelected >= requiredHorses) {
      // Already have enough horses - don't allow adding more
      return;
    }

    this.picksAt(i).push(this.makePickControl(null));
    // Check if we need to clear skipped places after adding
    setTimeout(() => this.autoPopulatePlaces(), 0);
  }

  removePick(i: number, j: number) {
    const fa = this.picksAt(i);
    fa.removeAt(j);
    if (fa.length === 0) fa.push(this.makePickControl(null));
  }

  private isValidGlobally(): boolean {
    const np = this.npSet();
    const max = this.maxNum();

    // Collect all selected horses (excluding skipped places)
    const allHorses: number[] = [];
    for (let i = 0; i < this.places.length; i++) {
      if (this.isPlaceSkipped(i)) continue; // Skip places that should be skipped
      const picks = this.picksAt(i)
        .controls.map((c) => c.value)
        .filter((v): v is number => typeof v === 'number');
      allHorses.push(...picks);
    }

    // Must have exactly reqLen() horses
    if (allHorses.length !== this.reqLen()) return false;

    // All horses must be valid (within range, not non-partants, unique)
    for (const horse of allHorses) {
      if (horse < 1 || horse > max || np.has(String(horse))) return false;
    }
    if (new Set(allHorses).size !== allHorses.length) return false;

    // First place must have at least one horse
    const firstPlaceHorses = this.picksAt(0)
      .controls.map((c) => c.value)
      .filter((v): v is number => typeof v === 'number');
    if (firstPlaceHorses.length === 0) return false;

    // Check that all non-skipped places have valid horses (not just form.valid which includes skipped places)
    for (let i = 0; i < this.places.length; i++) {
      if (!this.isPlaceSkipped(i)) {
        const placeHorses = this.picksAt(i)
          .controls.map((c) => c.value)
          .filter((v): v is number => typeof v === 'number');
        // Each non-skipped place must have at least one horse
        if (placeHorses.length === 0) {
          return false;
        }
        // Check that all controls in this place are valid
        const placePicks = this.picksAt(i);
        for (const ctrl of placePicks.controls) {
          if (ctrl.invalid && ctrl.touched) {
            return false;
          }
        }
      }
    }

    return true;
  }

  canSave(): boolean {
    return this.isValidGlobally();
  }

  onSave() {
    if (!this.isValidGlobally()) {
      this.form.markAllAsTouched();
      return;
    }
    // Build places array, excluding skipped places
    const places: number[][] = [];
    for (let i = 0; i < this.places.length; i++) {
      if (this.isPlaceSkipped(i)) continue; // Skip places that should be skipped
      const placeHorses = this.picksAt(i)
        .controls.map((c) => c.value)
        .filter((v): v is number => typeof v === 'number');
      places.push(placeHorses);
    }
    this.save.emit({horses: places, typesParisOuverts: this.course.typesParisOuverts});
  }
}
