import SwiftUI
import NucleicProtocol

/// Chooses the navigation shell by width so one universal binary serves both idioms:
///   • **compact** (iPhone, and iPad in Slide Over / narrow split) → the five-tab `TabView`.
///   • **regular** (iPad full-screen / Stage Manager) → the Mac's sidebar + detail split.
///
/// Both render the SAME `RemoteStore` projection — the iPad is a third renderer of the one host
/// authority (UX_MACOS §8), not a fork. The store holds all real state, so a size-class flip
/// (e.g. resizing a Stage Manager window) only swaps chrome; sessions, connection, and the open
/// transcript survive it.
struct AdaptiveRootView: View {
    @Environment(\.horizontalSizeClass) private var sizeClass

    var body: some View {
        if sizeClass == .regular {
            SplitRootView()
        } else {
            CompactRootView()
        }
    }
}

// MARK: - Compact (iPhone / narrow multitasking)

/// The iPhone shell: a four-tab `TabView` whose *system* tab bar is suppressed in favour of our
/// own `NucleicTabBar` (below). We draw the bar ourselves because SwiftUI's per-navigation
/// `.toolbar(.hidden, for: .tabBar)` restores the system bar late (~1s) and un-animated when you
/// pop back from a pushed `SessionDetailView` — long enough that the floating "+" renders low
/// (anchored to a bottom safe-area inset that hasn't grown yet) and is briefly untappable before
/// snapping up. (Confirmed still true on iOS 26's native Liquid Glass bar — a "go native" experiment
/// reproduced exactly this ~1s un-animated restore, so owning the bar remains the right call.)
/// Owning the bar lets us slide it out/in with a spring the instant a session opens or closes, so
/// the return is smooth and the "+" reflows in one coordinated animation — and lets the "+" ride
/// beside the tabs, which the native bar can't host. `NucleicTabBar` draws its own animated
/// selection "bubble" so we keep the native look the experiment was after.
///
/// A notification tap routes to the Sessions tab, where `SessionsView` consumes `pendingRoute`
/// and pushes the detail.
struct CompactRootView: View {
    @EnvironmentObject var store: RemoteStore
    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    // Initial tab; overridable via NUCLEIC_TAB for offline UI previews.
    @State private var tab = Int(ProcessInfo.processInfo.environment["NUCLEIC_TAB"] ?? "") ?? 0
    /// The shared "+" beside the tab bar opens the project-picker composer (the per-page discs are
    /// gone on compact — this one bar owns "new chat").
    @State private var showNewChat = false
    /// Measured height of the floating bar, fed back into the bottom inset so tab content lays out
    /// exactly above it (no guessing, no overlap).
    @State private var barHeight: CGFloat = 72

    /// The bar owns the bottom edge on the top-level tabs, but yields it to a pushed session's
    /// floating glass chat bar (`compactDetailPresented`) and to the new-chat composer while it's up
    /// (`showNewChat`) — both slide it out, so the composer doesn't just stack on top of a lingering
    /// tab bar. `compactDetailPresented` is driven by the session lifecycle (`RemoteStore.open`/
    /// `closeOpen`), so the bar hides whenever a session view opens — no matter which tab launched
    /// it, not only the Sessions list. `SessionsView` additionally clears it the instant a
    /// back-swipe begins (the path empties before `onDisappear` fires) for a snappy re-show.
    private var showsTabBar: Bool { !store.compactDetailPresented && !showNewChat }

    /// The "+" is inert without control scope or a project to start in, so it's shown only when a
    /// chat could actually be kicked off (mirrors `NewChatAffordance.canStart`).
    private var canStartChat: Bool { store.canControl && !store.dashboard.projects.isEmpty }

    /// Height reserved under each tab's content for the floating bar — the measured bar height when
    /// it's up, nothing when a session or the composer hides it (so those run full-bleed).
    private var contentInset: CGFloat { showsTabBar ? barHeight : 0 }

    var body: some View {
        TabView(selection: $tab) {
            HomeView().toolbar(.hidden, for: .tabBar).tag(0)
            SessionsView().toolbar(.hidden, for: .tabBar).tag(1)
            ProjectsView().toolbar(.hidden, for: .tabBar).tag(2)
            SettingsView().toolbar(.hidden, for: .tabBar).tag(3)
        }
        // Each tab's scroll view reads this and reserves room for the floating bar directly on
        // itself (via `.compactTabBarInset()`). Neither a `safeAreaInset` nor `contentMargins` on
        // the tab root reaches the pages' scroll content through the `TabView`, so it must be applied
        // to the `List`/`ScrollView` itself. Zero (its default) on iPad, which has no bar.
        .environment(\.compactTabBarInset, contentInset)
        // The bar itself lives in an always-mounted overlay and slides on `offset`, so it travels
        // continuously off the bottom of the screen (a fixed distance past the home-indicator curve)
        // instead of a transition that removes it mid-descent and makes it pop.
        .overlay(alignment: .bottom) {
            bottomBar
                .background(
                    GeometryReader { geo in
                        Color.clear.preference(key: BarHeightKey.self, value: geo.size.height)
                    })
                .onPreferenceChange(BarHeightKey.self) { barHeight = $0 }
                .offset(y: showsTabBar ? 0 : barHeight + 120)
        }
        .animation(reduceMotion ? nil : .spring(response: 0.38, dampingFraction: 0.9),
                   value: showsTabBar)
        // The project-picker composer, presented for the shared "+" (no per-page disc on compact).
        .newChatAffordance(isPresented: $showNewChat, showsButton: false)
        // A notification tap routes to its session by jumping to the Sessions tab.
        .onChange(of: store.pendingRoute) { _, route in
            if route != nil { tab = 1 }
        }
        // A Live Activity tap with work running but nothing waiting: show the Sessions list.
        .onChange(of: store.pendingSessionsList) { _, show in
            if show { tab = 1; store.pendingSessionsList = false }
        }
    }

    /// The floating bottom bar: the tab capsule, with the "+" disc riding to its right in the width
    /// freed by dropping the To-dos tab (the iOS 26 tab-bar-plus-accessory shape).
    private var bottomBar: some View {
        HStack(spacing: 10) {
            NucleicTabBar(selection: $tab, needsYou: store.needsYouCount)
            if canStartChat {
                Button { showNewChat = true } label: {
                    Image(systemName: "plus")
                        .font(.system(size: 22, weight: .semibold))
                        .foregroundStyle(Palette.accent)
                        .frame(width: 56, height: 56)
                        .contentShape(Circle())
                        .glassCircle()
                }
                .buttonStyle(.plain)
                .accessibilityLabel("New chat")
            }
        }
        .padding(.horizontal, 16)
        .padding(.bottom, 4)
    }
}

/// A preference carrying the floating bar's measured height up to `CompactRootView`, which feeds it
/// back into each tab's bottom inset so content sits exactly above the bar.
private struct BarHeightKey: PreferenceKey {
    static var defaultValue: CGFloat = 72
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = nextValue() }
}

/// Height the compact shell's floating tab bar wants reserved at the bottom of a tab's scroll
/// content, published down the environment by `CompactRootView`. Zero on iPad (no bar).
private struct CompactTabBarInsetKey: EnvironmentKey {
    static let defaultValue: CGFloat = 0
}

extension EnvironmentValues {
    var compactTabBarInset: CGFloat {
        get { self[CompactTabBarInsetKey.self] }
        set { self[CompactTabBarInsetKey.self] = newValue }
    }
}

extension View {
    /// Reserve room for the compact shell's floating tab bar at the bottom of a scroll view, so its
    /// content clears the bar when scrolled to the end. Apply this directly to the `List`/`ScrollView`
    /// inside a tab — a bottom `safeAreaInset` on the scroll view itself reliably insets its content,
    /// unlike one on the tab root, which the `TabView` doesn't forward. A no-op at regular width
    /// (iPad), where the environment inset is 0.
    func compactTabBarInset() -> some View { modifier(CompactTabBarInsetReader()) }
}

private struct CompactTabBarInsetReader: ViewModifier {
    @Environment(\.compactTabBarInset) private var inset
    func body(content: Content) -> some View {
        content.safeAreaInset(edge: .bottom, spacing: 0) {
            Color.clear.frame(height: inset)
        }
    }
}

/// Our own bottom tab bar for the compact shell — a floating Liquid Glass capsule of four items
/// (Home, Sessions, Projects, Settings), standing in for the system tab bar so `CompactRootView`
/// can animate its visibility itself (see that view for why) while keeping the iOS 26 floating-glass
/// look. Real `glassEffect` on iOS 26; an ultra-thin material capsule with a hairline edge + soft
/// shadow on earlier systems, matching the app's `glassSurface`/`glassCircle`. The Sessions item
/// carries the "needs you" badge the system tab item used to.
struct NucleicTabBar: View {
    @Binding var selection: Int
    /// Sessions blocking on a human — the red badge on the Sessions item (was the tab item badge).
    var needsYou: Int
    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    /// Ties every item's selected-capsule to one shared geometry, so the "bubble" interpolates its
    /// frame from the old tab to the new one instead of cross-fading — the native bar's slide.
    @Namespace private var bubble

    private struct Item { let index: Int; let title: String; let icon: String }
    private let items = [
        Item(index: 0, title: "Home", icon: "house"),
        Item(index: 1, title: "Sessions", icon: "square.stack.3d.up"),
        Item(index: 2, title: "Projects", icon: "folder"),
        Item(index: 3, title: "Settings", icon: "gearshape"),
    ]

    var body: some View {
        HStack(spacing: 0) {
            ForEach(items, id: \.index) { item in
                itemButton(item)
            }
        }
        .padding(.horizontal, 6)
        .padding(.vertical, 5)
        .glassBar()
        // Drive the bubble's slide (and the label/tint crossfade) off the selection itself, so any
        // change — tap here or a programmatic jump from a notification — springs across.
        .animation(reduceMotion ? nil : .spring(response: 0.32, dampingFraction: 0.78),
                   value: selection)
    }

    private func itemButton(_ item: Item) -> some View {
        let selected = selection == item.index
        return Button {
            selection = item.index
        } label: {
            VStack(spacing: 3) {
                Image(systemName: item.icon)
                    .font(.system(size: 21, weight: selected ? .semibold : .regular))
                    .frame(height: 24)
                    .overlay(alignment: .topTrailing) {
                        if item.index == 1, needsYou > 0 {
                            Text(needsYou > 99 ? "99+" : "\(needsYou)")
                                .font(.system(size: 11, weight: .bold))
                                .foregroundStyle(.white)
                                .padding(.horizontal, 5).padding(.vertical, 1)
                                .background(Palette.attention, in: Capsule())
                                .offset(x: 14, y: -6)
                                .fixedSize()
                        }
                    }
                Text(item.title).font(.system(size: 10, weight: .medium))
            }
            .foregroundStyle(selected ? Palette.accent : Color.secondary)
            .frame(maxWidth: .infinity)
            .padding(.vertical, 8)
            // The selected item rides a soft accent-tinted capsule that slides between tabs — only
            // the selected item mounts it, and the shared `bubble` geometry makes the single capsule
            // travel to the new tab, echoing the system floating bar's highlighted-tab indicator.
            .background {
                if selected {
                    Capsule()
                        .fill(Palette.accent.opacity(0.15))
                        .matchedGeometryEffect(id: "selectionBubble", in: bubble)
                }
            }
            .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
        .accessibilityLabel(item.title)
        .accessibilityAddTraits(selected ? [.isButton, .isSelected] : .isButton)
    }
}

private extension View {
    /// The floating tab bar's Liquid Glass capsule backing — the capsule twin of `glassSurface`.
    /// Real `glassEffect` on iOS 26; an ultra-thin material capsule with a hairline edge and soft
    /// shadow on earlier systems so it still lifts off the content.
    @ViewBuilder
    func glassBar() -> some View {
        if #available(iOS 26.0, *) {
            glassEffect(.regular, in: .capsule)
        } else {
            background(.ultraThinMaterial, in: Capsule())
                .overlay(Capsule().strokeBorder(Color.primary.opacity(0.08), lineWidth: 1))
                .shadow(color: .black.opacity(0.18), radius: 10, y: 3)
        }
    }
}

// MARK: - Regular (iPad sidebar + detail)

/// What the sidebar can select. Mirrors the Mac sidebar: a few global destinations plus the
/// live sessions themselves (grouped by project). Projects are reached through the `projects`
/// destination (`ProjectsView`), which also hosts the scoped "start a chat here" composer.
enum SidebarItem: Hashable {
    case home
    case projects
    case todos
    case settings
    case session(SessionID)
}

/// The Mac's information architecture on iPad: a two-column `NavigationSplitView` whose leading
/// column carries the destinations + the project→session tree, and whose detail is the selected
/// session (or a destination view). Selection lives here and binds the same `openSessionID`
/// lifecycle the iPhone push/pop path uses — the detail is keyed by session id so switching one
/// session for another fires `open`/`closeOpen` exactly as a push/pop would.
struct SplitRootView: View {
    @EnvironmentObject var store: RemoteStore
    @State private var selection: SidebarItem? = .home
    @State private var columnVisibility: NavigationSplitViewVisibility = .all
    /// One-shot: a Cmd+N sets this, the Home composer consumes it to grab focus.
    @State private var focusNewChat = false

    var body: some View {
        NavigationSplitView(columnVisibility: $columnVisibility) {
            SplitSidebar(selection: $selection, focusNewChat: $focusNewChat)
        } detail: {
            SplitDetail(selection: selection, focusNewChat: $focusNewChat)
        }
        .navigationSplitViewStyle(.balanced)
        .background { keyboardShortcuts }
        // Notification tap → select its session directly (the regular-width analogue of the
        // compact path's jump-to-Sessions-tab). Then clear the request so it isn't re-consumed.
        .onChange(of: store.pendingRoute) { _, route in
            guard let route else { return }
            selection = .session(route)
            store.pendingRoute = nil
        }
        // A Live Activity tap with work running but nothing waiting: reveal the sidebar, which
        // always lists the live sessions (the regular-width analogue of the Sessions tab).
        .onChange(of: store.pendingSessionsList) { _, show in
            guard show else { return }
            columnVisibility = .all
            store.pendingSessionsList = false
        }
        .onAppear {
            // Offline UI previews (NUCLEIC_DEMO): open straight into one session.
            if let raw = ProcessInfo.processInfo.environment["NUCLEIC_DEMO_SESSION"], !raw.isEmpty {
                selection = .session(SessionID(rawValue: raw))
            }
        }
    }

    /// Hardware-keyboard shortcuts (Magic Keyboard on iPad), the remote's echo of the Mac's menu
    /// commands: ⌘N new chat, ⌘1–4 jump to a destination, ⌘R refresh. Carried by hidden buttons —
    /// SwiftUI still routes a shortcut to a zero-opacity button that's in the hierarchy.
    private var keyboardShortcuts: some View {
        Group {
            Button("New chat") { selection = .home; focusNewChat = true }
                .keyboardShortcut("n", modifiers: .command)
            Button("Home") { selection = .home }.keyboardShortcut("1", modifiers: .command)
            Button("Projects") { selection = .projects }.keyboardShortcut("2", modifiers: .command)
            Button("To-dos") { selection = .todos }.keyboardShortcut("3", modifiers: .command)
            Button("Settings") { selection = .settings }.keyboardShortcut("4", modifiers: .command)
            Button("Refresh") { store.refreshSessions() }.keyboardShortcut("r", modifiers: .command)
        }
        .opacity(0)
        .accessibilityHidden(true)
    }
}

/// The leading column: global destinations, then the live sessions grouped under their projects
/// (attention-first within each group), mirroring the Mac sidebar tree. The persistent
/// connection status sits in the footer — one home for it, versus the phone's per-tab inset.
private struct SplitSidebar: View {
    @EnvironmentObject var store: RemoteStore
    @Binding var selection: SidebarItem?
    var focusNewChat: Binding<Bool> = .constant(false)

    /// Live sessions grouped under their project (dashboard order), with anything whose project
    /// isn't in the dashboard collected into a trailing "Other" group. Attention-first sort.
    private var projectGroups: [(name: String, rows: [WireSessionSummary])] {
        let live = store.liveSessions.sorted(by: StatusStyle.attentionThenRecency)
        var groups: [(name: String, rows: [WireSessionSummary])] = []
        var placed = Set<SessionID>()
        for project in store.dashboard.projects {
            let rows = live.filter { $0.projectID == project.id.rawValue }
            guard !rows.isEmpty else { continue }
            groups.append((project.name, rows))
            rows.forEach { placed.insert($0.sessionID) }
        }
        let orphans = live.filter { !placed.contains($0.sessionID) }
        if !orphans.isEmpty { groups.append(("Other", orphans)) }
        return groups
    }

    var body: some View {
        List(selection: $selection) {
            Section {
                Label("Home", systemImage: "house").tag(SidebarItem.home)
                Label("Projects", systemImage: "folder").tag(SidebarItem.projects)
                Label("To-dos", systemImage: "checklist").tag(SidebarItem.todos)
                Label("Settings", systemImage: "gearshape").tag(SidebarItem.settings)
            }
            if projectGroups.isEmpty {
                Section("Sessions") {
                    Text(store.connectivity.isLive
                        ? "Start a session from Home or the Mac to see it here."
                        : "Waiting to connect to your Mac…")
                        .font(.callout).foregroundStyle(.secondary)
                }
            } else {
                ForEach(projectGroups, id: \.name) { group in
                    Section(group.name) {
                        ForEach(group.rows, id: \.sessionID) { summary in
                            SessionRow(summary: summary, showProjectName: false)
                                .tag(SidebarItem.session(summary.sessionID))
                                .listRowBackground(SessionRowWash(summary: summary))
                                .swipeActions(edge: .leading) {
                                    Button {
                                        store.setFavorite(summary.sessionID, !summary.favorite)
                                    } label: {
                                        Label("Favorite", systemImage: summary.favorite ? "star.slash" : "star.fill")
                                    }.tint(.yellow)
                                }
                                .swipeActions(edge: .trailing) {
                                    Button(role: .destructive) {
                                        store.deleteSession(summary.sessionID)
                                    } label: { Label("Delete", systemImage: "trash") }
                                    Button {
                                        store.setArchived(summary.sessionID, !summary.archived)
                                    } label: {
                                        Label(summary.archived ? "Unarchive" : "Archive",
                                              systemImage: summary.archived ? "tray.and.arrow.up" : "archivebox")
                                    }.tint(.gray)
                                }
                        }
                    }
                }
            }
        }
        .listStyle(.sidebar)
        // Every Mac's sessions are merged into this sidebar (mesh P3), so a single host name would
        // misrepresent it — title it for the app.
        .navigationTitle("Nucleic")
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button {
                    selection = .home
                    focusNewChat.wrappedValue = true
                } label: {
                    Image(systemName: "square.and.pencil")
                }
                .help("New chat (⌘N)")
                .disabled(!store.canControl)
            }
            ToolbarItem(placement: .primaryAction) {
                Button { store.refreshSessions() } label: {
                    Image(systemName: "arrow.clockwise")
                }
                .help("Refresh (⌘R)")
            }
        }
    }
}

/// The detail column. A selected session reuses the existing `SessionDetailView` unchanged
/// (it owns its own subscribe/unsubscribe on appear/disappear); it's keyed `.id(sessionID)` so
/// switching sessions remounts and drives that lifecycle. The destination views already embed
/// their own `NavigationStack`, so they're shown directly.
private struct SplitDetail: View {
    let selection: SidebarItem?
    var focusNewChat: Binding<Bool> = .constant(false)

    var body: some View {
        switch selection {
        case .home, .none:
            HomeView(focusNewChat: focusNewChat)
        case .projects:
            ProjectsView()
        case .todos:
            TodosView()
        case .settings:
            SettingsView()
        case .session(let id):
            NavigationStack {
                SessionDetailView(sessionID: id)
            }
            .id(id)
        }
    }
}
