//
//  ProposalPicker.swift
//  SwanDesk — structured picker for strongSwan IKE / ESP proposal strings.
//
//  Replaces the free-form proposal text fields with three Pickers:
//    Encryption · Integrity · DH Group
//  Falls back to a plain TextField for proposals that contain commas
//  (multiple proposals) or unrecognised tokens, so no data is ever lost.
//

import SwiftUI

// MARK: - Algorithm tables

struct EncAlg: Identifiable, Hashable {
    let name: String        // strongSwan token, e.g. "aes256"
    let label: String       // display label
    let isAEAD: Bool        // GCM / Poly1305: no separate integrity needed
    var id: String { name }
    init(_ name: String, _ label: String, aead: Bool = false) {
        self.name = name; self.label = label; self.isAEAD = aead
    }
}

struct IntegAlg: Identifiable, Hashable {
    let name: String
    let label: String
    var id: String { name }
    init(_ name: String, _ label: String) { self.name = name; self.label = label }
}

struct DHGroup: Identifiable, Hashable {
    let number: Int
    let name: String        // strongSwan token, e.g. "ecp384"; "" = no PFS (ESP only)
    var label: String { name.isEmpty ? "None (no PFS)" : "\(number) (\(name))" }
    var id: String { name }
    init(_ number: Int, _ name: String) { self.number = number; self.name = name }
}

let knownEnc: [EncAlg] = [
    EncAlg("aes128",           "AES-128"),
    EncAlg("aes192",           "AES-192"),
    EncAlg("aes256",           "AES-256"),
    EncAlg("aes128gcm16",      "AES-128-GCM",         aead: true),
    EncAlg("aes192gcm16",      "AES-192-GCM",         aead: true),
    EncAlg("aes256gcm16",      "AES-256-GCM",         aead: true),
    EncAlg("chacha20poly1305", "ChaCha20-Poly1305",    aead: true),
    EncAlg("3des",             "3DES (legacy)"),
]

let knownInteg: [IntegAlg] = [
    IntegAlg("sha256",  "SHA-256"),
    IntegAlg("sha384",  "SHA-384"),
    IntegAlg("sha512",  "SHA-512"),
    IntegAlg("aesxcbc", "AES-XCBC"),
    IntegAlg("sha1",    "SHA-1 (legacy)"),
]

let knownDH: [DHGroup] = [
    DHGroup(14, "modp2048"),
    DHGroup(15, "modp3072"),
    DHGroup(16, "modp4096"),
    DHGroup(18, "modp8192"),
    DHGroup(19, "ecp256"),
    DHGroup(20, "ecp384"),
    DHGroup(21, "ecp521"),
    DHGroup(31, "curve25519"),
]

// MARK: - Proposal parser

/// Splits a single strongSwan proposal string (no commas) into its algorithm tokens.
/// Returns nil for multi-proposal strings, empty input, or when any token is unrecognised
/// (so the caller can safely fall back to a plain text field).
struct ParsedProposal {
    var encryption: String
    var integrity: String    // empty for AEAD ciphers
    var prf: String          // preserved but not shown in the UI (strongSwan infers it)
    var dhGroup: String      // empty = no PFS (ESP only)

    var encIsAEAD: Bool {
        knownEnc.first(where: { $0.name == encryption })?.isAEAD ?? false
    }

    var string: String {
        var parts = [encryption]
        if !integrity.isEmpty { parts.append(integrity) }
        if !prf.isEmpty      { parts.append(prf) }
        if !dhGroup.isEmpty  { parts.append(dhGroup) }
        return parts.joined(separator: "-")
    }

    init?(_ s: String) {
        guard !s.contains(","), !s.isEmpty else { return nil }
        let tokens = s.split(separator: "-").map(String.init)
        var enc = "", integ = "", prf = "", dh = ""
        var unknownCount = 0
        for t in tokens {
            if t.hasPrefix("aes") || t.hasPrefix("3des") || t.hasPrefix("camellia")
                || t.hasPrefix("chacha") {
                enc = t
            } else if t.hasPrefix("sha") || t == "aesxcbc" || t.hasPrefix("md5") {
                integ = t
            } else if t.hasPrefix("prf") {
                prf = t
            } else if t.hasPrefix("ecp") || t.hasPrefix("modp") || t.hasPrefix("curve")
                        || t == "x25519" {
                dh = t
            } else {
                unknownCount += 1
            }
        }
        guard !enc.isEmpty, unknownCount == 0 else { return nil }
        self.encryption = enc; self.integrity = integ; self.prf = prf; self.dhGroup = dh
    }
}

// MARK: - View

/// Structured pickers for one proposal string (IKE or ESP).
/// `showNoPFS` adds a "None (no PFS)" option to the DH Group picker (ESP only).
struct ProposalPicker: View {
    @Binding var proposal: String
    var showNoPFS: Bool = false

    var body: some View {
        if let current = ParsedProposal(proposal) {
            encPicker(current: current)
            if !current.encIsAEAD { integPicker(current: current) }
            dhPicker(current: current)
            Text("→ \(proposal)")
                .font(.caption)
                .foregroundStyle(.secondary)
        } else {
            TextField("Proposal (strongSwan syntax)", text: $proposal)
        }
    }

    // MARK: Encryption picker

    @ViewBuilder
    private func encPicker(current: ParsedProposal) -> some View {
        let options = knownEnc + (knownEnc.contains(where: { $0.name == current.encryption })
            ? [] : [EncAlg(current.encryption, current.encryption)])
        Picker("Encryption", selection: Binding(
            get: { ParsedProposal(proposal)?.encryption ?? current.encryption },
            set: { v in
                guard var p = ParsedProposal(proposal) else { return }
                p.encryption = v
                let nowAEAD = knownEnc.first(where: { $0.name == v })?.isAEAD ?? false
                if nowAEAD { p.integrity = "" }
                else if p.integrity.isEmpty { p.integrity = knownInteg.first?.name ?? "sha256" }
                proposal = p.string
            }
        )) {
            ForEach(options) { a in Text(a.label).tag(a.name) }
        }
    }

    // MARK: Integrity picker

    @ViewBuilder
    private func integPicker(current: ParsedProposal) -> some View {
        let options = knownInteg + (knownInteg.contains(where: { $0.name == current.integrity })
            ? [] : (current.integrity.isEmpty ? [] : [IntegAlg(current.integrity, current.integrity)]))
        Picker("Integrity", selection: Binding(
            get: { ParsedProposal(proposal)?.integrity ?? current.integrity },
            set: { v in
                guard var p = ParsedProposal(proposal) else { return }
                p.integrity = v
                proposal = p.string
            }
        )) {
            ForEach(options) { a in Text(a.label).tag(a.name) }
        }
    }

    // MARK: DH Group picker

    @ViewBuilder
    private func dhPicker(current: ParsedProposal) -> some View {
        let options = dhOptions(current: current)
        Picker("DH Group", selection: Binding(
            get: { ParsedProposal(proposal)?.dhGroup ?? current.dhGroup },
            set: { v in
                guard var p = ParsedProposal(proposal) else { return }
                p.dhGroup = v
                proposal = p.string
            }
        )) {
            ForEach(options) { g in Text(g.label).tag(g.name) }
        }
    }

    private func dhOptions(current: ParsedProposal) -> [DHGroup] {
        var options = showNoPFS ? [DHGroup(0, "")] + knownDH : knownDH
        if !options.contains(where: { $0.name == current.dhGroup }) {
            options.append(DHGroup(0, current.dhGroup))
        }
        return options
    }
}
