/// Measurement data viewer — switches on type to show appropriate charts.

import SwiftUI
import Charts

// MARK: - Router

struct MeasurementDataView: View {
    let measurement: Measurement

    @State private var points: [DataPoint] = []
    @State private var loaded = false

    var body: some View {
        Group {
            if !loaded {
                ProgressView()
            } else {
                content
            }
        }
        .navigationTitle(typeLabel)
        .onAppear { loadPoints() }
    }

    private func loadPoints() {
        guard let mid = measurement.id else { return }
        points = (try? Storage.shared.fetchDataPoints(measurementId: mid)) ?? []
        loaded = true
    }

    @ViewBuilder
    private var content: some View {
        switch MeasurementType(rawValue: measurement.type) {
        case .eis:
            EisDataView(points: decodePoints(EisPoint.self))
        case .lsv:
            LsvDataView(points: decodePoints(LsvPoint.self))
        case .amp:
            AmpDataView(points: decodePoints(AmpPoint.self))
        case .chlorine:
            ClDataView(
                points: decodePoints(ClPoint.self),
                result: decodeResult(ClResult.self)
            )
        case .ph:
            PhDataView(result: decodeResult(PhResult.self))
        case nil:
            Text("Unknown type: \(measurement.type)")
                .foregroundStyle(.secondary)
        }
    }

    private var typeLabel: String {
        switch measurement.type {
        case "eis": "EIS"
        case "lsv": "LSV"
        case "amp": "Amperometry"
        case "chlorine": "Chlorine"
        case "ph": "pH"
        default: measurement.type
        }
    }

    private func decodePoints<T: Decodable>(_ type: T.Type) -> [T] {
        let decoder = JSONDecoder()
        return points.compactMap { try? decoder.decode(T.self, from: $0.payload) }
    }

    private func decodeResult<T: Decodable>(_ type: T.Type) -> T? {
        guard let data = measurement.resultSummary else { return nil }
        return try? JSONDecoder().decode(T.self, from: data)
    }
}

// MARK: - EIS data view

enum EisPlotMode: String, CaseIterable, Identifiable {
    case nyquist = "Nyquist"
    case bodeMag = "Bode |Z|"
    case bodePhase = "Bode Phase"

    var id: String { rawValue }
}

struct EisDataView: View {
    let points: [EisPoint]
    @State private var plotMode: EisPlotMode = .nyquist
    @State private var showTable = false

    var body: some View {
        VStack(spacing: 0) {
            Picker("Plot", selection: $plotMode) {
                ForEach(EisPlotMode.allCases) { mode in
                    Text(mode.rawValue).tag(mode)
                }
            }
            .pickerStyle(.segmented)
            .padding(.horizontal)
            .padding(.vertical, 8)

            Divider()

            if points.isEmpty {
                noData
            } else {
                plotView
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }

            if showTable && !points.isEmpty {
                Divider()
                eisTable
                    .frame(maxHeight: 250)
            }
        }
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(showTable ? "Hide Table" : "Show Table") {
                    showTable.toggle()
                }
            }
        }
    }

    @ViewBuilder
    private var plotView: some View {
        switch plotMode {
        case .nyquist:
            nyquistChart
                .padding()
        case .bodeMag:
            bodeMagChart
                .padding()
        case .bodePhase:
            bodePhaseChart
                .padding()
        }
    }

    private var nyquistChart: some View {
        Chart {
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                PointMark(
                    x: .value("Z'", Double(pt.zReal)),
                    y: .value("-Z''", Double(-pt.zImag))
                )
                .foregroundStyle(Color(red: 0.4, green: 1, blue: 0.4))
                .symbolSize(20)
            }
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                LineMark(
                    x: .value("Z'", Double(pt.zReal)),
                    y: .value("-Z''", Double(-pt.zImag))
                )
                .foregroundStyle(Color(red: 0.4, green: 1, blue: 0.4).opacity(0.6))
                .lineStyle(StrokeStyle(lineWidth: 1.5))
            }
        }
        .chartXAxisLabel("Z' (Ohm)")
        .chartYAxisLabel("-Z'' (Ohm)")
        .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(.secondary)
            }
        }
    }

    private var bodeMagChart: some View {
        Chart {
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                LineMark(
                    x: .value("Freq", log10(max(Double(pt.freqHz), 1))),
                    y: .value("|Z|", log10(max(Double(pt.magOhms), 0.01)))
                )
                .foregroundStyle(Color.cyan)
                .lineStyle(StrokeStyle(lineWidth: 2))
            }
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                PointMark(
                    x: .value("Freq", log10(max(Double(pt.freqHz), 1))),
                    y: .value("|Z|", log10(max(Double(pt.magOhms), 0.01)))
                )
                .foregroundStyle(Color.cyan)
                .symbolSize(16)
            }
        }
        .chartXAxisLabel("log10(Freq Hz)")
        .chartYAxisLabel("log10(|Z| Ohm)")
        .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(.secondary)
            }
        }
    }

    private var bodePhaseChart: some View {
        Chart {
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                LineMark(
                    x: .value("Freq", log10(max(Double(pt.freqHz), 1))),
                    y: .value("Phase", Double(pt.phaseDeg))
                )
                .foregroundStyle(Color.orange)
                .lineStyle(StrokeStyle(lineWidth: 2))
            }
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                PointMark(
                    x: .value("Freq", log10(max(Double(pt.freqHz), 1))),
                    y: .value("Phase", Double(pt.phaseDeg))
                )
                .foregroundStyle(Color.orange)
                .symbolSize(16)
            }
        }
        .chartXAxisLabel("log10(Freq Hz)")
        .chartYAxisLabel("Phase (deg)")
        .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(.secondary)
            }
        }
    }

    private var eisTable: some View {
        MeasurementTable(
            columns: [
                MeasurementColumn(header: "Freq (Hz)", width: 80, alignment: .trailing),
                MeasurementColumn(header: "|Z| (Ohm)", width: 90, alignment: .trailing),
                MeasurementColumn(header: "Phase", width: 70, alignment: .trailing),
                MeasurementColumn(header: "Re", width: 80, alignment: .trailing),
                MeasurementColumn(header: "Im", width: 80, alignment: .trailing),
            ],
            rows: points,
            cellText: { pt, col in
                switch col {
                case 0: String(format: "%.1f", pt.freqHz)
                case 1: String(format: "%.2f", pt.magOhms)
                case 2: String(format: "%.2f", pt.phaseDeg)
                case 3: String(format: "%.2f", pt.zReal)
                case 4: String(format: "%.2f", pt.zImag)
                default: ""
                }
            }
        )
    }

    private var noData: some View {
        Text("No data points")
            .foregroundStyle(.secondary)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

// MARK: - LSV data view

struct LsvDataView: View {
    let points: [LsvPoint]
    @State private var showTable = false

    var body: some View {
        VStack(spacing: 0) {
            if points.isEmpty {
                noData
            } else {
                ivChart
                    .padding()
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }

            if showTable && !points.isEmpty {
                Divider()
                lsvTable
                    .frame(maxHeight: 250)
            }
        }
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(showTable ? "Hide Table" : "Show Table") {
                    showTable.toggle()
                }
            }
        }
    }

    private var ivChart: some View {
        Chart {
            ForEach(Array(points.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(points.enumerated()), id: \.offset) { _, pt in
                PointMark(
                    x: .value("V", Double(pt.vMv)),
                    y: .value("I", Double(pt.iUa))
                )
                .foregroundStyle(Color.yellow)
                .symbolSize(16)
            }
        }
        .chartXAxisLabel("Voltage (mV)")
        .chartYAxisLabel("Current (uA)")
        .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(.secondary)
            }
        }
    }

    private var lsvTable: some View {
        MeasurementTable(
            columns: [
                MeasurementColumn(header: "V (mV)", width: 80, alignment: .trailing),
                MeasurementColumn(header: "I (uA)", width: 90, alignment: .trailing),
            ],
            rows: points,
            cellText: { pt, col in
                switch col {
                case 0: String(format: "%.1f", pt.vMv)
                case 1: String(format: "%.3f", pt.iUa)
                default: ""
                }
            }
        )
    }

    private var noData: some View {
        Text("No data points")
            .foregroundStyle(.secondary)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

// MARK: - Amperometry data view

struct AmpDataView: View {
    let points: [AmpPoint]
    @State private var showTable = false

    var body: some View {
        VStack(spacing: 0) {
            if points.isEmpty {
                noData
            } else {
                ampChart
                    .padding()
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }

            if showTable && !points.isEmpty {
                Divider()
                ampTable
                    .frame(maxHeight: 250)
            }
        }
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(showTable ? "Hide Table" : "Show Table") {
                    showTable.toggle()
                }
            }
        }
    }

    private var ampChart: some View {
        let ampColor = Color(red: 0.6, green: 0.6, blue: 1.0)
        return Chart {
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                LineMark(
                    x: .value("t", Double(pt.tMs)),
                    y: .value("I", Double(pt.iUa))
                )
                .foregroundStyle(ampColor)
                .lineStyle(StrokeStyle(lineWidth: 2))
            }
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                PointMark(
                    x: .value("t", Double(pt.tMs)),
                    y: .value("I", Double(pt.iUa))
                )
                .foregroundStyle(ampColor)
                .symbolSize(16)
            }
        }
        .chartXAxisLabel("Time (ms)")
        .chartYAxisLabel("Current (uA)")
        .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(.secondary)
            }
        }
    }

    private var ampTable: some View {
        MeasurementTable(
            columns: [
                MeasurementColumn(header: "t (ms)", width: 80, alignment: .trailing),
                MeasurementColumn(header: "I (uA)", width: 90, alignment: .trailing),
            ],
            rows: points,
            cellText: { pt, col in
                switch col {
                case 0: String(format: "%.1f", pt.tMs)
                case 1: String(format: "%.3f", pt.iUa)
                default: ""
                }
            }
        )
    }

    private var noData: some View {
        Text("No data points")
            .foregroundStyle(.secondary)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

// MARK: - Chlorine data view

struct ClDataView: View {
    let points: [ClPoint]
    let result: ClResult?
    @State private var showTable = false

    var body: some View {
        VStack(spacing: 0) {
            if let r = result {
                resultBanner(r)
            }

            if points.isEmpty {
                noData
            } else {
                clChart
                    .padding()
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }

            if showTable && !points.isEmpty {
                Divider()
                clTable
                    .frame(maxHeight: 250)
            }
        }
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(showTable ? "Hide Table" : "Show Table") {
                    showTable.toggle()
                }
            }
        }
    }

    private func resultBanner(_ r: ClResult) -> some View {
        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)
        }
        .font(.subheadline.monospacedDigit())
        .padding(.horizontal)
        .padding(.vertical, 6)
    }

    private var clChart: some View {
        Chart {
            ForEach(Array(points.enumerated()), id: \.offset) { _, pt in
                LineMark(
                    x: .value("t", Double(pt.tMs)),
                    y: .value("I", Double(pt.iUa))
                )
                .foregroundStyle(by: .value("Phase", phaseLabel(pt.phase)))
                .lineStyle(StrokeStyle(lineWidth: 2))
            }
        }
        .chartForegroundStyleScale([
            "Conditioning": Color.gray,
            "Free": Color(red: 0.2, green: 1, blue: 0.5),
            "Total": Color(red: 1, green: 0.6, blue: 0.2),
        ])
        .chartXAxisLabel("Time (ms)")
        .chartYAxisLabel("Current (uA)")
        .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(.secondary)
            }
        }
        .chartLegend(position: .top)
    }

    private func phaseLabel(_ phase: UInt8) -> String {
        switch phase {
        case 1: "Free"
        case 2: "Total"
        default: "Conditioning"
        }
    }

    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: 70, alignment: .trailing),
            ],
            rows: points,
            cellText: { pt, col in
                switch col {
                case 0: String(format: "%.1f", pt.tMs)
                case 1: String(format: "%.3f", pt.iUa)
                case 2: phaseLabel(pt.phase)
                default: ""
                }
            }
        )
    }

    private var noData: some View {
        Text("No data points")
            .foregroundStyle(.secondary)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

// MARK: - pH data view

struct PhDataView: View {
    let result: PhResult?

    var body: some View {
        VStack(spacing: 0) {
            if let r = result {
                VStack(alignment: .leading, spacing: 12) {
                    Text(String(format: "pH: %.2f", r.ph))
                        .font(.system(size: 56, weight: .bold, design: .rounded))
                        .foregroundStyle(.primary)

                    Text(String(format: "OCP: %.1f mV", r.vOcpMv))
                        .font(.title3)
                        .foregroundStyle(.secondary)

                    Text(String(format: "Temperature: %.1f C", r.tempC))
                        .font(.title3)
                        .foregroundStyle(.secondary)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
                .padding()
            } else {
                Text("No pH result")
                    .foregroundStyle(.secondary)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
        }
    }
}

