import SwiftUI
import NucleicProtocol

/// Attention-first home (UX_IOS §3): NEEDS YOU pinned at top, then RUNNING, then DONE. Archived
/// chats are hidden (matching the Mac sidebar); disposition splits "needs you" from "done".
struct SessionsView: View {
    @EnvironmentObject var store: RemoteStore
    @State private var showArchived = false
    @State private var path = NavigationPath()

    private var grouped: [(title: String, rows: [WireSessionSummary])] {
        let pool = (showArchived ? store.sessions : store.liveSessions)
            .sorted(by: StatusStyle.attentionThenRecency)
        // One pass, first bucket wins — the old `done = pool.filter { !needs.contains($0) … }`
        // ran O(rows²) full-summary equality scans on every body evaluation.
        var needs: [WireSessionSummary] = [], running: [WireSessionSummary] = [], done: [WireSessionSummary] = []
        for summary in pool {
            if summary.status.needsYou(summary.disposition) { needs.append(summary) }
            else if summary.status == .running || summary.status == .provisioning || summary.status == .idle {
                running.append(summary)
            } else { done.append(summary) }
        }
        return [("Needs you", needs), ("Running", running), ("Done", done)].filter { !$0.rows.isEmpty }
    }

    var body: some View {
        NavigationStack(path: $path) {
            Group {
                if store.liveSessions.isEmpty && !showArchived {
                    ContentUnavailableView(
                        "No sessions", systemImage: "square.stack.3d.up",
                        description: Text(store.connectivity.isLive
                            ? "Start a session from Home or the Mac to see it here."
                            : "Waiting to connect to your Mac…"))
                } else {
                    List {
                        // Offline: the rows below are the saved history, not a live view — say so.
                        if !store.connectivity.isLive {
                            Section {
                                HStack(spacing: 8) {
                                    Image(systemName: "wifi.slash").foregroundStyle(.secondary)
                                    Text("Offline — showing saved chats")
                                        .font(.footnote).foregroundStyle(.secondary)
                                    Spacer(minLength: 8)
                                    if store.isPaired {
                                        Button("Reconnect") { store.reconnect() }
                                            .font(.caption.weight(.semibold))
                                            .buttonStyle(.borderless)
                                    }
                                }
                            }
                            .listRowBackground(Color.clear)
                        }
                        ForEach(grouped, id: \.title) { group in
                            Section(group.title.uppercased()) {
                                ForEach(group.rows, id: \.sessionID) { summary in
                                    NavigationLink(value: summary.sessionID) {
                                        SessionRow(summary: summary)
                                    }
                                    .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(.insetGrouped)
                    .refreshable { store.refreshSessions() }
                    .compactTabBarInset()
                }
            }
            .navigationTitle("Sessions")
            .navigationDestination(for: SessionID.self) { id in
                SessionDetailView(sessionID: id)
            }
            // Offline UI previews (with NUCLEIC_DEMO): jump straight into one session.
            .onAppear {
                if let raw = ProcessInfo.processInfo.environment["NUCLEIC_DEMO_SESSION"],
                    !raw.isEmpty, path.isEmpty {
                    path.append(SessionID(rawValue: raw))
                }
                consumeRoute()
                // Sync the shell's tab-bar visibility to the current push depth on (re)appear —
                // `onChange` alone won't fire for a path restored across a remount.
                store.compactDetailPresented = !path.isEmpty
            }
            // A notification tap while this tab is already up.
            .onChange(of: store.pendingRoute) { consumeRoute() }
            // The tab bar's visibility is owned by the session lifecycle (`RemoteStore.open`/
            // `closeOpen`), so it hides/shows no matter which tab a chat opens from. This handler is
            // just the responsiveness refinement for *this* tab: the path empties the instant a
            // back-swipe/tap begins, so clearing here slides the bar back in immediately instead of
            // waiting for `SessionDetailView.onDisappear` (which fires only at the end of the pop).
            .onChange(of: path) { _, newPath in
                if newPath.isEmpty { store.compactDetailPresented = false }
            }
        }
    }

    /// Push the session a notification tap asked for, then clear the request.
    private func consumeRoute() {
        guard let route = store.pendingRoute else { return }
        store.pendingRoute = nil
        path.append(route)
    }
}

struct SessionRow: View {
    @EnvironmentObject var store: RemoteStore
    let summary: WireSessionSummary
    /// Show the project name as subtext. Suppressed when the row already lives inside
    /// its project's own section (e.g. `ProjectDetailView`), where the name is redundant.
    var showProjectName = true

    /// Whether Orchestra is latched on (the effort carries the sentinel).
    private var orchestra: Bool {
        guard let effort = summary.effort else { return false }
        return effort.caseInsensitiveCompare(store.modelCatalog.orchestraSentinelOrFallback) == .orderedSame
    }

    var body: some View {
        HStack(spacing: 12) {
            Image(systemName: StatusStyle.symbol(summary.status, disposition: summary.disposition))
                .foregroundStyle(Palette.status(summary.status, disposition: summary.disposition))
                .font(.title3)
                .frame(width: 24)
            VStack(alignment: .leading, spacing: 2) {
                HStack(spacing: 5) {
                    if summary.favorite {
                        Image(systemName: "star.fill").font(.caption2).foregroundStyle(.yellow)
                    }
                    Text(summary.title).font(.body.weight(.medium)).lineLimit(1)
                    // The same trailing markers the Mac sidebar row carries: auto-approval
                    // bolt, Orchestra note, autoship box. Suppressed on a moved-away tombstone.
                    if summary.movedTo == nil {
                        if summary.auto {
                            Image(systemName: "bolt.fill").font(.caption2).foregroundStyle(Palette.accent)
                        }
                        if orchestra {
                            Image(systemName: "music.note.list").font(.caption2).foregroundStyle(Palette.orchestra)
                        }
                        if summary.autoShip {
                            Image(systemName: "shippingbox.fill").font(.caption2).foregroundStyle(Palette.accent)
                        }
                    }
                }
                if let moved = summary.movedTo {
                    // Moved to another Mac (mesh P5): the name is baked by the host, so the phone
                    // renders it directly without needing to know that Mac itself.
                    Label("Moved to \(moved.deviceName)", systemImage: "arrow.up.forward")
                        .font(.caption).foregroundStyle(.secondary).lineLimit(1)
                } else if let arrived = summary.arrivedFrom {
                    // Arrived from another Mac (mesh P5) — subtle provenance, with the project name
                    // folded in when it would otherwise show.
                    Label(showProjectName ? "\(summary.projectName) · from \(arrived.deviceName)"
                                          : "from \(arrived.deviceName)",
                          systemImage: "arrow.down.forward")
                        .font(.caption).foregroundStyle(.secondary).lineLimit(1)
                } else if showProjectName {
                    // With more than one Mac connected, tag the owning Mac so the merged list stays
                    // distinguishable (mesh P3); a single-Mac view just shows the project.
                    if store.connectedHostCount > 1, let host = store.hostName(forSession: summary.sessionID) {
                        Text("\(summary.projectName) · \(host)").font(.caption).foregroundStyle(.secondary)
                    } else {
                        Text(summary.projectName).font(.caption).foregroundStyle(.secondary)
                    }
                }
            }
            Spacer()
            VStack(alignment: .trailing, spacing: 2) {
                if summary.pendingApprovalCount > 0 {
                    Text("\(summary.pendingApprovalCount)")
                        .font(.caption2.bold())
                        .padding(.horizontal, 6).padding(.vertical, 2)
                        .background(Palette.attention, in: Capsule())
                        .foregroundStyle(.white)
                }
                if let diff = summary.diffStat, diff.filesChanged > 0 {
                    Text("+\(diff.added)/−\(diff.removed)")
                        .font(.caption.monospacedDigit())
                        .foregroundStyle(.secondary)
                }
            }
        }
        .padding(.vertical, 2)
    }
}

/// The Mac sidebar's row washes, mirrored: a breathing amber fill on a chat that's blocked on
/// you, and a steady green fill on a chat that finished while you weren't looking. Applied as
/// a `listRowBackground` so the wash sits under the whole row.
struct SessionRowWash: View {
    @EnvironmentObject var store: RemoteStore
    let summary: WireSessionSummary
    @Environment(\.accessibilityReduceMotion) private var reduceMotion
    @State private var pulsing = false

    var body: some View {
        ZStack {
            Color(.secondarySystemGroupedBackground)  // the list row's normal surface
            if summary.status.needsYou(summary.disposition) {
                // Breathing amber (0.10 → 0.30 on a 1.3s ease, matching the Mac); a steady
                // mid-tint under Reduce Motion.
                Palette.attention.opacity(reduceMotion ? 0.18 : (pulsing ? 0.30 : 0.10))
                    .animation(
                        reduceMotion ? nil
                            : .easeInOut(duration: 1.3).repeatForever(autoreverses: true),
                        value: pulsing)
                    .onAppear { pulsing = true }
            } else if store.unseenCompletion(summary) {
                Palette.success.opacity(0.18)
            }
        }
    }
}
