export function lfoWave(shape, phase) {
  const normalizedPhase = phase - Math.floor(phase);
  switch (shape) {
    case 'triangle':
      return normalizedPhase < 0.5
        ? normalizedPhase * 4 - 1
        : 3 - normalizedPhase * 4;
    case 'square':
      return normalizedPhase < 0.5 ? 1 : -1;
    case 'sawtooth':
      return normalizedPhase * 2 - 1;
    case 'pulse': {
      const pulsePhase = normalizedPhase * 4;
      return pulsePhase < 1 ? (1 - pulsePhase * pulsePhase) * 2 - 1 : -1;
    }
    case 'sine':
    default:
      return Math.sin(normalizedPhase * Math.PI * 2);
  }
}

export function lfoOffset(lfo, time, paramRange, bpm = 129.2) {
  if (!lfo?.enabled || !Number.isFinite(bpm) || bpm <= 0) return null;
  const frequency = Number(lfo.frequency);
  if (!Number.isFinite(frequency) || frequency <= 0) return null;
  const cycleDuration = (60 / bpm) * frequency;
  const phase = time / cycleDuration + (lfo.phase || 0);
  const range = paramRange.max - paramRange.min;
  const amplitude = (lfo.amplitude ?? 0.5) * range / 2;
  return lfoWave(lfo.shape, phase) * amplitude;
}

export function calculateLfoValue(lfo, time, paramRange, bpm = 129.2) {
  const offset = lfoOffset(lfo, time, paramRange, bpm);
  if (offset === null) return null;
  return (paramRange.max + paramRange.min) / 2 + offset;
}

export function combineAnimatedValue(keyframeValue, lfo, time, paramRange, bpm = 129.2) {
  const lfoValue = calculateLfoValue(lfo, time, paramRange, bpm);
  if (lfoValue === null) return keyframeValue;
  if (keyframeValue === null) return lfoValue;
  return keyframeValue + lfoOffset(lfo, time, paramRange, bpm);
}

