use crate::protocol::LsvPoint;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PeakKind {
    FreeCl,
    TotalCl,
    Crossover,
}

#[derive(Debug, Clone)]
pub struct LsvPeak {
    pub v_mv: f32,
    pub i_ua: f32,
    pub kind: PeakKind,
}

pub fn smooth(data: &[f32], window: usize) -> Vec<f32> {
    let n = data.len();
    if n == 0 || window < 2 {
        return data.to_vec();
    }
    let half = window / 2;
    let mut out = Vec::with_capacity(n);
    for i in 0..n {
        let lo = i.saturating_sub(half);
        let hi = (i + half).min(n - 1);
        let sum: f32 = data[lo..=hi].iter().sum();
        out.push(sum / (hi - lo + 1) as f32);
    }
    out
}

pub fn find_extrema(v: &[f32], i_smooth: &[f32], min_prominence: f32) -> Vec<(usize, bool)> {
    let n = i_smooth.len();
    if n < 3 {
        return Vec::new();
    }
    let mut candidates: Vec<(usize, bool)> = Vec::new();
    for i in 1..n - 1 {
        let prev = i_smooth[i - 1];
        let curr = i_smooth[i];
        let next = i_smooth[i + 1];
        if curr > prev && curr > next {
            candidates.push((i, true));
        } else if curr < prev && curr < next {
            candidates.push((i, false));
        }
    }

    candidates.retain(|&(idx, is_max)| {
        let val = i_smooth[idx];
        let left_bound = i_smooth[..idx].iter().copied()
            .reduce(if is_max { f32::min } else { f32::max })
            .unwrap_or(val);
        let right_bound = i_smooth[idx + 1..].iter().copied()
            .reduce(if is_max { f32::min } else { f32::max })
            .unwrap_or(val);
        let prom = if is_max {
            val - left_bound.max(right_bound)
        } else {
            left_bound.min(right_bound) - val
        };
        prom >= min_prominence
    });

    let _ = v; // voltage array available for future use
    candidates
}

pub fn detect_peaks(points: &[LsvPoint]) -> Vec<LsvPeak> {
    if points.len() < 5 {
        return Vec::new();
    }

    let i_vals: Vec<f32> = points.iter().map(|p| p.i_ua).collect();
    let v_vals: Vec<f32> = points.iter().map(|p| p.v_mv).collect();

    let window = 5.max(points.len() / 50);
    let smoothed = smooth(&i_vals, window);

    let i_min = smoothed.iter().copied().fold(f32::INFINITY, f32::min);
    let i_max = smoothed.iter().copied().fold(f32::NEG_INFINITY, f32::max);
    let prominence = (i_max - i_min) * 0.05;

    let extrema = find_extrema(&v_vals, &smoothed, prominence);

    let mut peaks: Vec<LsvPeak> = Vec::new();

    // find crossover: where current changes sign
    for i in 1..smoothed.len() {
        if smoothed[i - 1].signum() != smoothed[i].signum() && smoothed[i - 1].signum() != 0.0 {
            let frac = smoothed[i - 1].abs() / (smoothed[i - 1].abs() + smoothed[i].abs());
            let v_cross = v_vals[i - 1] + frac * (v_vals[i] - v_vals[i - 1]);
            peaks.push(LsvPeak {
                v_mv: v_cross,
                i_ua: 0.0,
                kind: PeakKind::Crossover,
            });
            break;
        }
    }

    // largest peak in positive voltage region -> FreeCl
    let free_cl = extrema.iter()
        .filter(|&&(idx, is_max)| is_max && v_vals[idx] >= 0.0)
        .max_by(|a, b| smoothed[a.0].partial_cmp(&smoothed[b.0]).unwrap_or(std::cmp::Ordering::Equal));
    if let Some(&(idx, _)) = free_cl {
        peaks.push(LsvPeak {
            v_mv: v_vals[idx],
            i_ua: smoothed[idx],
            kind: PeakKind::FreeCl,
        });
    }

    // largest peak in negative voltage region -> TotalCl
    let total_cl = extrema.iter()
        .filter(|&&(idx, is_max)| is_max && v_vals[idx] < 0.0)
        .max_by(|a, b| smoothed[a.0].partial_cmp(&smoothed[b.0]).unwrap_or(std::cmp::Ordering::Equal));
    if let Some(&(idx, _)) = total_cl {
        peaks.push(LsvPeak {
            v_mv: v_vals[idx],
            i_ua: smoothed[idx],
            kind: PeakKind::TotalCl,
        });
    }

    peaks
}
