/**
 * Context-photo utilities for PixieSprout.
 *
 * These helpers deliberately accept legacy/unknown persisted values at the
 * normalization boundary. Everything returned from that boundary is safe for
 * current consumers to render and store.
 */
import type { GardenContextPhoto, PlantSetupProfile, RoomProfile } from '../types/garden';
import { isIndoorSpaceType, isOutdoorSpaceType } from './formatters/spaceFormatters';

export const maxContextPhotos = 5;

let generatedPhotoSequence = 0;

function nonBlankString(value: unknown): string | undefined {
  if (typeof value !== 'string') return undefined;
  const clean = value.trim();
  return clean || undefined;
}

function finiteNumber(value: unknown): number | undefined {
  return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}

function normalizeHeading(value: unknown): number | undefined {
  const heading = finiteNumber(value);
  return heading == null ? undefined : ((heading % 360) + 360) % 360;
}

function stableUriHash(uri: string): string {
  // FNV-1a, kept in unsigned 32-bit space for deterministic legacy IDs.
  let hash = 0x811c9dc5;
  for (let index = 0; index < uri.length; index += 1) {
    hash ^= uri.charCodeAt(index);
    hash = Math.imul(hash, 0x01000193);
  }
  return `${(hash >>> 0).toString(36)}_${uri.length.toString(36)}`;
}

function makeGeneratedPhotoId(): string {
  generatedPhotoSequence = (generatedPhotoSequence + 1) % Number.MAX_SAFE_INTEGER;
  return `photo_${Date.now()}_${generatedPhotoSequence}_${Math.floor(Math.random() * 0x100000).toString(36)}`;
}

function uniquePhotoId(preferredId: unknown, uri: string, usedIds: Set<string>): string {
  const baseId = nonBlankString(preferredId) || `photo_${stableUriHash(uri)}`;
  let id = baseId;
  let suffix = 2;
  while (usedIds.has(id)) {
    id = `${baseId}_${suffix}`;
    suffix += 1;
  }
  usedIds.add(id);
  return id;
}

export function makeContextPhoto(
  uri: string,
  note?: string,
  extra?: Partial<Pick<GardenContextPhoto, 'source' | 'heading' | 'altitude'>>,
): GardenContextPhoto {
  const cleanUri = nonBlankString(uri) || '';
  const cleanNote = nonBlankString(note);
  const heading = normalizeHeading(extra?.heading);
  const altitude = finiteNumber(extra?.altitude);

  return {
    id: makeGeneratedPhotoId(),
    uri: cleanUri,
    ...(cleanNote ? { note: cleanNote } : {}),
    createdAt: new Date().toISOString(),
    ...(extra?.source ? { source: extra.source } : {}),
    ...(heading != null ? { heading } : {}),
    ...(altitude != null ? { altitude } : {}),
  };
}

export function normalizeContextPhotos(value?: unknown, legacyUris: unknown[] = []): GardenContextPhoto[] {
  const photos: GardenContextPhoto[] = [];
  const seenUris = new Set<string>();
  const usedIds = new Set<string>();
  const fallbackCreatedAt = new Date().toISOString();

  const addPhoto = (raw: unknown): void => {
    if (photos.length >= maxContextPhotos) return;

    let uri: unknown;
    let note: unknown;
    let createdAt: unknown;
    let id: unknown;
    let source: unknown;
    let heading: unknown;
    let altitude: unknown;

    if (typeof raw === 'string') {
      uri = raw;
    } else if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
      const record = raw as Record<string, unknown>;
      uri = record.uri;
      note = record.note;
      createdAt = record.createdAt;
      id = record.id;
      source = record.source;
      heading = record.heading;
      altitude = record.altitude;
    } else {
      return;
    }

    const cleanUri = nonBlankString(uri);
    if (!cleanUri || seenUris.has(cleanUri)) return;

    seenUris.add(cleanUri);
    const cleanNote = nonBlankString(note);
    const cleanCreatedAt = nonBlankString(createdAt) || fallbackCreatedAt;
    const cleanSource = nonBlankString(source) as GardenContextPhoto['source'] | undefined;
    const cleanHeading = normalizeHeading(heading);
    const cleanAltitude = finiteNumber(altitude);

    photos.push({
      id: uniquePhotoId(id, cleanUri, usedIds),
      uri: cleanUri,
      ...(cleanNote ? { note: cleanNote } : {}),
      createdAt: cleanCreatedAt,
      ...(cleanSource ? { source: cleanSource } : {}),
      ...(cleanHeading != null ? { heading: cleanHeading } : {}),
      ...(cleanAltitude != null ? { altitude: cleanAltitude } : {}),
    });
  };

  const visitCurrentValue = (raw: unknown): void => {
    if (photos.length >= maxContextPhotos) return;
    if (Array.isArray(raw)) {
      raw.forEach(visitCurrentValue);
      return;
    }
    addPhoto(raw);
  };

  const visitLegacyUri = (raw: unknown): void => {
    if (photos.length >= maxContextPhotos) return;
    if (Array.isArray(raw)) {
      raw.forEach(visitLegacyUri);
      return;
    }
    if (typeof raw === 'string') addPhoto(raw);
  };

  visitCurrentValue(value);
  visitLegacyUri(legacyUris);
  return photos;
}

export function contextPhotoUris(photos?: GardenContextPhoto[]): string[] {
  if (!Array.isArray(photos)) return [];
  return photos.flatMap(photo => {
    const uri = nonBlankString(photo?.uri);
    return uri ? [uri] : [];
  });
}

export function formatPhotoCount(photos: GardenContextPhoto[]): string {
  const count = Array.isArray(photos) ? photos.length : 0;
  if (!count) return 'Missing';
  return `${count} ${count === 1 ? 'photo' : 'photos'}`;
}

export function hasPlantSetupPhoto(setup?: PlantSetupProfile | null): boolean {
  return normalizeContextPhotos(setup?.setupPhotos, [setup?.setupPhotoUri, setup?.photoUris]).length > 0;
}

export function hasSpaceEnvironmentPhoto(room?: RoomProfile | null): boolean {
  return normalizeContextPhotos(room?.environmentPhotos, [room?.environmentPhotoUri, room?.photoUri]).length > 0;
}

export function isSpaceEnvironmentComplete(room?: RoomProfile | null): boolean {
  if (!room) return false;

  if (isOutdoorSpaceType(room.spaceType)) {
    const hasSunTiming = Array.isArray(room.outdoorSunTimings)
      && room.outdoorSunTimings.some(value => Boolean(value && value !== ('unsure' as typeof value)));
    const lightQuality = nonBlankString(room.outdoorLightQuality);
    return Boolean(hasSunTiming && lightQuality && lightQuality !== 'unsure');
  }

  if (!isIndoorSpaceType(room.spaceType)) return false;

  const directions = Array.isArray(room.windowDirections) ? room.windowDirections : [];
  const hasWindowContext = directions.some(direction => Boolean(direction && direction !== 'unsure'));
  const lightProfile = nonBlankString(room.lightProfile);
  return Boolean(hasWindowContext && lightProfile && lightProfile !== 'unsure');
}
