import SwiftUI
import NucleicProtocol

/// One session: a segmented Transcript/Diff read view over a status-driven action area
/// (UX_IOS §4). Subscribes on appear, unsubscribes on disappear.
struct SessionDetailView: View {
    @EnvironmentObject var store: RemoteStore
    let sessionID: SessionID
    @State private var tab = 0
    @State private var draft = ""
    @State private var showRename = false
    @State private var renameDraft = ""
    @State private var showIntegrate = false

    private var summary: WireSessionSummary? {
        store.sessions.first { $0.sessionID == sessionID }
    }

    var body: some View {
        VStack(spacing: 0) {
            Picker("", selection: $tab) {
                Text("Transcript").tag(0)
                Text("Diff").tag(1)
            }
            .pickerStyle(.segmented)
            .padding(.horizontal)
            .padding(.bottom, 8)

            if store.canControl, let summary { controlBar(summary) }

            Divider()

            if tab == 0 {
                TranscriptList(events: store.openEvents)
            } else {
                DiffSummaryView(diffStat: summary?.diffStat)
            }

            actionArea
        }
        .navigationTitle(summary?.title ?? "Session")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            if let summary, store.canControl {
                ToolbarItem(placement: .topBarTrailing) { sessionMenu(summary) }
            }
        }
        .alert("Rename chat", isPresented: $showRename) {
            TextField("Title", text: $renameDraft)
            Button("Cancel", role: .cancel) {}
            Button("Rename") { store.renameSession(sessionID, to: renameDraft) }
        }
        .confirmationDialog("Integrate this branch", isPresented: $showIntegrate, titleVisibility: .visible) {
            Button("Merge") { store.integrate(sessionID, .merge) }
            Button("Squash & merge") { store.integrate(sessionID, .squash) }
            Button("Rebase") { store.integrate(sessionID, .rebase) }
            Button("Cancel", role: .cancel) {}
        }
        .onAppear { store.open(sessionID) }
        .onDisappear { store.closeOpen() }
    }

    /// Whether this session's project is under Nucleic Control (gates Orchestra + autoship).
    private var controlled: Bool {
        store.dashboard.projects.first { $0.id.rawValue == summary?.projectID }?.isNucleicControlled ?? false
    }

    private var modelBinding: Binding<String?> {
        Binding(get: { summary?.model }, set: { store.setSessionModel(sessionID, $0) })
    }
    private var effortBinding: Binding<String> {
        Binding(
            get: {
                let fallback = store.modelCatalog.fallbackEffort.isEmpty
                    ? MobileEfforts.fallback : store.modelCatalog.fallbackEffort
                return summary?.effort ?? fallback
            },
            set: { store.setSessionEffort(sessionID, $0) })
    }

    /// Live context-window occupancy (newest turn's input tokens ÷ the model's window), read
    /// from the transcript exactly as the Mac header does.
    private var contextPercent: Int? {
        let used = store.openEvents.reversed().lazy.compactMap { event -> Int? in
            switch event.kind {
            case .turnCompleted(let turn): return turn.usage?.contextInputTokens
            case .usage(let usage): return usage.contextInputTokens
            default: return nil
            }
        }.first { $0 > 0 }
        guard let used else { return nil }
        let window = store.modelCatalog.contextWindow(summary?.model)
        guard window > 0 else { return nil }
        return min(100, Int((Double(used) / Double(window)) * 100))
    }

    /// The model / effort / auto controls for the open session — the mobile echo of the Mac's
    /// session header. Reads current state from the summary; each change is a control intent.
    @ViewBuilder
    private func controlBar(_ summary: WireSessionSummary) -> some View {
        HStack(spacing: 10) {
            // A session's backend is fixed at creation, so only same-backend models are offered.
            ModelMenu(model: modelBinding, catalog: store.modelCatalog, backend: summary.backend)
            EffortMenu(effort: effortBinding, catalog: store.modelCatalog,
                       // No explicit model yet → use the session backend's default, so the menu
                       // shows that backend's effort range rather than collapsing to one level.
                       modelSKU: summary.model ?? store.modelCatalog.models(for: summary.backend).first?.sku,
                       controlled: controlled,
                       // Orchestra is a one-way latch for an existing chat — the host won't turn it
                       // off, so disable the other levels here to match.
                       lockWhenOrchestra: true)
            Spacer(minLength: 4)
            if let percent = contextPercent {
                Label("\(percent)%", systemImage: "gauge.with.dots.needle.33percent")
                    .font(.caption2).foregroundStyle(.secondary)
                    .help("Context window used")
            }
            Toggle(isOn: Binding(get: { summary.auto }, set: { store.setSessionAuto(sessionID, $0) })) {
                Label("Auto", systemImage: summary.auto ? "bolt.fill" : "bolt.slash")
            }
            .toggleStyle(.button).tint(Palette.accent).font(.caption2)
        }
        .padding(.horizontal)
        .padding(.bottom, 6)
    }

    @ViewBuilder
    private func sessionMenu(_ summary: WireSessionSummary) -> some View {
        Menu {
            Button { renameDraft = summary.title; showRename = true } label: {
                Label("Rename", systemImage: "pencil")
            }
            Button { store.setFavorite(sessionID, !summary.favorite) } label: {
                Label(summary.favorite ? "Unfavorite" : "Favorite",
                      systemImage: summary.favorite ? "star.slash" : "star")
            }
            // Autoship is a Nucleic Control capability — offered only for Control projects, and
            // the host couples it with auto-approval (enabling it turns Auto on).
            if controlled {
                Button { store.setSessionAutoShip(sessionID, !summary.autoShip) } label: {
                    Label(summary.autoShip ? "Turn off Autoship" : "Turn on Autoship",
                          systemImage: summary.autoShip ? "shippingbox.fill" : "shippingbox")
                }
            }
            if summary.status == .running {
                Button { store.interrupt(sessionID) } label: { Label("Interrupt", systemImage: "stop.circle") }
            }
            Section {
                Button { showIntegrate = true } label: { Label("Integrate…", systemImage: "arrow.triangle.merge") }
                Button { store.setArchived(sessionID, !summary.archived) } label: {
                    Label(summary.archived ? "Unarchive" : "Archive",
                          systemImage: summary.archived ? "tray.and.arrow.up" : "archivebox")
                }
            }
            Button(role: .destructive) { store.deleteSession(sessionID) } label: {
                Label("Delete", systemImage: "trash")
            }
        } label: {
            Image(systemName: "ellipsis.circle")
        }
    }

    @ViewBuilder
    private var actionArea: some View {
        Divider()
        if let approval = store.openApprovals.first {
            ApprovalCardView(approval: approval)
                .padding()
        } else if let status = summary?.status {
            let running = status == .running
            let canCompose = running || status == .awaitingInput || status.isTerminal
            VStack(spacing: 8) {
                // A follow-up submitted mid-turn, held by the host to send when the current
                // turn finishes.
                if let queued = summary?.queuedMessage, !queued.isEmpty {
                    HStack(spacing: 6) {
                        Image(systemName: "clock").foregroundStyle(.secondary)
                        Text(queued).lineLimit(1).truncationMode(.tail)
                            .foregroundStyle(.secondary)
                        Spacer(minLength: 8)
                    }
                    .font(.footnote)
                    .frame(maxWidth: .infinity, alignment: .leading)
                }
                if running {
                    HStack(spacing: 8) {
                        ProgressView().controlSize(.small)
                        Text("Agent is working…").font(.footnote).foregroundStyle(.secondary)
                        Spacer()
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)
                }
                if canCompose {
                    HStack(spacing: 8) {
                        TextField(running ? "Queue a follow-up…" : "Send a follow-up…",
                                  text: $draft, axis: .vertical)
                            .textFieldStyle(.roundedBorder)
                            .lineLimit(1...4)
                            .keyboardDismissable()
                        Button {
                            store.sendInput(draft, to: sessionID)
                            draft = ""
                        } label: {
                            Image(systemName: running ? "clock.arrow.circlepath" : "arrow.up.circle.fill")
                                .font(.title2)
                        }
                        .disabled(draft.trimmingCharacters(in: .whitespaces).isEmpty || !store.connectivity.isLive)
                    }
                }
            }
            .padding()
        }
    }
}

struct TranscriptList: View {
    let events: [AgentEvent]
    @AppStorage("nucleic.showRawEvents") private var showRaw = false
    @AppStorage("nucleic.showLockEvents") private var showLockEvents = true

    private var items: [TranscriptItem] {
        TranscriptProjection.build(events, showRaw: showRaw, showLockEvents: showLockEvents)
    }

    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                LazyVStack(alignment: .leading, spacing: 10) {
                    ForEach(items) { item in
                        TranscriptRow(item: item).id(item.id)
                    }
                }
                .padding()
            }
            // A drag on the transcript dismisses the keyboard, so a tall multiline composer can
            // be put away without leaving the session.
            .scrollDismissesKeyboard(.interactively)
            // Coalescing means item count lags event count; key the autoscroll on the raw stream
            // so every streamed delta keeps the view pinned to the bottom.
            .onChange(of: events.count) {
                if let last = items.last { withAnimation { proxy.scrollTo(last.id, anchor: .bottom) } }
            }
        }
    }
}

struct DiffSummaryView: View {
    let diffStat: DiffStat?

    var body: some View {
        VStack(spacing: 12) {
            if let diff = diffStat, diff.filesChanged > 0 {
                Label("\(diff.filesChanged) file\(diff.filesChanged == 1 ? "" : "s") changed", systemImage: "doc.text")
                    .font(.headline)
                HStack(spacing: 16) {
                    Text("+\(diff.added)").foregroundStyle(.green)
                    Text("−\(diff.removed)").foregroundStyle(.red)
                }
                .font(.title3.monospacedDigit())
                Text("Open the Mac for the full patch.")
                    .font(.footnote).foregroundStyle(.secondary)
            } else {
                ContentUnavailableView("No changes yet", systemImage: "doc")
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .padding()
    }
}
