import SwiftUI
import Charts

struct ChlorineView: View {
    @Bindable var state: AppState

    var body: some View {
        VStack(spacing: 0) {
            controlsRow
            clPeakLabels
            Divider()
            GeometryReader { geo in
                if geo.size.width > 700 {
                    HSplitLayout(ratio: 0.55) {
                        VStack(spacing: 4) {
                            voltammogramPlot
                            resultBanner
                            chlorinePlot
                        }
                    } trailing: {
                        clTable
                    }
                } else {
                    ScrollView {
                        VStack(spacing: 12) {
                            voltammogramPlot.frame(height: 250)
                            resultBanner
                            chlorinePlot.frame(height: 250)
                            clTable.frame(height: 300)
                        }
                        .padding()
                    }
                }
            }
        }
    }

    // MARK: - Controls

    private var controlsRow: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 8) {
                if state.clManualPeaks {
                    Button("Start LSV") { state.startLSV() }
                        .buttonStyle(ActionButtonStyle(color: .green))

                    Button("Manual") {
                        state.clManualPeaks = false
                        state.lsvPeaks = detectLsvPeaks(state.lsvPoints)
                    }
                    .font(.caption)

                    Divider().frame(height: 24)

                    LabeledField("Cond mV", text: $state.clCondV, width: 70)
                    LabeledField("Cond ms", text: $state.clCondT, width: 70)
                    LabeledField("Free mV", text: $state.clFreeV, width: 70)
                    LabeledField("Total mV", text: $state.clTotalV, width: 70)
                    LabeledField("Settle ms", text: $state.clDepT, width: 70)
                    LabeledField("Meas ms", text: $state.clMeasT, width: 70)

                    LabeledPicker("RTIA", selection: $state.clRtia, items: LpRtia.allCases) { $0.label }
                        .frame(width: 120)

                    Button("Measure") { state.startChlorine() }
                        .buttonStyle(ActionButtonStyle(color: .green))
                } else {
                    Button("Start Auto") { state.startClAuto() }
                        .buttonStyle(ActionButtonStyle(color: .green))
                        .disabled(state.clAutoState != .idle)

                    Button("Auto") {
                        state.clManualPeaks = true
                        state.lsvPeaks.removeAll()
                    }
                    .font(.caption)

                    LabeledPicker("RTIA", selection: $state.clRtia, items: LpRtia.allCases) { $0.label }
                        .frame(width: 120)

                    if let pots = state.clAutoPotentials {
                        Text(String(format: "free=%.0f%@ total=%.0f%@",
                                    pots.vFree,
                                    pots.vFreeDetected ? "" : "?",
                                    pots.vTotal,
                                    pots.vTotalDetected ? "" : "?"))
                            .font(.caption.monospacedDigit())
                            .foregroundStyle(.secondary)
                    }
                }
            }
            .padding(.horizontal)
            .padding(.vertical, 8)
        }
    }

    // MARK: - Result banner

    @ViewBuilder
    private var resultBanner: some View {
        if let r = state.clResult {
            HStack(spacing: 16) {
                Text(String(format: "Free: %.3f uA", r.iFreeUa))
                    .foregroundStyle(Color(red: 0.2, green: 1, blue: 0.5))
                Text(String(format: "Total: %.3f uA", r.iTotalUa))
                    .foregroundStyle(Color(red: 1, green: 0.6, blue: 0.2))
                Text(String(format: "Combined: %.3f uA", r.iTotalUa - r.iFreeUa))
                    .foregroundStyle(.secondary)

                if let f = state.clFactor {
                    let ppm = f * Double(abs(r.iFreeUa))
                    Text(String(format: "Free Cl: %.2f ppm", ppm))
                        .foregroundStyle(.cyan)
                }

                if let (_, refR) = state.clRef {
                    Divider().frame(height: 16)
                    Text(String(format: "vs Ref: dFree=%.3f dTotal=%.3f",
                                r.iFreeUa - refR.iFreeUa,
                                r.iTotalUa - refR.iTotalUa))
                        .foregroundStyle(.secondary)
                }
            }
            .font(.subheadline.monospacedDigit())
            .padding(.horizontal)
            .padding(.vertical, 4)
        }
    }

    // MARK: - Peak labels

    @ViewBuilder
    private var clPeakLabels: some View {
        if !state.lsvPeaks.isEmpty {
            HStack(spacing: 12) {
                ForEach(Array(state.lsvPeaks.enumerated()), id: \.offset) { _, peak in
                    let label: String = {
                        switch peak.kind {
                        case .freeCl:   return String(format: "Free: %.0fmV %.2fuA", peak.vMv, peak.iUa)
                        case .totalCl:  return String(format: "Total: %.0fmV %.2fuA", peak.vMv, peak.iUa)
                        case .crossover: return String(format: "X-over: %.0fmV", peak.vMv)
                        }
                    }()
                    Text(label)
                        .font(.caption)
                        .foregroundStyle(clPeakColor(peak.kind))
                }
            }
            .padding(.horizontal, 8)
            .padding(.vertical, 4)
        }
    }

    private func clPeakColor(_ kind: PeakKind) -> Color {
        switch kind {
        case .freeCl:   .green
        case .totalCl:  .orange
        case .crossover: .purple
        }
    }

    // MARK: - Voltammogram

    private var voltammogramPlot: some View {
        Group {
            if state.lsvPoints.isEmpty {
                Text("No LSV data")
                    .foregroundStyle(Color(white: 0.4))
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color.black.opacity(0.3))
            } else {
                PlotContainer(title: "") {
                    Chart {
                        if let ref = state.lsvRef {
                            ForEach(Array(ref.enumerated()), id: \.offset) { _, pt in
                                LineMark(x: .value("V", Double(pt.vMv)), y: .value("I", Double(pt.iUa)))
                                    .foregroundStyle(Color.gray.opacity(0.5))
                                    .lineStyle(StrokeStyle(lineWidth: 1.5))
                            }
                        }
                        ForEach(Array(state.lsvPoints.enumerated()), id: \.offset) { _, pt in
                            LineMark(x: .value("V", Double(pt.vMv)), y: .value("I", Double(pt.iUa)))
                                .foregroundStyle(Color.yellow)
                                .lineStyle(StrokeStyle(lineWidth: 2))
                        }
                        ForEach(Array(state.lsvPoints.enumerated()), id: \.offset) { _, pt in
                            PointMark(x: .value("V", Double(pt.vMv)), y: .value("I", Double(pt.iUa)))
                                .foregroundStyle(Color.yellow)
                                .symbolSize(16)
                        }
                        ForEach(Array(state.lsvPeaks.enumerated()), id: \.offset) { _, peak in
                            PointMark(x: .value("V", Double(peak.vMv)), y: .value("I", Double(peak.iUa)))
                                .foregroundStyle(clPeakColor(peak.kind))
                                .symbolSize(100)
                                .symbol(.diamond)
                            RuleMark(x: .value("V", Double(peak.vMv)))
                                .foregroundStyle(clPeakColor(peak.kind).opacity(0.3))
                                .lineStyle(StrokeStyle(lineWidth: 1, dash: [4, 4]))
                        }
                    }
                    .chartXAxisLabel("V (mV)")
                    .chartYAxisLabel("I (uA)", position: .leading)
                    .chartXAxis {
                        AxisMarks { _ in
                            AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5))
                                .foregroundStyle(Color.gray.opacity(0.3))
                            AxisValueLabel().font(.caption2).foregroundStyle(.secondary)
                        }
                    }
                    .chartYAxis {
                        AxisMarks(position: .leading) { _ in
                            AxisGridLine(stroke: StrokeStyle(lineWidth: 0.5))
                                .foregroundStyle(Color.gray.opacity(0.3))
                            AxisValueLabel().font(.caption2).foregroundStyle(Color.yellow)
                        }
                    }
                    .padding(8)
                }
            }
        }
        .background(Color.black.opacity(0.3))
        .clipShape(RoundedRectangle(cornerRadius: 8))
    }

    // MARK: - Chlorine plot

    private var chlorinePlot: some View {
        Group {
            if state.clPoints.isEmpty {
                Text("No data")
                    .foregroundStyle(Color(white: 0.4))
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color.black.opacity(0.3))
            } else {
                PlotContainer(title: "") {
                    chlorineCanvas
                }
            }
        }
        .background(Color.black.opacity(0.3))
        .clipShape(RoundedRectangle(cornerRadius: 8))
    }

    private var chlorineCanvas: some View {
        Canvas { context, size in
            let ml: CGFloat = 50, mr: CGFloat = 12, mt: CGFloat = 12, mb: CGFloat = 22
            let xl = ml, xr = size.width - mr
            let yt = mt, yb = size.height - mb

            let valid = state.clPoints.filter { $0.tMs.isFinite && $0.iUa.isFinite }
            guard !valid.isEmpty else { return }

            let ts = valid.map { CGFloat($0.tMs) }
            let is_ = valid.map { CGFloat($0.iUa) }
            let (tMin, tMax) = (ts.min()!, ts.max()!)
            let (iMin, iMax) = (is_.min()!, is_.max()!)
            let tPad = max(tMax - tMin, 100) * 0.05
            let iPad = max(iMax - iMin, 0.001) * 0.12
            let xvLo = tMin - tPad, xvHi = tMax + tPad
            let yvLo = iMin - iPad, yvHi = iMax + iPad

            func lx(_ v: CGFloat) -> CGFloat { xl + (v - xvLo) / (xvHi - xvLo) * (xr - xl) }
            func ly(_ v: CGFloat) -> CGFloat { yt + (yvHi - v) / (yvHi - yvLo) * (yb - yt) }

            // grid
            let gridColor = Color(white: 0.25).opacity(0.6)
            drawClGrid(context: context, xl: xl, xr: xr, yt: yt, yb: yb,
                       xvLo: xvLo, xvHi: xvHi, yvLo: yvLo, yvHi: yvHi, gridColor: gridColor)

            // axis labels
            context.draw(
                Text("I (uA)").font(.caption2).foregroundStyle(.secondary),
                at: CGPoint(x: 20, y: yt - 2))
            context.draw(
                Text("t (ms)").font(.caption2).foregroundStyle(.secondary),
                at: CGPoint(x: (xl + xr) / 2, y: yb + 12))

            // reference
            if let refPts = state.clRef?.points {
                let refFree = refPts.filter { $0.phase == 1 }
                    .map { CGPoint(x: lx(CGFloat($0.tMs)), y: ly(CGFloat($0.iUa))) }
                let refTotal = refPts.filter { $0.phase == 2 }
                    .map { CGPoint(x: lx(CGFloat($0.tMs)), y: ly(CGFloat($0.iUa))) }
                drawClPolyline(context: context, points: refFree, color: Color.gray.opacity(0.5), width: 1.5)
                drawClPolyline(context: context, points: refTotal, color: Color.gray.opacity(0.5), width: 1.5)
            }

            let clFreeColor = Color(red: 0.2, green: 1, blue: 0.5)
            let clTotalColor = Color(red: 1, green: 0.6, blue: 0.2)
            let condColor = Color.gray

            // conditioning
            let condPts = valid.filter { $0.phase == 0 }
                .map { CGPoint(x: lx(CGFloat($0.tMs)), y: ly(CGFloat($0.iUa))) }
            drawClPolyline(context: context, points: condPts, color: condColor, width: 1.5)

            // free
            let freePts = valid.filter { $0.phase == 1 }
                .map { CGPoint(x: lx(CGFloat($0.tMs)), y: ly(CGFloat($0.iUa))) }
            drawClPolyline(context: context, points: freePts, color: clFreeColor, width: 2)
            for pt in freePts {
                context.fill(Path(ellipseIn: CGRect(x: pt.x - 3, y: pt.y - 3, width: 6, height: 6)),
                             with: .color(clFreeColor))
            }

            // total
            let totalPts = valid.filter { $0.phase == 2 }
                .map { CGPoint(x: lx(CGFloat($0.tMs)), y: ly(CGFloat($0.iUa))) }
            drawClPolyline(context: context, points: totalPts, color: clTotalColor, width: 2)
            for pt in totalPts {
                context.fill(Path(ellipseIn: CGRect(x: pt.x - 3, y: pt.y - 3, width: 6, height: 6)),
                             with: .color(clTotalColor))
            }

            // phase boundary
            if let lastFree = valid.last(where: { $0.phase == 1 }),
               let firstTotal = valid.first(where: { $0.phase == 2 }) {
                let bx = lx(CGFloat((lastFree.tMs + firstTotal.tMs) / 2))
                if bx > xl && bx < xr {
                    var bp = Path()
                    bp.move(to: CGPoint(x: bx, y: yt))
                    bp.addLine(to: CGPoint(x: bx, y: yb))
                    context.stroke(bp, with: .color(Color(white: 0.4)), lineWidth: 1)
                }
            }

            // legend
            context.draw(
                Text("Free").font(.caption2).foregroundStyle(clFreeColor),
                at: CGPoint(x: xl + 25, y: yt + 10))
            context.draw(
                Text("Total").font(.caption2).foregroundStyle(clTotalColor),
                at: CGPoint(x: xl + 70, y: yt + 10))
        }
    }

    // MARK: - Table

    private var clTable: some View {
        MeasurementTable(
            columns: [
                MeasurementColumn(header: "t (ms)", width: 80, alignment: .trailing),
                MeasurementColumn(header: "I (uA)", width: 90, alignment: .trailing),
                MeasurementColumn(header: "Phase", width: 60, alignment: .trailing),
            ],
            rows: state.clPoints,
            cellText: { pt, col in
                switch col {
                case 0: String(format: "%.1f", pt.tMs)
                case 1: String(format: "%.3f", pt.iUa)
                case 2:
                    switch pt.phase {
                    case 1: "Free"
                    case 2: "Total"
                    default: "Cond"
                    }
                default: ""
                }
            }
        )
    }
}

// MARK: - Drawing helpers (chlorine-local)

private func drawClPolyline(context: GraphicsContext, points: [CGPoint], color: Color, width: CGFloat) {
    guard points.count >= 2 else { return }
    var path = Path()
    path.move(to: points[0])
    for pt in points.dropFirst() {
        guard pt.x.isFinite && pt.y.isFinite else { continue }
        path.addLine(to: pt)
    }
    context.stroke(path, with: .color(color), lineWidth: width)
}

private func drawClGrid(context: GraphicsContext, xl: CGFloat, xr: CGFloat, yt: CGFloat, yb: CGFloat,
                         xvLo: CGFloat, xvHi: CGFloat, yvLo: CGFloat, yvHi: CGFloat, gridColor: Color) {
    func niceStep(_ range: CGFloat, _ ticks: Int) -> CGFloat {
        guard abs(range) > 1e-10 else { return 1 }
        let rough = range / CGFloat(ticks)
        let mag = pow(10, floor(log10(abs(rough))))
        let norm = abs(rough) / mag
        let s: CGFloat
        if norm < 1.5 { s = 1 } else if norm < 3.5 { s = 2 } else if norm < 7.5 { s = 5 } else { s = 10 }
        return s * mag
    }

    let xs = niceStep(xvHi - xvLo, 5)
    if xs > 0 {
        var g = (xvLo / xs).rounded(.up) * xs
        while g <= xvHi {
            let x = xl + (g - xvLo) / (xvHi - xvLo) * (xr - xl)
            var p = Path(); p.move(to: CGPoint(x: x, y: yt)); p.addLine(to: CGPoint(x: x, y: yb))
            context.stroke(p, with: .color(gridColor), lineWidth: 0.5)
            context.draw(Text(String(format: "%.0f", g)).font(.system(size: 9)).foregroundStyle(.secondary),
                         at: CGPoint(x: x, y: yb + 10))
            g += xs
        }
    }
    let ys = niceStep(yvHi - yvLo, 4)
    if ys > 0 {
        var g = (yvLo / ys).rounded(.up) * ys
        while g <= yvHi {
            let y = yt + (yvHi - g) / (yvHi - yvLo) * (yb - yt)
            var p = Path(); p.move(to: CGPoint(x: xl, y: y)); p.addLine(to: CGPoint(x: xr, y: y))
            context.stroke(p, with: .color(gridColor), lineWidth: 0.5)
            context.draw(Text(String(format: "%.1f", g)).font(.system(size: 9)).foregroundStyle(.secondary),
                         at: CGPoint(x: xl - 20, y: y))
            g += ys
        }
    }
}
