import SwiftUI
import AppKit
import NucleicCore
import NucleicProtocol

// MARK: - Shared settings vocabulary

/// How much of the mesh's Carbon data this Mac chooses to hold. The full spec models a per-device
/// disk cap and a rendezvous-hashed replica set (`docs/CARBON_SHARDING.md` §5, §7.3); at the user's
/// altitude that reduces to a single role choice — "hold everything" for a storage-rich desktop, or
/// "hold my share within a budget" for a laptop/phone.
enum CarbonStorageMode: String, CaseIterable, Identifiable {
    /// Keep a full set of the mesh's Carbon Copies on this Mac (desktop with ample storage).
    case all
    /// Keep only this Mac's assigned share, bounded by the disk budget (laptop/phone).
    case optimize

    var id: String { rawValue }

    var label: String {
        switch self {
        case .all: "All Copies"
        case .optimize: "Optimized"
        }
    }

    var explanation: String {
        switch self {
        case .all:
            "Best for a Mac with plenty of storage, like a desktop: this Mac keeps a full set of "
            + "Carbon Copies for the mesh, so it can rebuild any project's sessions on its own."
        case .optimize:
            "Best for a laptop or phone with limited storage: this Mac keeps only the Copies it's "
            + "assigned, staying within the disk budget below. Your other devices hold the rest."
        }
    }
}

/// The `nucleic.carbon.*` defaults keys (spec §12). Kept in one place so the Carbon settings pane
/// and the Carbon Viewer read the same values, and so the future data layer binds to the identical
/// keys the user has already been setting.
enum CarbonSettings {
    // No enabled key: Carbon is mandatory, always-on (CARBON_SHARDING D17). The former
    // `nucleic.carbon.enabled` default is simply ignored if present — locking and durability
    // can't sit behind a feature flag, so there is nothing to read.

    static let storageModeKey = "nucleic.carbon.storageMode"

    static let diskCapGiBKey = "nucleic.carbon.diskCapGiB"
    static let diskCapGiBDefault = 2

    static let snapshotSecondsKey = "nucleic.carbon.snapshotSeconds"
    static let snapshotSecondsDefault = 20
    static let snapshotSecondsRange = 5...300

    static let relayAllowedKey = "nucleic.carbon.relayAllowed"
    static let relayAllowedDefault = true

    static let paranoidModeKey = "nucleic.carbon.paranoidMode"
    static let paranoidModeDefault = false
}

// MARK: - Window

/// The **Carbon Viewer** — a standalone, read-only window over the Carbon data layer, the sibling of
/// the Covalence Mesh Viewer. It shows what Carbon Copies are stored on *this* Mac and what lives on
/// your other mesh devices, with each project's durability at a glance (spec §12's "read-only:
/// per-project replica set + per-member sync lag; per-session durability chip").
///
/// Everything here is read from the live store (`AppStore.carbonStoreSnapshot`): per-project blob
/// counts and bytes from `CarbonShardStore`, this member's head per stream, and — for the mesh rows
/// — the peer heads gossip has reported (§8.2). The viewer polls on a cadence rather than observing,
/// because the store is an actor; it never produces a snapshot or touches the wire itself.
@MainActor
final class CarbonViewerWindowController: NSObject, NSWindowDelegate {
    static let shared = CarbonViewerWindowController()

    private var window: NSWindow?

    private override init() { super.init() }

    /// Open (or refocus) the viewer. One window per app — reopening brings it forward.
    func open(store: AppStore) {
        if let window {
            window.makeKeyAndOrderFront(nil)
            return
        }
        let content = CarbonViewerView()
            .environment(store)
            .frame(minWidth: 520, minHeight: 440)
        let window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 680, height: 620),
            styleMask: [.titled, .closable, .resizable, .miniaturizable],
            backing: .buffered, defer: false)
        window.title = "Carbon Viewer"
        window.contentViewController = NSHostingController(rootView: content)
        window.isReleasedWhenClosed = false
        window.delegate = self
        window.center()
        window.makeKeyAndOrderFront(nil)
        self.window = window
    }

    func windowWillClose(_ notification: Notification) {
        guard let closing = notification.object as? NSWindow, closing === window else { return }
        window = nil
    }
}

// MARK: - Content

struct CarbonViewerView: View {
    @Environment(AppStore.self) private var store
    @AppStorage(CarbonSettings.storageModeKey) private var storageModeRaw = CarbonStorageMode.optimize.rawValue
    @AppStorage(CarbonSettings.diskCapGiBKey) private var diskCapGiB = CarbonSettings.diskCapGiBDefault

    /// The live store snapshot, polled rather than observed: it comes from an actor
    /// (`CarbonShardStore`) plus the sync coordinator's gossip, neither of which is observable
    /// state — the `CovalenceMeshViewerView` pattern. nil until the first load completes, which
    /// is distinct from "loaded and empty" and rendered differently.
    @State private var snapshot: CarbonStoreSnapshot?

    private var storageMode: CarbonStorageMode {
        CarbonStorageMode(rawValue: storageModeRaw) ?? .optimize
    }

    /// Other mesh members that can hold Carbon Copies — Macs and runners only, never phones
    /// (spec §2: phones are never Carbon members). Same filter `carbonStoreSnapshot` uses, so
    /// presence rows and gossip rows join cleanly on `deviceID`.
    private var copyHolders: [PeerClient.PeerPresence] {
        store.meshPeers
            .filter { $0.kind.canOwnSessions }
            .sorted { store.meshHostLabel($0.deviceID) < store.meshHostLabel($1.deviceID) }
    }

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 18) {
                summaryRow
                statusBanner
                if let blocked = snapshot?.blocked, !blocked.isEmpty {
                    blockedSection(blocked)
                }
                thisDeviceSection
                elsewhereSection
            }
            .padding(16)
            .frame(maxWidth: .infinity, alignment: .leading)
        }
        .task {
            while !Task.isCancelled {
                snapshot = await store.carbonStoreSnapshot()
                try? await Task.sleep(for: .seconds(3))
            }
        }
    }

    // MARK: Summary

    /// Flowed, not an `HStack`: six chips don't fit the window's natural width, and an HStack
    /// answers that by compressing them until their labels hyphenate mid-word ("Car-bon on").
    /// `FlowLayout` gives each chip its intrinsic width and wraps the overflow to the next line.
    private var summaryRow: some View {
        FlowLayout(spacing: 8) {
            chip("Carbon on", systemImage: "atom", tint: .green)
            chip(
                "This Mac: \(storageMode.label)",
                systemImage: storageMode == .all ? "internaldrive" : "slider.horizontal.3",
                tint: .blue)
            if storageMode == .optimize {
                chip("Budget \(diskCapGiB) GB", systemImage: "gauge.with.dots.needle.33percent", tint: .secondary)
            }
            if let snapshot, !snapshot.projects.isEmpty {
                chip(
                    "\(snapshot.totalShards) shard(s)", systemImage: "square.stack.3d.up",
                    tint: .green)
                chip(
                    Self.byteLabel(snapshot.totalBytes), systemImage: "internaldrive",
                    tint: .secondary)
            }
            chip(
                "\(copyHolders.count) other holder(s)",
                systemImage: "externaldrive.connected.to.line.below",
                tint: copyHolders.isEmpty ? .orange : .green)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }

    /// One capsule. `lineLimit(1)` + `fixedSize` keep the label on a single line at its natural
    /// width — a chip that has to wrap or truncate to fit belongs on the next row instead.
    private func chip(_ text: String, systemImage: String, tint: Color) -> some View {
        Label(text, systemImage: systemImage)
            .font(.caption)
            .lineLimit(1)
            .foregroundStyle(tint)
            .padding(.horizontal, 8)
            .padding(.vertical, 4)
            .background(tint.opacity(0.12), in: Capsule())
            .fixedSize()
    }

    // MARK: Banners

    /// Three honest states: still reading the store, reading it and finding nothing, or an
    /// active producer with a shard count to show for itself.
    @ViewBuilder
    private var statusBanner: some View {
        if let snapshot {
            if snapshot.projects.isEmpty {
                banner(
                    symbol: "exclamationmark.triangle",
                    tint: .orange,
                    title: "No Carbon Copies yet",
                    message: "Carbon is running, but nothing has been sharded on this Mac yet. "
                        + "Copies appear once a session in an enrolled project does work worth "
                        + "capturing.")
            } else {
                banner(
                    symbol: "atom",
                    tint: .green,
                    title: "Carbon is active",
                    message: "\(snapshot.totalShards) shard(s) across "
                        + "\(snapshot.projects.count) project(s), "
                        + "\(Self.byteLabel(snapshot.totalBytes)) on disk. Uncommitted work in "
                        + "every enrolled project is captured continuously and replicated to the "
                        + "mesh members below.")
            }
        } else {
            banner(
                symbol: "clock", tint: .secondary, title: "Reading the Carbon store…",
                message: "Counting the shards and manifests held on this Mac.")
        }
    }

    private func banner(symbol: String, tint: Color, title: String, message: String) -> some View {
        HStack(alignment: .top, spacing: 10) {
            Image(systemName: symbol)
                .foregroundStyle(tint)
                .font(.title3)
            VStack(alignment: .leading, spacing: 2) {
                Text(title).font(.callout.weight(.semibold))
                Text(message).font(.caption).foregroundStyle(.secondary)
                    .fixedSize(horizontal: false, vertical: true)
            }
            Spacer(minLength: 0)
        }
        .padding(12)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(tint.opacity(0.08), in: RoundedRectangle(cornerRadius: 10))
    }

    // MARK: Blocked projects (§17.6 — an inability, surfaced, never silent)

    private func blockedSection(
        _ blocked: [CarbonStoreSnapshot.BlockedProject]
    ) -> some View {
        VStack(alignment: .leading, spacing: 6) {
            Text("Not protected").font(.headline)
            ForEach(blocked) { project in
                HStack(spacing: 8) {
                    Circle().fill(Color.orange).frame(width: 7, height: 7)
                    Label {
                        Text(projectLabel(project.uuid))
                    } icon: {
                        Image(systemName: "exclamationmark.triangle")
                    }
                    Spacer()
                    Text(project.reason)
                        .font(.caption).foregroundStyle(.secondary)
                }
                .font(.callout)
                Divider()
            }
            Text("Carbon can't shard these repos' shape — it isn't a setting you can turn on.")
                .font(.caption2).foregroundStyle(.tertiary)
        }
    }

    // MARK: This device

    private var thisDeviceSection: some View {
        VStack(alignment: .leading, spacing: 6) {
            Text("Stored on this Mac").font(.headline)
            HStack(spacing: 8) {
                Circle().fill(Color.green).frame(width: 7, height: 7)
                Label {
                    Text("\(store.syncHostName) (this Mac)").fontWeight(.semibold)
                } icon: {
                    Image(systemName: "desktopcomputer")
                }
                Spacer()
                Text(storageMode.label)
                    .font(.caption)
                    .padding(.horizontal, 6).padding(.vertical, 2)
                    .background(Color.blue.opacity(0.12), in: Capsule())
            }
            .font(.callout)
            if let snapshot {
                if snapshot.projects.isEmpty {
                    emptyStateRow(
                        "No Carbon Copies stored on this Mac yet.",
                        detail: storageMode == .all
                            ? "In All Copies mode this Mac will hold a full set for the mesh."
                            : "In Optimized mode this Mac will hold its assigned share, up to "
                                + "\(diskCapGiB) GB.")
                } else {
                    ForEach(snapshot.projects) { project in
                        projectRow(project)
                        Divider()
                    }
                }
            } else {
                emptyStateRow("Reading…", detail: "Counting blobs in the local Carbon store.")
            }
        }
    }

    private func projectRow(
        _ project: CarbonStoreSnapshot.ProjectHoldings
    ) -> some View {
        VStack(alignment: .leading, spacing: 3) {
            HStack(spacing: 8) {
                Image(systemName: "folder")
                Text(projectLabel(project.uuid)).fontWeight(.semibold)
                if project.keyMissing {
                    Text("key missing")
                        .font(.caption2)
                        .padding(.horizontal, 5).padding(.vertical, 1)
                        .background(Color.orange.opacity(0.15), in: Capsule())
                        .foregroundStyle(.orange)
                }
                Spacer()
                Text(Self.byteLabel(project.usage.bytes))
                    .font(.caption).foregroundStyle(.secondary)
            }
            .font(.callout)
            Text(usageLine(project.usage))
                .font(.caption2).foregroundStyle(.secondary)
                .padding(.leading, 22)
            ForEach(Self.streamSummaries(project.heads)) { stream in
                Text(
                    "\(stream.label) — \(stream.chains) chain(s), newest snapshot "
                        + "#\(stream.newestSeq)")
                    .font(.caption2).foregroundStyle(.tertiary)
                    .padding(.leading, 22)
            }
        }
        .padding(.vertical, 2)
    }

    private func usageLine(_ usage: CarbonStoreUsage) -> String {
        var parts = ["\(usage.shardCount) shard(s)", "\(usage.manifestCount) manifest(s)"]
        if usage.quarantinedCount > 0 {
            parts.append("\(usage.quarantinedCount) quarantined")
        }
        return parts.joined(separator: " · ")
    }

    // MARK: Elsewhere

    private var elsewhereSection: some View {
        VStack(alignment: .leading, spacing: 6) {
            Text("Stored on other devices").font(.headline)
            if copyHolders.isEmpty {
                emptyStateRow(
                    "No other Carbon-capable devices on your mesh.",
                    detail: "Pair another Mac or add a runner so Copies of this Mac's work have "
                        + "somewhere to live — a mesh of one can't protect your data elsewhere.")
            } else {
                ForEach(copyHolders, id: \.deviceID) { peer in
                    holderRow(peer)
                    Divider()
                }
            }
        }
    }

    private func holderRow(_ peer: PeerClient.PeerPresence) -> some View {
        let holdings = snapshot?.peers.first { $0.deviceID == peer.deviceID }
        return VStack(alignment: .leading, spacing: 2) {
            HStack(spacing: 8) {
                Circle()
                    .fill(peer.connected ? Color.green : Color.secondary.opacity(0.4))
                    .frame(width: 7, height: 7)
                Label {
                    Text(store.meshHostLabel(peer.deviceID))
                } icon: {
                    Image(systemName: peer.kind == .cloud ? "cloud" : "desktopcomputer")
                }
                Spacer()
                Text(peer.connected ? "online" : "offline")
                    .font(.caption).foregroundStyle(.secondary)
            }
            .font(.callout)
            Text(holderDetail(holdings))
                .font(.caption2)
                // Orange only for a lag we can actually attest to; an unheard-from peer stays
                // neutral, matching the "no Copies reported yet" wording.
                .foregroundStyle(
                    (holdings?.reported == true && (holdings?.behind ?? 0) > 0)
                        ? .orange : .secondary)
                .padding(.leading, 15)
        }
    }

    /// Gossip is *state*, so a peer we've heard nothing from is an honest unknown, not a claim
    /// that it holds zero — say which one it is rather than collapsing both to "no Copies".
    private func holderDetail(
        _ holdings: CarbonStoreSnapshot.PeerHoldings?
    ) -> String {
        guard let holdings, holdings.reported else { return "No Copies reported yet." }
        if holdings.behind == 0 {
            return "Holds all \(holdings.caughtUp) of this Mac's chain(s)."
        }
        return "Caught up on \(holdings.caughtUp) of "
            + "\(holdings.caughtUp + holdings.behind) chain(s) — \(holdings.behind) behind."
    }

    // MARK: Helpers

    /// A project's display name, falling back to a short UUID for one this Mac only mirrors
    /// (held for the mesh, not open here) or has since removed.
    private func projectLabel(_ uuid: UUID) -> String {
        store.carbonProjectName(for: uuid) ?? "Project \(uuid.uuidString.prefix(8))"
    }

    private static func byteLabel(_ bytes: UInt64) -> String {
        ByteCountFormatter.string(fromByteCount: Int64(bytes), countStyle: .file)
    }

    /// Heads collapsed to one row per stream kind: a busy project has a head per session per
    /// stream, which is a wall of rows at the wrong altitude for a "what am I protecting?" view.
    private static func streamSummaries(_ heads: [CarbonHead]) -> [StreamSummary] {
        Dictionary(grouping: heads, by: \.key.stream)
            .map { kind, group in
                StreamSummary(
                    kind: kind, chains: group.count,
                    newestSeq: group.map(\.snapshotSeq).max() ?? 0)
            }
            .sorted { $0.kind.rawValue < $1.kind.rawValue }
    }

    private struct StreamSummary: Identifiable {
        let kind: CarbonStreamKind
        let chains: Int
        let newestSeq: UInt64

        var id: String { kind.rawValue }

        /// The user-facing name for a stream kind (spec §3), not the frozen wire string.
        var label: String {
            switch kind {
            case .worktreeSnapshot: "Uncommitted work"
            case .history: "Git history"
            case .nativeTranscript: "Agent transcripts"
            case .lock: "Lock ledger"
            }
        }
    }

    private func emptyStateRow(_ title: String, detail: String) -> some View {
        VStack(alignment: .leading, spacing: 2) {
            Text(title).font(.callout).foregroundStyle(.secondary)
            Text(detail).font(.caption2).foregroundStyle(.tertiary)
                .fixedSize(horizontal: false, vertical: true)
        }
        .padding(.vertical, 4)
    }
}
