/**
 * Shared audio-analysis contract.
 *
 * Values in track_audio_features are deliberately modest: they are inputs to
 * recommendation heuristics, not a claim that we can infer every Spotify-like
 * descriptor from local DSP.  Keep the persisted set restricted to fields we
 * actually compute and validate before it reaches the database.
 */
export const AUDIO_ANALYSIS_VERSION = 2;

export const AUDIO_ANALYSIS_FEATURES = ['bpm', 'key', 'energy', 'danceability'] as const;
export type AudioAnalysisFeature = typeof AUDIO_ANALYSIS_FEATURES[number];

export const AUDIO_ANALYSIS_JOB_OPTIONS = {
  attempts: 3,
  backoff: { type: 'exponential', delay: 5_000 },
  removeOnComplete: { age: 7 * 24 * 60 * 60, count: 10_000 },
  removeOnFail: { age: 30 * 24 * 60 * 60, count: 10_000 },
} as const;

const ENERGY_FLOOR_DBFS = -48;
const ENERGY_CEILING_DBFS = -6;

export function clampUnit(value: number): number | null {
  return Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : null;
}

/**
 * Convert Essentia's mean-square sample energy to a perceptually useful 0..1
 * RMS level.  A linear multiplier makes normal mastered music saturate at 1;
 * dBFS preserves the distinction between quiet and loud recordings.
 */
export function normaliseMeanSquareEnergy(meanSquare: number): number | null {
  if (!Number.isFinite(meanSquare) || meanSquare < 0) return null;
  if (meanSquare === 0) return 0;

  const rms = Math.sqrt(meanSquare);
  const dbfs = 20 * Math.log10(rms);
  return clampUnit((dbfs - ENERGY_FLOOR_DBFS) / (ENERGY_CEILING_DBFS - ENERGY_FLOOR_DBFS));
}

export function normaliseDanceability(value: number): number | null {
  if (!Number.isFinite(value) || value < 0) return null;
  // Essentia Danceability is normally 0..3. Values outside that range are
  // clamped rather than allowed to poison downstream scoring.
  return clampUnit(value / 3);
}

export function validBpm(value: number | null | undefined): number | null {
  if (!Number.isFinite(value) || value == null || value < 30 || value > 300) return null;
  return Math.round(value * 10) / 10;
}

export function validKey(value: string | null | undefined): string | null {
  if (!value) return null;
  const key = value.trim();
  // Keep tag conventions such as "8A" intact, but reject malformed or
  // unexpectedly large values before persisting them.
  return key.length > 0 && key.length <= 32 ? key : null;
}

export function audioAnalysisJobId(trackId: string): string {
  return `audio-${trackId}`;
}
