import { Injectable, signal } from '@angular/core';
import { Observable, from, of } from 'rxjs';
import { map, catchError, switchMap, tap } from 'rxjs/operators';
import { LocalInferenceService } from '../../services/local-inference.service';
import { RemoteInferenceService } from './remote-inference.service';
import { ImageProcessorService } from '../../services/image-processor.service';
import { AnalysisResponse, DetectionResult } from '../interfaces/palm-analysis.interface';

export type InferenceMode = 'local' | 'api';
export type LocalEngine = 'onnx' | 'tflite';

@Injectable({
  providedIn: 'root'
})
export class InferenceService {
  // Use Signals for reactive UI updates
  public mode = signal<InferenceMode>('local');
  public localEngine = signal<LocalEngine>('onnx');
  public detections = signal<any[]>([]);
  public summary = signal<any>({});

  constructor(
    private readonly localInferenceService: LocalInferenceService,
    private readonly remoteInferenceService: RemoteInferenceService,
    private readonly imageProcessor: ImageProcessorService
  ) { }

  /**
   * Main analyze entry point. imageData is base64 string.
   */
  analyze(imageData: string, width: number, height: number): Observable<any[]> {
    if (this.mode() === 'local') {
      return this.runLocalAnalysis(imageData, width, height);
    } else {
      return this.runRemoteAnalysis(imageData);
    }
  }

  private runLocalAnalysis(imageData: string, width: number, height: number): Observable<any[]> {
    const blob = this.base64ToBlob(imageData);
    if (!blob) return of([]);

    const file = new File([blob], 'capture.jpg', { type: 'image/jpeg' });

    const modelPath = this.localEngine() === 'onnx'
      ? 'assets/models/onnx/best.onnx'
      : 'assets/models/tflite/best_float32.tflite';

    return from(this.localInferenceService.loadModel(modelPath)).pipe(
      switchMap(() => from(this.imageProcessor.processImage(file))),
      switchMap(processedData => from(this.localInferenceService.runInference(processedData))),
      map(rawData => {
        if (!rawData) return [];
        return this.localInferenceService.parseDetections(rawData, 0.25, width, height);
      }),
      tap(dets => {
        this.detections.set(dets);
        this.summary.set(this.calculateSummary(dets));
      }),
      catchError(err => {
        console.error('Local Inference Hub Error:', err);
        return of([]);
      })
    );
  }

  private runRemoteAnalysis(imageData: string): Observable<any[]> {
    const blob = this.base64ToBlob(imageData);
    if (!blob) return of([]);

    return this.remoteInferenceService.analyze(blob).pipe(
      map((response: AnalysisResponse) => {
        // console.log(response)
        const mappedDets = response.detections.map(det => ({
          box: det.box,
          confidence: det.confidence,
          class: det.class,
          color: this.getGradeColor(det.class),
          is_health_alert: det.is_health_alert
        }));

        this.detections.set(mappedDets);
        this.summary.set({ ...response.industrial_summary });

        return mappedDets;
      }),
      catchError(err => {
        console.error('Remote Inference Hub Error:', err);
        return of([]);
      })
    );
  }

  private calculateSummary(detections: any[]): any {
    const summary: any = {
      'Empty_Bunch': 0, 'Underripe': 0, 'Abnormal': 0, 'Ripe': 0, 'Unripe': 0, 'Overripe': 0
    };
    detections.forEach(d => { if (summary[d.class] !== undefined) summary[d.class]++; });
    return summary;
  }

  private base64ToBlob(base64: string): Blob | null {
    try {
      if (!base64 || !base64.includes(',')) return null;
      const parts = base64.split(',');
      const byteString = atob(parts[1]);
      const mimeString = parts[0].split(':')[1].split(';')[0];
      const ab = new ArrayBuffer(byteString.length);
      const ia = new Uint8Array(ab);
      for (let i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
      }
      return new Blob([ab], { type: mimeString });
    } catch (e) {
      console.error('Blob Conversion Failed:', e);
      return null;
    }
  }

  private getGradeColor(className: string): string {
    const colors: { [key: string]: string } = {
      'Empty_Bunch': '#6C757D', 'Underripe': '#F9A825', 'Abnormal': '#DC3545',
      'Ripe': '#00A651', 'Unripe': '#9E9D24', 'Overripe': '#5D4037'
    };
    return colors[className] || '#000000';
  }
}
