import SwiftUI
import NucleicCore

/// Live state for one scratch chat, driven off the session's controller by id (not the single
/// `openSession*` slot the main window uses). Seeds from the transcript so far, then follows the
/// controller's event stream, refreshing the session record + pending approvals as events arrive.
@MainActor
@Observable
final class EphemeralChatModel {
    let sessionID: SessionID
    var events: [AgentEvent] = []
    var session: Session?
    var pendingApprovals: [ApprovalRequest] = []
    /// True once the initial seed has loaded, so the view can hold layout until content is present.
    var loaded = false

    private var streamTask: Task<Void, Never>?

    init(sessionID: SessionID) { self.sessionID = sessionID }

    /// Whether a turn is in flight (drives the Stop button): running, provisioning, or blocked
    /// mid-turn on an approval.
    var isBusy: Bool { session?.status.hasTurnInFlight ?? false }

    /// Whether to show the "Working…" indicator — only while actually working, not while parked on
    /// an approval (the approval bar covers that).
    var isWorking: Bool {
        switch session?.status {
        case .running, .provisioning: return true
        default: return false
        }
    }

    func start(store: AppStore) {
        guard streamTask == nil else { return }
        streamTask = Task { [sessionID] in
            events = await store.liveTranscript(sessionID)
            await refreshSnapshot(store: store)
            loaded = true
            guard let stream = await store.subscribeLive(sessionID) else { return }
            let known = Set(events.map(\.seq))
            var seen = known
            for await event in stream {
                if Task.isCancelled { break }
                if !seen.contains(event.seq) {
                    events.append(event)
                    seen.insert(event.seq)
                }
                await refreshSnapshot(store: store)
            }
        }
    }

    func stop() {
        streamTask?.cancel()
        streamTask = nil
    }

    private func refreshSnapshot(store: AppStore) async {
        guard let snapshot = await store.liveSnapshot(sessionID) else { return }
        session = snapshot.session
        pendingApprovals = snapshot.pendingApprovals
    }
}

/// The chat surface hosted inside an `EphemeralChatWindowController` window: a header with the
/// Ephemeral/Saved switch and Discard, the transcript (reusing the app's `TranscriptRow`), an
/// approval bar routed to this session, and a composer. Deliberately lighter than the main
/// `SessionDetailView` — no worktree/git/build chrome, since a scratch chat has no project.
struct EphemeralChatWindowView: View {
    @Environment(AppStore.self) private var store
    @AppStorage(SubmitKeyMode.storageKey) private var submitKeyRaw = SubmitKeyMode.modifierSends.rawValue

    let sessionID: SessionID

    @State private var model: EphemeralChatModel
    @State private var draft = ""
    @State private var composerHeight = ChatInputField.restingHeight

    init(sessionID: SessionID) {
        self.sessionID = sessionID
        _model = State(initialValue: EphemeralChatModel(sessionID: sessionID))
    }

    private var submitMode: SubmitKeyMode { SubmitKeyMode(rawValue: submitKeyRaw) ?? .modifierSends }
    private var saved: Bool { store.isEphemeralSaved(sessionID) }

    var body: some View {
        VStack(spacing: 0) {
            header
            Divider()
            transcript
            if let approval = model.pendingApprovals.first {
                Divider()
                Group {
                    if approval.toolName == AskUserQuestion.toolName,
                       let questions = AskUserQuestion.questions(from: approval.input) {
                        AskUserQuestionBar(request: approval, questions: questions, sessionID: sessionID)
                    } else {
                        ApprovalBar(request: approval, sessionID: sessionID)
                    }
                }
            }
            Divider()
            composer
        }
        .task { model.start(store: store) }
        .onDisappear { model.stop() }
    }

    private var header: some View {
        HStack(spacing: 10) {
            Image(systemName: "bolt")
                .foregroundStyle(.secondary)
            Text(model.session?.title ?? "Scratch chat")
                .font(.headline)
                .lineLimit(1)
            Spacer()
            // Ephemeral ⇄ Saved switch: a Saved chat is kept (project-less) after its window closes;
            // an ephemeral one is discarded with its scratch dir. Reflects `AppStore`'s Saved set.
            Button {
                store.setEphemeralPersistence(sessionID, saved: !saved)
            } label: {
                Label(saved ? "Saved" : "Ephemeral",
                      systemImage: saved ? "tray.and.arrow.down" : "bolt")
            }
            .help(saved
                ? "Saved: this scratch chat is kept (project-less) after you close the window."
                : "Ephemeral: this chat and its scratch workspace are discarded when you close the window.")
            if model.isBusy {
                Button { Task { await store.interruptSession(sessionID) } } label: {
                    Label("Stop", systemImage: "stop.circle")
                }
                .help("Interrupt the running turn")
            }
            Button(role: .destructive) {
                Task { await store.deleteEphemeralSession(sessionID) }
                EphemeralChatWindowController.shared.close(sessionID)
            } label: {
                Label("Discard", systemImage: "trash")
            }
            .help("Discard this scratch chat now, removing its scratch workspace.")
        }
        .labelStyle(.titleAndIcon)
        .padding(.horizontal, 12)
        .padding(.vertical, 8)
    }

    private var transcript: some View {
        ScrollViewReader { proxy in
            ScrollView {
                VStack(alignment: .leading, spacing: 6) {
                    ForEach(TranscriptProjection.items(model.events, worktreeRoot: model.session?.worktreePath).items) { item in
                        TranscriptRow(item: item)
                            .id(item.id)
                    }
                    if model.isWorking {
                        WorkingIndicator(text: "Working…").id(Self.workingID)
                    }
                    Color.clear.frame(height: 1).id(Self.bottomID)
                }
                .padding(.horizontal, 14)
                .padding(.vertical, 10)
                .frame(maxWidth: .infinity, alignment: .leading)
            }
            .defaultScrollAnchor(.bottom)
            // Follow live output to the bottom as events stream in.
            .onChange(of: model.events.count) {
                withAnimation(.easeOut(duration: 0.15)) { proxy.scrollTo(Self.bottomID, anchor: .bottom) }
            }
        }
    }

    private var composer: some View {
        HStack(alignment: .bottom, spacing: 8) {
            ChatInputField(
                text: $draft, placeholder: "Message the scratch agent…",
                submitMode: submitMode, isEnabled: model.loaded,
                height: $composerHeight, onSend: { _ in send() })
                .frame(height: composerHeight)
                .padding(8)
                .background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 8))
            Button { send() } label: { SubmitKeyIcon(mode: submitMode) }
                .buttonStyle(.plain)
                .frame(width: 28)
                .disabled(draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
        }
        .padding(12)
    }

    private func send() {
        let text = draft.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !text.isEmpty else { return }
        draft = ""
        Task { await store.sendToSession(sessionID, text) }
    }

    private static let workingID = "ephemeral.working"
    private static let bottomID = "ephemeral.bottom"
}
