import { Injectable, inject } from '@angular/core';
import { Observable, forkJoin, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { GameApiService } from './game-api.service';
import { AnnotatedMove, AnalysisResponse, MoveQuality } from '../models/analysis.models';

const DEFAULT_DEPTH = 14;

@Injectable({ providedIn: 'root' })
export class AnalysisService {
  private readonly gameApi = inject(GameApiService);

  analyzePosition(fen: string, depth = DEFAULT_DEPTH): Observable<AnalysisResponse> {
    return this.gameApi.analyzePosition({ fen, depth });
  }

  /**
   * Analyse a sequence of FEN positions (one per ply) and return annotated moves.
   * fenHistory[0] is the starting position; fenHistory[n] is reached after move san[n-1].
   */
  analyzeGame(
    sans: string[],
    fenHistory: string[],
    depth = DEFAULT_DEPTH,
  ): Observable<AnnotatedMove[]> {
    if (sans.length === 0 || fenHistory.length < 2) {
      return of([]);
    }

    const requests = fenHistory.map((fen) => this.gameApi.analyzePosition({ fen, depth }));

    return forkJoin(requests).pipe(
      map((results) => this.buildAnnotations(sans, fenHistory, results)),
    );
  }

  private buildAnnotations(
    sans: string[],
    fenHistory: string[],
    results: AnalysisResponse[],
  ): AnnotatedMove[] {
    return sans.map((san, i) => {
      const evalBefore = results[i]?.eval ?? null;
      const winChanceBefore = results[i]?.winChance ?? null;
      const evalAfter = results[i + 1]?.eval ?? null;
      const winChanceAfter = results[i + 1]?.winChance ?? null;
      const bestMove = results[i]?.bestMove ?? null;

      const quality = this.classifyQuality(
        evalBefore,
        evalAfter,
        winChanceBefore,
        winChanceAfter,
        san,
        bestMove,
      );

      return {
        san,
        fen: fenHistory[i + 1] ?? fenHistory[i],
        evalBefore,
        evalAfter,
        quality,
        bestMove,
        winChanceBefore,
        winChanceAfter,
      };
    });
  }

  /**
   * Classify move quality based on win-chance delta.
   * Win-chance is from the engine's perspective (side to move before the move).
   * After the move the side has flipped, so we invert the after value.
   */
  private classifyQuality(
    _evalBefore: number | null,
    _evalAfter: number | null,
    winChanceBefore: number | null,
    winChanceAfter: number | null,
    san: string,
    bestMove: string | null,
  ): MoveQuality | null {
    if (winChanceBefore === null || winChanceAfter === null) return null;

    // The engine expresses win chance for the mover. After our move the mover
    // has switched, so the opponent's win chance = winChanceAfter. Our remaining
    // winning chance from our own perspective is 1 - winChanceAfter.
    const wcAfterOurPerspective = 1 - winChanceAfter;
    const delta = wcAfterOurPerspective - winChanceBefore; // negative = we lost win chance

    if (bestMove && san === bestMove) {
      if (delta >= -0.01) return 'brilliant';
      return 'best';
    }

    if (delta >= -0.02) return 'good';
    if (delta >= -0.05) return 'inaccuracy';
    if (delta >= -0.1) return 'mistake';
    return 'blunder';
  }
}
