/**
 * Card recognition service that orchestrates the full pipeline.
 * 
 * Pipeline:
 * 1. Card detection (optional) - find card boundaries
 * 2. Perspective correction - warp to rectangle
 * 3. CLAHE preprocessing - normalize lighting
 * 4. Resize to 32x32
 * 5. Compute perceptual hash
 * 6. Match against database via Hamming distance
 */

import { detectCard, CardDetectionResult } from "./cardDetector";
import { warpPerspective } from "./perspectiveCorrection";
import { applyCLAHE } from "./clahe";
import {
  computeColorHash,
  hammingDistance,
  MATCH_THRESHOLD,
  HASH_BITS,
} from "./perceptualHash";
import { resizeImage, rotateImage } from "./imageUtils";

export interface RecognitionOptions {
  /** Enable card detection and perspective correction. */
  enableCardDetection?: boolean;
  /** Enable rotation matching (try 0°, 90°, 180°, 270°). */
  enableRotationMatching?: boolean;
  /** Minimum confidence to accept a match (0-1). */
  minConfidence?: number;
  /** Maximum Hamming distance to accept a match. */
  matchThreshold?: number;
}

export interface CardMatch {
  /** Card ID from database. */
  cardId: string;
  /** Match confidence (0-1). */
  confidence: number;
  /** Hamming distance between hashes. */
  distance: number;
  /** Rotation used for match (0, 90, 180, or 270). */
  rotation: number;
}

export interface RecognitionResult {
  success: boolean;
  match?: CardMatch;
  cardDetection?: CardDetectionResult;
  error?: string;
  processingTimeMs: number;
}

export interface CardHashEntry {
  id: string;
  name: string;
  setCode: string;
  collectorNumber?: string;
  imageUri?: string;
  hash: Uint8Array;
}

const DEFAULT_OPTIONS: Required<RecognitionOptions> = {
  enableCardDetection: true,
  enableRotationMatching: true,
  minConfidence: 0.85,
  matchThreshold: MATCH_THRESHOLD,
};

/**
 * Calculate confidence from Hamming distance.
 */
export function calculateConfidence(distance: number, totalBits: number = HASH_BITS): number {
  return 1 - distance / totalBits;
}

/**
 * Recognize a card from RGBA pixel data.
 * 
 * @param pixels - RGBA pixel data
 * @param width - Image width
 * @param height - Image height
 * @param cardHashes - Array of card hashes to match against
 * @param options - Recognition options
 * @returns Recognition result with best match
 */
export function recognizeCard(
  pixels: Uint8Array | Uint8ClampedArray,
  width: number,
  height: number,
  cardHashes: CardHashEntry[],
  options: RecognitionOptions = {}
): RecognitionResult {
  const startTime = performance.now();
  const opts = { ...DEFAULT_OPTIONS, ...options };
  
  try {
    if (cardHashes.length === 0) {
      return {
        success: false,
        error: "No cards in database",
        processingTimeMs: performance.now() - startTime,
      };
    }
    
    let cardPixels = pixels;
    let cardWidth = width;
    let cardHeight = height;
    let detection: CardDetectionResult | undefined;
    
    // Step 1: Detect and extract card (if enabled)
    if (opts.enableCardDetection) {
      detection = detectCard(pixels, width, height);
      
      if (detection.found) {
        const warped = warpPerspective(
          pixels,
          width,
          height,
          detection.corners
        );
        cardPixels = warped.pixels;
        cardWidth = warped.width;
        cardHeight = warped.height;
      }
    }
    
    // Step 2: Find best match (with or without rotation)
    const match = opts.enableRotationMatching
      ? findBestMatchWithRotations(cardPixels, cardWidth, cardHeight, cardHashes, opts)
      : findBestMatchSingle(cardPixels, cardWidth, cardHeight, cardHashes, opts);
    
    const processingTimeMs = performance.now() - startTime;
    
    if (!match) {
      return {
        success: false,
        cardDetection: detection,
        error: "No match found",
        processingTimeMs,
      };
    }
    
    return {
      success: true,
      match,
      cardDetection: detection,
      processingTimeMs,
    };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : "Unknown error",
      processingTimeMs: performance.now() - startTime,
    };
  }
}

/**
 * Compute hash for an image with full preprocessing pipeline.
 */
export function computeImageHash(
  pixels: Uint8Array | Uint8ClampedArray,
  width: number,
  height: number
): Uint8Array {
  // Apply CLAHE
  const clahePixels = applyCLAHE(pixels, width, height);
  
  // Resize to 32x32
  const resized = resizeImage(clahePixels, width, height, 32, 32);
  
  // Compute hash
  return computeColorHash(resized);
}

/**
 * Find best match trying all 4 rotations.
 */
function findBestMatchWithRotations(
  pixels: Uint8Array | Uint8ClampedArray,
  width: number,
  height: number,
  cardHashes: CardHashEntry[],
  opts: Required<RecognitionOptions>
): CardMatch | null {
  let bestMatch: CardMatch | null = null;
  
  const rotations: Array<0 | 90 | 180 | 270> = [0, 90, 180, 270];
  
  for (const rotation of rotations) {
    const { pixels: rotatedPixels, width: rotatedWidth, height: rotatedHeight } =
      rotation === 0
        ? { pixels, width, height }
        : rotateImage(pixels, width, height, rotation);
    
    // Apply CLAHE
    const clahePixels = applyCLAHE(rotatedPixels, rotatedWidth, rotatedHeight);
    
    // Resize to 32x32
    const resized = resizeImage(clahePixels, rotatedWidth, rotatedHeight, 32, 32);
    
    // Compute hash
    const queryHash = computeColorHash(resized);
    
    // Find best match for this rotation
    for (const card of cardHashes) {
      if (!card.hash || card.hash.length !== queryHash.length) continue;
      
      const distance = hammingDistance(queryHash, card.hash);
      const confidence = calculateConfidence(distance);
      
      if (distance <= opts.matchThreshold && confidence >= opts.minConfidence) {
        if (!bestMatch || distance < bestMatch.distance) {
          bestMatch = {
            cardId: card.id,
            confidence,
            distance,
            rotation,
          };
        }
        
        // Early exit on perfect match
        if (distance === 0) {
          return bestMatch;
        }
      }
    }
  }
  
  return bestMatch;
}

/**
 * Find best match without rotation.
 */
function findBestMatchSingle(
  pixels: Uint8Array | Uint8ClampedArray,
  width: number,
  height: number,
  cardHashes: CardHashEntry[],
  opts: Required<RecognitionOptions>
): CardMatch | null {
  // Apply CLAHE
  const clahePixels = applyCLAHE(pixels, width, height);
  
  // Resize to 32x32
  const resized = resizeImage(clahePixels, width, height, 32, 32);
  
  // Compute hash
  const queryHash = computeColorHash(resized);
  
  let bestMatch: CardMatch | null = null;
  
  for (const card of cardHashes) {
    if (!card.hash || card.hash.length !== queryHash.length) continue;
    
    const distance = hammingDistance(queryHash, card.hash);
    const confidence = calculateConfidence(distance);
    
    if (distance <= opts.matchThreshold && confidence >= opts.minConfidence) {
      if (!bestMatch || distance < bestMatch.distance) {
        bestMatch = {
          cardId: card.id,
          confidence,
          distance,
          rotation: 0,
        };
      }
      
      if (distance === 0) {
        return bestMatch;
      }
    }
  }
  
  return bestMatch;
}

/**
 * Create a recognition service instance with cached card hashes.
 */
export function createRecognitionService(cardHashes: CardHashEntry[]) {
  let cachedHashes = cardHashes;
  
  return {
    /**
     * Recognize a card from RGBA pixel data.
     */
    recognize(
      pixels: Uint8Array | Uint8ClampedArray,
      width: number,
      height: number,
      options?: RecognitionOptions
    ): RecognitionResult {
      return recognizeCard(pixels, width, height, cachedHashes, options);
    },
    
    /**
     * Update the cached card hashes.
     */
    updateHashes(hashes: CardHashEntry[]) {
      cachedHashes = hashes;
    },
    
    /**
     * Get the number of cached hashes.
     */
    getHashCount(): number {
      return cachedHashes.length;
    },
  };
}
