import SwiftUI
import CoreImage.CIFilterBuiltins
import NucleicProtocol
import NucleicTailnet
import UIKit

struct SettingsView: View {
    @EnvironmentObject var store: RemoteStore
    @State private var showScanner = false
    @State private var showManualPair = false
    @State private var showAddDevice = false
    @State private var showRenameDevice = false
    @State private var deviceName = IdentityStore.deviceName()
    @State private var deviceNameDraft = ""
    @AppStorage("nucleic.showRawEvents") private var showRaw = false
    @AppStorage("nucleic.showLockEvents") private var showLockEvents = true
    @AppStorage(HeartbeatSettings.shareAnonymousUsageKey) private var shareAnonymousUsage = true
    @AppStorage(AppAppearance.storageKey) private var appearanceRaw = AppAppearance.system.rawValue
    @AppStorage(AppTextSize.storageKey) private var textSizeRaw = AppTextSize.medium.rawValue
    @AppStorage(ColorVisionMode.storageKey) private var colorVisionRaw = ColorVisionMode.standard.rawValue

    var body: some View {
        NavigationStack {
            Form {
                if store.demoMode {
                    Section {
                        Button(role: .destructive) { store.exitDemo() } label: {
                            Label("Leave demo", systemImage: "arrow.uturn.left")
                        }
                    } header: {
                        Text("Demo")
                    } footer: {
                        Text("")
                    }
                }

                Section {
                    Picker("Theme", selection: $appearanceRaw) {
                        ForEach(AppAppearance.allCases) { appearance in
                            Text(appearance.label).tag(appearance.rawValue)
                        }
                    }
                    Picker("Text size", selection: $textSizeRaw) {
                        ForEach(AppTextSize.allCases) { size in
                            Text(size.label).tag(size.rawValue)
                        }
                    }
                    Picker("Color vision", selection: $colorVisionRaw) {
                        ForEach(ColorVisionMode.allCases) { mode in
                            Text(mode.label).tag(mode.rawValue)
                        }
                    }
                } header: {
                    Text("Appearance")
                } footer: {
                    Text("")
                }

                Section {
                    // Mesh-wide headline — "Connected" means this phone can reach the mesh (any Mac
                    // OR cloud runner), not one specific Mac. A phone that only talks to cloud runners
                    // over the relay still reads "Connected".
                    HStack(spacing: 12) {
                        Circle()
                            .fill(store.isMeshConnected ? Color.green
                                : (store.isPaired ? Color.orange : Color.secondary.opacity(0.4)))
                            .frame(width: 10, height: 10)
                        VStack(alignment: .leading, spacing: 2) {
                            Text(store.meshStatusHeadline).font(.body.weight(.medium))
                            Text(store.meshStatusDetail).font(.footnote).foregroundStyle(.secondary)
                        }
                        Spacer()
                        Button("Reconnect") { store.reconnect() }
                            .buttonStyle(.bordered)
                            .controlSize(.small)
                            .disabled(!store.isPaired || store.demoMode)
                    }
                    .padding(.vertical, 2)

                    // One row per device on the mesh: a live dot, its kind, and how it's reached —
                    // the connection method (LAN / Tailnet / Relay / Direct (STUN)) for a direct link,
                    // or "via <device>" when reached by way of another mesh device.
                    ForEach(store.meshDevices) { device in
                        MeshDeviceRow(device: device)
                            .swipeActions(edge: .trailing) {
                                if device.isPairedHost {
                                    Button(role: .destructive) { store.unpair(device.id) } label: {
                                        Label("Remove", systemImage: "minus.circle")
                                    }
                                }
                            }
                    }

                    if store.isPaired {
                        // Read-only diagnostics view of the mesh — the iOS analogue of the Mac's
                        // Covalence Mesh Viewer.
                        NavigationLink {
                            MeshInfoView()
                        } label: {
                            Label("Mesh info", systemImage: "point.3.connected.trianglepath.dotted")
                        }
                    }
                } header: {
                    Text("Connection")
                } footer: {
                    Text("“Connected” means this device can reach your mesh — a Mac or a cloud runner "
                         + "(an iPhone can send jobs to cloud runners without a Mac connection). Each "
                         + "device shows how it's reached: on your network (LAN), your tailnet, the "
                         + "relay, a direct link, or by way of another device. Swipe a Mac to remove it.")
                }

                // Remote agent sign-in (docs/REMOTE_AGENT_LOGIN.md): each connected host's
                // per-provider auth state, with Sign in where that host brokers the flow.
                AgentAccountsSection()

                Section("This device") {
                    LabeledContent("Name", value: deviceName)
                    Button("Rename device") {
                        deviceNameDraft = deviceName
                        showRenameDevice = true
                    }
                    LabeledContent("Scope", value: store.grantedScope.rawValue.capitalized)
                    LabeledContent("Key fingerprint", value: store.deviceFingerprint)
                        .font(.footnote.monospaced())
                }

                Section {
                    Toggle("Resolve over Nucleic Edge", isOn: Binding(
                        get: { store.syncedSettings.resolveApprovalsViaCloud },
                        set: { store.updateSyncedSettings(
                            SyncedSettings(resolveApprovalsViaCloud: $0)) }))
                } header: {
                    Text("Lock-screen approvals")
                } footer: {
                    Text("Send Approve/Deny from the Live Activity to your Mac through the Nucleic "
                         + "relay, so a decision lands even when this iPhone is locked or off your "
                         + "Wi-Fi. When off, the lock-screen buttons open Nucleic to approve "
                         + "locally instead. Synced with your Mac.")
                }

                Section {
                    Toggle("Show lock events", isOn: $showLockEvents)
                    Toggle("Show advanced detail", isOn: $showRaw)
                } header: {
                    Text("Transcript")
                } footer: {
                    Text("Advanced detail surfaces per-turn token counts, rate-limit notices, turn "
                         + "markers, and raw events. Hidden by default so consecutive tool calls "
                         + "read as one group.")
                }

                Section {
                    Toggle("Share anonymous usage", isOn: $shareAnonymousUsage)
                } header: {
                    Text("Privacy")
                } footer: {
                    Text("")
                }

                Section {
                    // Grow the mesh: ask a connected Mac to mint a join code this phone shows —
                    // scan it from a new iPhone/iPad, or copy the link to a new Mac. Only when a
                    // live Mac can mint one (the phone can't originate a join code on its own).
                    if store.canAddDeviceToMesh {
                        Button {
                            showAddDevice = true
                        } label: {
                            Label("Add a device to this mesh", systemImage: "plus.circle")
                        }
                    }
                    Button {
                        showScanner = true
                    } label: {
                        Label("Join a Covalence mesh", systemImage: "circle.dotted.and.circle")
                    }
                    Button {
                        showManualPair = true
                    } label: {
                        Label("Enter code manually", systemImage: "keyboard")
                    }
                    if store.isPaired {
                        Button(role: .destructive) { store.unpair() } label: {
                            Label("Unpair this device", systemImage: "minus.circle")
                        }
                    }
                } footer: {
                    Text("")
                }
            }
            .navigationTitle("Settings")
            .compactTabBarInset()
            .sheet(isPresented: $showScanner) {
                PairingScannerView { payload in
                    showScanner = false
                    store.pair(with: payload)
                }
            }
            .sheet(isPresented: $showManualPair) {
                ManualPairingView { payload in
                    showManualPair = false
                    store.pair(with: payload)
                }
            }
            .sheet(isPresented: $showAddDevice, onDismiss: { store.cancelPairingCode() }) {
                AddDeviceView()
            }
            .alert("Rename this device", isPresented: $showRenameDevice) {
                TextField("Device name", text: $deviceNameDraft)
                Button("Cancel", role: .cancel) {}
                Button("Save") {
                    if store.renameDevice(to: deviceNameDraft) {
                        deviceName = store.deviceName
                    }
                }
                .disabled(deviceNameDraft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
            } message: {
                Text("This name identifies your iPhone or iPad to every Mac in the mesh.")
            }
        }
    }
}

/// Shown before pairing (UX_IOS §7): explain the flow and launch the scanner.
struct PairingIntroView: View {
    @EnvironmentObject var store: RemoteStore
    @State private var showScanner = false
    @State private var showManualPair = false

    var body: some View {
        VStack(spacing: 24) {
            Image(systemName: "qrcode.viewfinder").font(.system(size: 72)).foregroundStyle(.tint)
            Text("Join your Mac's group").font(.title2.weight(.semibold))
            Text("On your Mac, open Nucleic ▸ Settings ▸ Add device to show a QR code, then scan it here — or enter the join code beneath it manually. Scanning any Mac in the group connects you to all of them.")
                .multilineTextAlignment(.center)
                .foregroundStyle(.secondary)
                .padding(.horizontal, 32)
            Button {
                showScanner = true
            } label: {
                Label("Scan QR code", systemImage: "camera").font(.headline).padding(.horizontal)
            }
            .buttonStyle(.borderedProminent)

            // A camera-free path — the iPad's scanner can't run while the app is mirrored to an
            // external display or in some Stage Manager states; pasting the code always works.
            Button {
                showManualPair = true
            } label: {
                Label("Enter code manually", systemImage: "keyboard")
            }
            .buttonStyle(.bordered)

            // Feedback after a scan: the whole UI is still the intro until pairing completes, so
            // without this a failed or in-flight pair looks like "nothing happened."
            pairingStatus

            VStack(spacing: 6) {
                Button { store.enterDemo() } label: {
                    Label("Explore a demo — no Mac needed", systemImage: "play.circle")
                }
                .buttonStyle(.bordered)
                Text("Sample data and a simulated agent — no Mac or account required.")
                    .font(.footnote)
                    .foregroundStyle(.secondary)
                    .multilineTextAlignment(.center)
                    .padding(.horizontal, 32)
            }
            .padding(.top, 8)
        }
        .padding()
        // Fill the available height so the enclosing VStack reaches full screen and the
        // BuildBanner/DemoBanner strips stay pinned at the top. Without this the intro only
        // claims its intrinsic height, so the stack gets centered and the banner drifts down.
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .sheet(isPresented: $showScanner) {
            PairingScannerView { payload in
                showScanner = false
                store.pair(with: payload)
            }
        }
        .sheet(isPresented: $showManualPair) {
            ManualPairingView { payload in
                showManualPair = false
                store.pair(with: payload)
            }
        }
    }

    /// A status line reflecting an in-flight or failed pairing attempt. Silent in the resting
    /// `.unpaired` state (the intro copy already covers that).
    @ViewBuilder
    private var pairingStatus: some View {
        switch store.connectivity {
        case .connecting, .reconnecting:
            // A first tailnet pairing with no auth key detours through a browser login
            // that can take minutes — and this intro is the ONLY screen an unpaired phone
            // has (no Settings tab yet), so the way back to the login page must live here.
            if let loginURL = store.tailnetLoginURL {
                VStack(spacing: 6) {
                    Label("Approve this iPhone in your browser to join your tailnet.",
                          systemImage: "person.crop.circle.badge.checkmark")
                        .font(.footnote).foregroundStyle(.secondary)
                        .multilineTextAlignment(.center).padding(.horizontal, 32)
                    Link("Open the Tailscale login page", destination: loginURL)
                        .font(.footnote.weight(.medium))
                }
            } else {
                Label("Connecting to your Mac…", systemImage: "arrow.triangle.2.circlepath")
                    .font(.footnote).foregroundStyle(.secondary)
            }
        case .hostOffline:
            Label("Can't reach your Mac. Make sure it's on the same Wi‑Fi and remote access is on.",
                  systemImage: "wifi.exclamationmark")
                .font(.footnote).foregroundStyle(.orange)
                .multilineTextAlignment(.center).padding(.horizontal, 32)
        case .failed(let message):
            Label(message, systemImage: "exclamationmark.triangle.fill")
                .font(.footnote).foregroundStyle(.red)
                .multilineTextAlignment(.center).padding(.horizontal, 32)
        case .connected, .unpaired:
            EmptyView()
        }
    }
}

/// A camera-free pairing path (UX_IOS §7): paste the Mac's `nucleic://pair?d=…` code instead of
/// scanning its QR. The iPad's camera can be blocked while mirrored to an external display or in
/// some Stage Manager states, where the scanner's `couldNotStart` path leaves you stuck — this
/// always works. Parses with the same `PairingPayload(qrString:)` the scanner uses.
struct ManualPairingView: View {
    let onEntered: (PairingPayload) -> Void
    @Environment(\.dismiss) private var dismiss
    @State private var code = ""
    @State private var error: String?
    @FocusState private var fieldFocused: Bool

    var body: some View {
        NavigationStack {
            Form {
                Section {
                    TextField("nucleic://pair?d=…", text: $code, axis: .vertical)
                        .textInputAutocapitalization(.never)
                        .autocorrectionDisabled()
                        .keyboardType(.URL)
                        .font(.callout.monospaced())
                        .lineLimit(2...6)
                        .focused($fieldFocused)
                        .onChange(of: code) { error = nil }
                } header: {
                    Text("Join code")
                } footer: {
                    Text("On your Mac, open Nucleic ▸ Settings ▸ Add device and copy the join link shown beneath the QR code, then paste it here.")
                }
                if let error {
                    Label(error, systemImage: "exclamationmark.triangle.fill")
                        .font(.callout).foregroundStyle(.red)
                }
                Button {
                    submit()
                } label: {
                    Label("Join", systemImage: "link").frame(maxWidth: .infinity)
                }
                .buttonStyle(.borderedProminent)
                .disabled(code.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
                .keyboardShortcut(.defaultAction)
            }
            .navigationTitle("Enter pairing code")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } }
                ToolbarItem(placement: .primaryAction) {
                    Button {
                        if let pasted = UIPasteboard.general.string { code = pasted }
                    } label: {
                        Label("Paste", systemImage: "doc.on.clipboard")
                    }
                    .disabled(!UIPasteboard.general.hasStrings)
                }
            }
            .onAppear { fieldFocused = true }
        }
    }

    private func submit() {
        let trimmed = code.trimmingCharacters(in: .whitespacesAndNewlines)
        guard let payload = try? PairingPayload(qrString: trimmed) else {
            error = "That isn't a valid Nucleic pairing code. Copy the link beneath the QR on your Mac."
            return
        }
        onEntered(payload)
        dismiss()
    }
}

/// "Add a device to this mesh": the phone can't mint a join code itself (it runs no listener), so
/// it asks a connected Mac to open a pairing window and relays back that Mac's code — shown here as
/// a QR to scan from a new iPhone/iPad, or a copyable link to send to a new Mac. Scanning/pasting it
/// joins the new device to the whole group. Dismissing the sheet closes the Mac's pairing window
/// (`RemoteStore.cancelPairingCode`, wired to the sheet's `onDismiss`).
struct AddDeviceView: View {
    @EnvironmentObject var store: RemoteStore
    @Environment(\.dismiss) private var dismiss
    @State private var copied = false
    @State private var copyResetTask: Task<Void, Never>?

    var body: some View {
        NavigationStack {
            Form {
                switch store.addDevice {
                case .idle, .requesting:
                    Section {
                        HStack {
                            ProgressView()
                            Text("Asking a Mac in your mesh for a join code…")
                                .foregroundStyle(.secondary)
                        }
                    }
                case .unavailable:
                    Section {
                        Label("Couldn't get a join code", systemImage: "exclamationmark.triangle.fill")
                            .foregroundStyle(.orange)
                        Text("This needs a Mac in your mesh that's connected right now. Make sure "
                             + "one is reachable with its meshing service on, then try again.")
                            .font(.callout).foregroundStyle(.secondary)
                        Button("Try again") { store.requestPairingCode() }
                    }
                case .ready(let code):
                    codeSection(code)
                }
            }
            .navigationTitle("Add a device")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .confirmationAction) { Button("Done") { dismiss() } }
            }
            // A Mac joining via this code needs the user's OK — the accepting Mac forwarded its
            // confirm here so it can be approved from the phone (an iPhone/iPad joins hands-free).
            .confirmationDialog(
                "Let “\(store.pendingMacPairRequest?.label ?? "another Mac")” join?",
                isPresented: Binding(
                    get: { store.pendingMacPairRequest != nil },
                    set: { if !$0 { store.respondMacPair(false) } }),
                titleVisibility: .visible
            ) {
                Button("Allow") { store.respondMacPair(true) }
                Button("Don't allow", role: .destructive) { store.respondMacPair(false) }
                Button("Cancel", role: .cancel) { store.respondMacPair(false) }
            } message: {
                Text("It joins the whole group — it gets the same control of your Macs' sessions an "
                     + "iPhone does, and every Mac here stays connected.")
            }
        }
        // Fire the request as the sheet appears; `onDismiss` (on the presenter) tells the Mac to
        // close its pairing window.
        .onAppear { if case .ready = store.addDevice {} else { store.requestPairingCode() } }
        .onDisappear { copyResetTask?.cancel() }
    }

    @ViewBuilder
    private func codeSection(_ code: String) -> some View {
        Section {
            if let image = Self.qrImage(code) {
                HStack {
                    Spacer()
                    Image(uiImage: image)
                        .interpolation(.none)
                        .resizable()
                        .frame(width: 220, height: 220)
                        .padding(10)
                        .background(.white, in: RoundedRectangle(cornerRadius: 12))
                    Spacer()
                }
                .listRowBackground(Color.clear)
            }
        } header: {
            Text("Scan to join")
        } footer: {
            Text("On a new iPhone or iPad, open Nucleic Remote ▸ Join a Covalence mesh and scan this. "
                 + "It joins the whole group — every Mac and device here.")
        }

        Section {
            Text(code)
                .font(.system(.caption, design: .monospaced))
                .lineLimit(2)
                .truncationMode(.middle)
                .textSelection(.enabled)
            Button {
                copyCode(code)
            } label: {
                Label(copied ? "Copied" : "Copy join code",
                      systemImage: copied ? "checkmark" : "doc.on.doc")
            }
        } header: {
            Text("Or send a code")
        } footer: {
            Text("Paste into “Join mesh…” on another Mac, or “Enter code manually” on an iPhone or "
                 + "iPad. A Mac joining asks you to approve it right here; an iPhone or iPad joins "
                 + "on its own.")
        }
    }

    private func copyCode(_ code: String) {
        UIPasteboard.general.string = code
        withAnimation(.easeOut(duration: 0.12)) { copied = true }
        copyResetTask?.cancel()
        copyResetTask = Task {
            try? await Task.sleep(for: .seconds(1.4))
            guard !Task.isCancelled else { return }
            withAnimation(.easeIn(duration: 0.2)) { copied = false }
        }
    }

    /// Render the join string as a QR code (CoreImage), mirroring the Mac's pairing sheet.
    private static func qrImage(_ string: String) -> UIImage? {
        let filter = CIFilter.qrCodeGenerator()
        filter.message = Data(string.utf8)
        filter.correctionLevel = "M"
        guard let output = filter.outputImage else { return nil }
        let scaled = output.transformed(by: CGAffineTransform(scaleX: 10, y: 10))
        let context = CIContext()
        guard let cg = context.createCGImage(scaled, from: scaled.extent) else { return nil }
        return UIImage(cgImage: cg)
    }
}
