import Foundation

enum PeakKind {
    case freeCl, totalCl, crossover
}

struct LsvPeak {
    var vMv: Float
    var iUa: Float
    var kind: PeakKind
}

func smoothLsv(_ data: [Float], window: Int) -> [Float] {
    let n = data.count
    if n == 0 || window < 2 { return data }
    let half = window / 2
    var out = [Float](repeating: 0, count: n)
    for i in 0..<n {
        let lo = max(0, i - half)
        let hi = min(n - 1, i + half)
        var sum: Float = 0
        for j in lo...hi { sum += data[j] }
        out[i] = sum / Float(hi - lo + 1)
    }
    return out
}

func findExtrema(_ v: [Float], _ iSmooth: [Float], minProminence: Float) -> [(Int, Bool)] {
    let n = iSmooth.count
    if n < 3 { return [] }

    var candidates: [(Int, Bool)] = []
    for i in 1..<(n - 1) {
        let prev = iSmooth[i - 1]
        let curr = iSmooth[i]
        let next = iSmooth[i + 1]
        if curr > prev && curr > next {
            candidates.append((i, true))
        } else if curr < prev && curr < next {
            candidates.append((i, false))
        }
    }

    return candidates.filter { (idx, isMax) in
        let val = iSmooth[idx]
        let leftSlice = iSmooth[..<idx]
        let rightSlice = iSmooth[(idx + 1)...]
        let leftBound: Float
        let rightBound: Float
        if isMax {
            leftBound = leftSlice.min() ?? val
            rightBound = rightSlice.min() ?? val
            return val - max(leftBound, rightBound) >= minProminence
        } else {
            leftBound = leftSlice.max() ?? val
            rightBound = rightSlice.max() ?? val
            return min(leftBound, rightBound) - val >= minProminence
        }
    }
}

/// Detect Q/HQ redox peak in the -100 to +600 mV window.
/// Returns peak voltage in mV if found.
func detectQhqPeak(_ points: [LsvPoint]) -> Float? {
    if points.count < 5 { return nil }

    let iVals = points.map { $0.iUa }
    let vVals = points.map { $0.vMv }

    let window = max(5, points.count / 50)
    let smoothed = smoothLsv(iVals, window: window)

    guard let iMin = smoothed.min(), let iMax = smoothed.max() else { return nil }
    let prominence = (iMax - iMin) * 0.05

    let extrema = findExtrema(vVals, smoothed, minProminence: prominence)

    let candidates = extrema
        .filter { $0.1 && vVals[$0.0] >= -100 && vVals[$0.0] <= 600 }
        .max(by: { smoothed[$0.0] < smoothed[$1.0] })

    if let (idx, _) = candidates {
        return vVals[idx]
    }
    return nil
}

func detectLsvPeaks(_ points: [LsvPoint]) -> [LsvPeak] {
    if points.count < 5 { return [] }

    let iVals = points.map { $0.iUa }
    let vVals = points.map { $0.vMv }

    let window = max(5, points.count / 50)
    let smoothed = smoothLsv(iVals, window: window)

    guard let iMin = smoothed.min(), let iMax = smoothed.max() else { return [] }
    let prominence = (iMax - iMin) * 0.05

    let extrema = findExtrema(vVals, smoothed, minProminence: prominence)

    var peaks: [LsvPeak] = []

    // crossover: where current changes sign
    for i in 1..<smoothed.count {
        let prev = smoothed[i - 1]
        let curr = smoothed[i]
        let crossed = (prev > 0 && curr < 0) || (prev < 0 && curr > 0)
        if crossed {
            let aPrev = abs(prev)
            let aCurr = abs(curr)
            let frac = aPrev / (aPrev + aCurr)
            let dv = vVals[i] - vVals[i - 1]
            let vCross = vVals[i - 1] + frac * dv
            peaks.append(LsvPeak(vMv: vCross, iUa: 0, kind: .crossover))
            break
        }
    }

    // largest peak in positive voltage region -> freeCl
    let freeCl = extrema
        .filter { $0.1 && vVals[$0.0] >= 0 }
        .max(by: { smoothed[$0.0] < smoothed[$1.0] })
    if let (idx, _) = freeCl {
        peaks.append(LsvPeak(vMv: vVals[idx], iUa: smoothed[idx], kind: .freeCl))
    }

    // largest peak in negative voltage region -> totalCl
    let totalCl = extrema
        .filter { $0.1 && vVals[$0.0] < 0 }
        .max(by: { smoothed[$0.0] < smoothed[$1.0] })
    if let (idx, _) = totalCl {
        peaks.append(LsvPeak(vMv: vVals[idx], iUa: smoothed[idx], kind: .totalCl))
    }

    return peaks
}
