import * as FileSystem from 'expo-file-system';
import { manipulateAsync, SaveFormat, type Action } from 'expo-image-manipulator';

import { logDev, warnDev } from '../providerUtils';
import { getPlantScanPhotos } from './inputNormalization';
import type { PlantIdentificationInput, PlantScanPhoto, PlantScanPhotoRole } from './types';

// Image upload quality for plant identification (Bekky, 2026-08-19).
// Raised from 512px @ JPEG q0.4 → 1024px @ q0.7: the tiny-flower/petal detail
// critical for species ID was being destroyed by heavy downscale+compress.
const maxUploadImageDimension = 1024;
const uploadJpegQuality = 0.7;

export type OptimizedPlantScanImage = {
  role: PlantScanPhotoRole;
  sourceUri: string;
  uploadUri: string;
  width?: number;
  height?: number;
  base64: string;
};

function estimateBase64ByteLength(payload: string): number {
  const compactPayload = payload.replace(/\s/g, '');
  const paddingLength = compactPayload.endsWith('==')
    ? 2
    : compactPayload.endsWith('=')
      ? 1
      : 0;

  return Math.max(0, Math.floor((compactPayload.length * 3) / 4) - paddingLength);
}

async function getOriginalByteLength(uri: string): Promise<number | undefined> {
  if (/^data:[^,]*;base64,/i.test(uri)) {
    const commaIndex = uri.indexOf(',');
    return commaIndex >= 0 ? estimateBase64ByteLength(uri.slice(commaIndex + 1)) : undefined;
  }

  try {
    const info = await FileSystem.getInfoAsync(uri);
    const size = (info as { size?: unknown }).size;
    return typeof size === 'number' && Number.isFinite(size) && size >= 0 ? size : undefined;
  } catch {
    return undefined;
  }
}

function isPositiveFiniteNumber(value: unknown): value is number {
  return typeof value === 'number' && Number.isFinite(value) && value > 0;
}

function getResizeAction(photo: PlantScanPhoto): Action | null {
  const width = isPositiveFiniteNumber(photo.width) ? photo.width : undefined;
  const height = isPositiveFiniteNumber(photo.height) ? photo.height : undefined;

  if (width && height) {
    const largestDimension = Math.max(width, height);
    if (largestDimension <= maxUploadImageDimension) return null;

    return width >= height
      ? { resize: { width: maxUploadImageDimension } }
      : { resize: { height: maxUploadImageDimension } };
  }

  if (width) {
    return width > maxUploadImageDimension
      ? { resize: { width: maxUploadImageDimension } }
      : null;
  }

  if (height) {
    return height > maxUploadImageDimension
      ? { resize: { height: maxUploadImageDimension } }
      : null;
  }

  // Picker-provided dimensions should normally be present. Preserve the existing
  // width cap as a fallback so an unknown, very large image is not uploaded raw.
  return { resize: { width: maxUploadImageDimension } };
}

async function optimizePhoto(
  photo: PlantScanPhoto,
  provider: string,
): Promise<OptimizedPlantScanImage | null> {
  try {
    const originalByteLength = await getOriginalByteLength(photo.uri);
    const resizeAction = getResizeAction(photo);

    // ImageManipulator always writes a new JPEG, which strips source EXIF/GPS
    // metadata before the image bytes leave the device.
    const optimized = await manipulateAsync(
      photo.uri,
      resizeAction ? [resizeAction] : [],
      {
        compress: uploadJpegQuality,
        format: SaveFormat.JPEG,
        base64: true,
      },
    );

    const base64 = optimized.base64 || '';
    if (!base64) {
      warnDev('[PlantIdentification] optimized image was empty', {
        provider,
        role: photo.role,
      });
      return null;
    }

    logDev('[PlantIdentification] optimized image', {
      provider,
      role: photo.role,
      originalByteLength,
      originalBase64Estimate: typeof originalByteLength === 'number'
        ? Math.ceil((originalByteLength / 3) * 4)
        : undefined,
      optimizedBase64Length: base64.length,
      width: optimized.width,
      height: optimized.height,
      maxDimension: maxUploadImageDimension,
      jpegQuality: uploadJpegQuality,
    });

    return {
      role: photo.role,
      sourceUri: photo.uri,
      uploadUri: optimized.uri,
      width: optimized.width,
      height: optimized.height,
      base64,
    };
  } catch (error) {
    warnDev('[PlantIdentification] image optimization failed', {
      provider,
      role: photo.role,
      message: error instanceof Error ? error.message : String(error),
    });
    return null;
  }
}

export async function optimizePlantScanImages(
  input: PlantIdentificationInput,
  provider: string,
  maximumImages = Number.POSITIVE_INFINITY,
): Promise<OptimizedPlantScanImage[]> {
  const normalizedMaximum = Number.isFinite(maximumImages)
    ? Math.max(0, Math.floor(maximumImages))
    : Number.POSITIVE_INFINITY;
  const photos = getPlantScanPhotos(input).slice(0, normalizedMaximum);
  const optimizedImages: OptimizedPlantScanImage[] = [];

  // Process sequentially to avoid holding several decoded full-resolution images
  // and several base64 copies in memory at the same time on lower-end devices.
  for (const photo of photos) {
    const optimizedImage = await optimizePhoto(photo, provider);
    if (optimizedImage) optimizedImages.push(optimizedImage);
  }

  return optimizedImages;
}


export function alignInputWithOptimizedImages(
  input: PlantIdentificationInput,
  images: readonly OptimizedPlantScanImage[],
): PlantIdentificationInput {
  if (images.length === 0) return input;

  const originalPhotos = getPlantScanPhotos(input);
  const alreadyAligned = originalPhotos.length === images.length
    && originalPhotos.every((photo, index) => photo.uri === images[index]?.sourceUri);
  if (alreadyAligned) return input;

  const alignedPhotos: PlantScanPhoto[] = images.map(image => ({
    role: image.role,
    uri: image.sourceUri,
    ...(isPositiveFiniteNumber(image.width) ? { width: image.width } : {}),
    ...(isPositiveFiniteNumber(image.height) ? { height: image.height } : {}),
  }));

  return {
    ...input,
    photoUri: alignedPhotos[0]?.uri || input.photoUri,
    photoUris: alignedPhotos.map(photo => photo.uri),
    photos: alignedPhotos,
  };
}
