/**
 * Capture-time sensor context for environment/setup/placement photos.
 *
 * Heading and altitude are best-effort metadata. These helpers never request a
 * permission and never allow a slow sensor/location read to block photo capture
 * indefinitely. Gallery imports should continue to omit this metadata.
 */
import * as Location from 'expo-location';
import type { GardenContextPhoto } from '../types/garden';

export interface CaptureSensorContext {
  heading?: number;
  altitude?: number;
}

const HEADING_TIMEOUT_MS = 2_500;
const ALTITUDE_TIMEOUT_MS = 3_500;
const MAX_ALTITUDE_ACCURACY_METERS = 100;

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

function withTimeout<T>(operation: Promise<T>, timeoutMs: number): Promise<T | undefined> {
  return new Promise(resolve => {
    let settled = false;
    const finish = (value?: T): void => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      resolve(value);
    };
    const timer = setTimeout(() => finish(undefined), timeoutMs);
    operation.then(value => finish(value), () => finish(undefined));
  });
}

/** Read the current compass heading, normalized to the range [0, 360). */
export async function readHeading(): Promise<number | undefined> {
  try {
    const heading = await withTimeout(Location.getHeadingAsync(), HEADING_TIMEOUT_MS);
    if (!heading) return undefined;

    const trueHeading = finiteNumber(heading.trueHeading);
    const magneticHeading = finiteNumber(heading.magHeading);
    const selectedHeading = trueHeading != null && trueHeading >= 0 ? trueHeading : magneticHeading;
    return selectedHeading == null ? undefined : ((selectedHeading % 360) + 360) % 360;
  } catch {
    return undefined;
  }
}

/**
 * Read altitude only when foreground location permission is already granted.
 * `mayShowUserSettingsDialog: false` preserves the no-mid-flow-prompt contract.
 */
export async function readAltitude(): Promise<number | undefined> {
  try {
    const permission = await withTimeout(Location.getForegroundPermissionsAsync(), ALTITUDE_TIMEOUT_MS);
    if (!permission || (permission.status !== 'granted' && !permission.granted)) return undefined;

    const location = await withTimeout(
      Location.getCurrentPositionAsync({
        accuracy: Location.Accuracy.Balanced,
        mayShowUserSettingsDialog: false,
      }),
      ALTITUDE_TIMEOUT_MS,
    );
    if (!location) return undefined;

    const altitude = finiteNumber(location.coords.altitude);
    const altitudeAccuracy = finiteNumber(location.coords.altitudeAccuracy);
    if (altitude == null) return undefined;
    if (altitudeAccuracy != null && (altitudeAccuracy < 0 || altitudeAccuracy > MAX_ALTITUDE_ACCURACY_METERS)) return undefined;
    return altitude;
  } catch {
    return undefined;
  }
}

/** Read heading and altitude concurrently for a live camera capture. */
export async function captureSensorContext(): Promise<CaptureSensorContext> {
  const [heading, altitude] = await Promise.all([readHeading(), readAltitude()]);
  return {
    ...(heading != null ? { heading } : {}),
    ...(altitude != null ? { altitude } : {}),
  };
}

/** Build the per-photo metadata payload for a live camera capture. */
export async function makeCameraContextExtra(): Promise<Partial<Pick<GardenContextPhoto, 'source' | 'heading' | 'altitude'>>> {
  const { heading, altitude } = await captureSensorContext();
  return {
    source: 'camera',
    ...(heading != null ? { heading } : {}),
    ...(altitude != null ? { altitude } : {}),
  };
}
