import SwiftUI
import NucleicCore

/// The "start a new chat" composer: pick a project (with its model, effort, auto/ship,
/// and base branch/worktree), type the first message, and start a chat. Shared by two
/// hosts — pinned inline at the bottom of the home dashboard, and floated over the
/// window as the ⌘⇧N quick-launch panel (`NewChatComposerSheet`). Both drive the same
/// state and `start()`, so the two presentations can't drift.
struct NewChatComposer: View {
    @Environment(AppStore.self) private var store
    @Environment(\.dismiss) private var dismiss
    /// Persisted so the composer reopens on the last project you picked — shared by the
    /// home bar and the floating panel so they agree on the selection.
    @AppStorage("nucleic.home.lastProjectID") private var lastProjectID = ""
    @AppStorage(SubmitKeyMode.storageKey) private var submitKeyRaw = SubmitKeyMode.modifierSends.rawValue
    /// Drives the composer's accent: lavender for a Nucleic Control project, matching how
    /// that project's own session is themed (read straight from the same setting `RootView` uses).
    @AppStorage(ColorVisionMode.storageKey) private var colorVisionRaw = ColorVisionMode.standard.rawValue

    /// The first-message draft. The home bar binds this to `store.homeDraft` (persists across
    /// navigation); the floating panel binds a transient local draft that clears on dismiss.
    @Binding var draft: String
    /// Floated over the window (⌘N / ⌘⇧N) rather than pinned inline: adds a titled header with a
    /// Cancel action, sizes to a fixed panel width, and dismisses itself once a chat starts.
    var floating = false
    /// When floating, whether starting the chat leaves the user on their current view (⌘N)
    /// rather than jumping to the new session (⌘⇧N). Ignored for the inline home bar.
    var background = false

    @State private var composerHeight = ChatInputField.restingHeight
    /// Files/images staged in the composer (attach button, paste, or drag) to send with the
    /// first message of the new chat; materialized into its worktree once it's created.
    @State private var attachments: [ComposerAttachment] = []
    /// True while a file/image drag hovers the composer, to light up the drop zone.
    @State private var composerDropTargeted = false
    @State private var showingAddProject = false
    @State private var modelOverride: String?
    @State private var effortOverride: String?
    @State private var branches: [String] = []
    @State private var selectedBranch: String?
    @State private var useWorktree = true
    /// nil until the user explicitly toggles, so the button reflects the app default.
    @State private var autoOverride: Bool?
    @State private var shipOverride: Bool?
    /// False until the composer's initial async state (project resolution, branch load) has
    /// settled, so the rows lay out in their final positions on open instead of animating in
    /// from a looser spacing and snapping into place. Flipped true after the first load so
    /// genuine interactions afterward (e.g. the Ship toggle) still animate.
    @State private var layoutAnimationsEnabled = false

    /// Width reserved for the send button, so the bottom controls row can be
    /// inset to line up with the text field's trailing edge.
    private let sendButtonWidth: CGFloat = 28

    private var submitMode: SubmitKeyMode { SubmitKeyMode(rawValue: submitKeyRaw) ?? .modifierSends }

    var body: some View {
        content
            // Hold animations off while the composer's opening state settles (see
            // `layoutAnimationsEnabled`); a value-based `.animation` lower in the tree would
            // otherwise re-animate that first settle, so it's gated on the same flag.
            .transaction { if !layoutAnimationsEnabled { $0.animation = nil } }
            .task(id: selectedSummary?.id) {
                await loadBranches()
                layoutAnimationsEnabled = true
            }
            .sheet(isPresented: $showingAddProject) { AddProjectSheet() }
    }

    @ViewBuilder
    private var content: some View {
        if floating {
            VStack(alignment: .leading, spacing: 14) {
                HStack {
                    Text("New chat").font(.headline)
                    Spacer()
                    Text("\(startHint) to start & open · \(backgroundHint) in background")
                        .font(.caption2).foregroundStyle(.tertiary)
                    Button("Cancel", role: .cancel) { dismiss() }
                        .keyboardShortcut(.cancelAction)
                        .tint(nil)  // drop the inherited app accent; dimmed teal is hard to read in dark mode
                }
                composerCore
            }
            .padding(20)
            .frame(width: 640)
        } else {
            composerCore.padding(12)
        }
    }

    /// The composer proper — the project/branch row, the text field with its send button,
    /// and the mode/model/effort row. Identical in both presentations.
    private var composerCore: some View {
        VStack(spacing: 8) {
            HStack(spacing: 8) {
                Menu {
                    // One picker across the mesh (mesh session sync): local and peer-Mac
                    // projects interleave name-sorted, a globe marking the remote ones —
                    // matching the sidebar exactly.
                    ForEach(store.sidebarProjects, id: \.rowID) { project in
                        Button {
                            lastProjectID = project.id.rawValue
                        } label: {
                            if project.isRemote {
                                Label(project.name, systemImage: "globe")
                            } else {
                                Text(project.name)
                            }
                        }
                    }
                    if !store.sidebarProjects.isEmpty { Divider() }
                    Button("New Project…", systemImage: "folder.badge.plus") {
                        showingAddProject = true
                    }
                } label: {
                    HStack(spacing: 4) {
                        Image(systemName: "folder")
                        Text(selectedSummary?.name ?? "Select a project")
                        if isRemoteProject {
                            Image(systemName: "globe").font(.caption2).foregroundStyle(.secondary)
                        }
                        Image(systemName: "chevron.down").font(.caption2)
                    }
                }
                .menuStyle(.button)
                // The button menu style draws its own disclosure chevron; we supply a
                // custom-styled one in the label above, so hide the built-in to avoid
                // a double chevron.
                .menuIndicator(.hidden)
                .fixedSize()
                // nvrsion projects have no per-session base branch and no per-session worktree —
                // every session runs on the one shared workspace — so neither the branch picker
                // nor the worktree toggle has anything to control; hide both. A remote project
                // hides them too: the base branch and worktree are the owner's affairs (the
                // wire start uses the owner's defaults).
                if selectedProject?.nvrsionActive != true && !isRemoteProject {
                    branchMenu
                    Toggle(isOn: $useWorktree) {
                        Label("Worktree", systemImage: "arrow.triangle.branch")
                    }
                    .toggleStyle(.checkbox)
                    .help(useWorktree
                        ? "Run on an isolated worktree branched from \(effectiveBranch)."
                        : "Run directly in the main checkout on \(effectiveBranch) — no isolation.")
                }
                Spacer()
                // Provider service status (Claude / OpenAI / xAI) — a glanceable health pill,
                // expanding to the current incidents on click/hover. Trailing-inset so its right
                // edge lines up with the text field's trailing edge (matching the controls row).
                StatusFeedIndicator(palette: composerPalette)
            }
            .font(.callout)
            // Past the text field's trailing edge (sendButtonWidth + 8), plus an optical nudge
            // left so the pill doesn't read as hugging the right edge.
            .padding(.trailing, sendButtonWidth + 20)
            // Staged attachments (attach button / paste / drag), shown above the field.
            if !attachments.isEmpty {
                ComposerAttachmentBar(attachments: $attachments)
                    .padding(.trailing, sendButtonWidth + 8)
            }
            HStack(alignment: .bottom, spacing: 8) {
                ChatInputField(
                    text: $draft, placeholder: "Start a new chat…",
                    submitMode: submitMode, isEnabled: selectedSummary != nil,
                    height: $composerHeight, onSend: { start(background: $0) },
                    // Paste/drag can't stage files for a remote project — they don't ride
                    // the mesh start verb (the attach button is already disabled).
                    onAttach: { if !isRemoteProject { attachments.append(contentsOf: $0) } },
                    onDropTargetedChanged: { composerDropTargeted = $0 })
                    .frame(height: composerHeight)
                    .padding(8)
                    .background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 8))
                    // A persistent lavender ring cues a Nucleic Control project. When Orchestra
                    // is on, its own purple glow owns the ring, so don't double it up.
                    .overlay {
                        if isControlProject && !isOrchestra {
                            RoundedRectangle(cornerRadius: 8)
                                .strokeBorder(composerPalette.accent.opacity(0.55), lineWidth: 1)
                                .allowsHitTesting(false)
                        }
                    }
                    // Light up the field's border while a file/image drag hovers it.
                    .overlay {
                        if composerDropTargeted {
                            RoundedRectangle(cornerRadius: 8)
                                .strokeBorder(composerPalette.accent, lineWidth: 2)
                                .allowsHitTesting(false)
                        }
                    }
                    .orchestraGlow(active: isOrchestra)
                Button { start(background: background) } label: {
                    SubmitKeyIcon(mode: submitMode)
                }
                .buttonStyle(.plain)
                .frame(width: sendButtonWidth)
                .help(submitMode.detail)
                .disabled(selectedSummary == nil || !canStart)
            }
            HStack(spacing: 8) {
                autoToggle
                // Shipping implies Auto, so the Ship toggle is only meaningful when Auto
                // is on. It slides out from behind the Auto button when Auto is enabled
                // and tucks back under it when Auto is turned off. Under nvrsion the trunk is
                // promoted at the project level, so a per-chat Merge toggle is meaningless
                // and hidden entirely.
                // Hidden for a remote project too: autoship is configured on the owner (the
                // wire start carries no ship flag), so a toggle here would promise a merge
                // the owner may not run.
                if effectiveAuto && !nvrsionActive && !isRemoteProject {
                    shipToggle
                        .transition(.move(edge: .leading).combined(with: .opacity))
                }
                // Attach files/images. Sits with the left-hand controls (not beside the field) so
                // it never indents the composer. Disabled for a remote project — attachments
                // materialize into a local worktree, and they don't ride the mesh start verb.
                ComposerAttachButton(isEnabled: selectedProject != nil) { picked in
                    attachments.append(contentsOf: picked)
                }
                Spacer()
                modelMenu
                effortMenu
            }
            .animation(layoutAnimationsEnabled ? .easeInOut(duration: 0.25) : nil, value: effectiveAuto)
            .font(.callout)
            .padding(.trailing, sendButtonWidth + 8)
        }
    }

    /// The keystroke that starts the chat and follows into it, mirrored in the floating header hint.
    private var startHint: String {
        switch submitMode {
        case .returnSends: "↵"
        case .modifierSends: "⌘↵"
        }
    }

    /// The keystroke that starts the chat but stays put (fires it off in the background). Always
    /// the ⌘⇧Return variant regardless of submit mode.
    private var backgroundHint: String { "⌘⇧↵" }

    private func loadBranches() async {
        guard let project = selectedProject else { branches = []; return }
        branches = await store.branches(in: project)
        if let current = selectedBranch, !branches.contains(current) { selectedBranch = nil }
    }

    /// Base branch a new chat would start from: the explicit pick, else the
    /// project's default branch.
    private var effectiveBranch: String {
        selectedBranch ?? selectedProject?.defaultBranch.value ?? "main"
    }

    /// The project a new chat would start in — anywhere in the mesh (mesh session sync): the
    /// last-picked project (persisted), resolved over the unified list so a peer Mac's project
    /// is pickable exactly like a local one. A remembered pick that can't be resolved (its
    /// peer is disconnected, or the project is gone) fails CLOSED to no selection rather than
    /// silently retargeting an unrelated project — a typed prompt must never start an agent
    /// in a repo it wasn't written for. Only a fresh composer (nothing remembered) defaults
    /// to the store's current/first local project.
    private var selectedSummary: ProjectSummary? {
        guard !lastProjectID.isEmpty else { return store.currentProject.map(ProjectSummary.init) }
        return store.sidebarProjects.first { $0.id.rawValue == lastProjectID }
    }

    /// Whether the selected project lives on a peer Mac. Gates the local-only affordances
    /// (base branch, worktree toggle, attachments, ship) — the chat itself starts on the
    /// owner over the mesh.
    private var isRemoteProject: Bool { selectedSummary?.isRemote == true }

    /// The full local record behind the selection, when it lives on this Mac — branch
    /// loading, attachments, and ship act on it. `nil` for a peer Mac's project.
    private var selectedProject: Project? {
        guard let summary = selectedSummary, !summary.isRemote else { return nil }
        return store.project(summary.id)
    }

    // Effective concrete values for the chat about to be started: the user's
    // override, or the app default (never shows "Default").
    private var effectiveModel: String { modelOverride ?? store.defaultModel ?? ModelCatalog.fallbackModel }
    // Clamp to what the chosen model supports, so a stored "max" can't survive onto a Codex model.
    private var effectiveEffort: String {
        let raw = ModelCatalog.clampedEffort(
            effortOverride ?? store.defaultEffort ?? ModelCatalog.fallbackEffort, for: effectiveModel)
        // Orchestra requires Nucleic Control; off-control (e.g. an override carried over when the
        // project picker changes) it falls back to its underlying level, so the menu never shows
        // it active — and a started chat never carries it — where it can't run.
        if ModelCatalog.isOrchestra(raw), !isControlProject {
            return OrchestrationMode.resolvedEffort(raw) ?? ModelCatalog.fallbackEffort
        }
        return raw
    }
    private var defaultModel: String { store.defaultModel ?? ModelCatalog.fallbackModel }
    private var defaultEffort: String { store.defaultEffort ?? ModelCatalog.fallbackEffort }

    /// Menu item: checkmark on the selected value, a "house" on the app default.
    @ViewBuilder
    private func choiceLabel(_ display: String, badge: String? = nil, isSelected: Bool, isDefault: Bool) -> some View {
        let title = Text(display)
            + (badge.map { Text(" \($0)").foregroundColor(.secondary) } ?? Text(""))
            + (isDefault ? Text("  (default)") : Text(""))
        if isSelected {
            Label { title } icon: { Image(systemName: "checkmark") }
        } else if isDefault {
            Label { title } icon: { Image(systemName: "house") }
        } else {
            title
        }
    }

    private var modelMenu: some View {
        Menu {
            // Group the SKUs by provider (Claude, GPT, Grok), set apart with a divider.
            ForEach(Array(ModelCatalog.modelGroups.enumerated()), id: \.offset) { index, group in
                if index > 0 { Divider() }
                ForEach(group, id: \.self) { sku in
                    Button { modelOverride = sku } label: {
                        choiceLabel(ModelCatalog.displayName(sku),
                                    badge: ModelCatalog.contextBadge(for: sku),
                                    isSelected: sku == effectiveModel, isDefault: sku == defaultModel)
                    }
                }
            }
        } label: {
            Text("Model: \(ModelCatalog.displayName(effectiveModel))")
                + (ModelCatalog.contextBadge(for: effectiveModel).map { Text(" \($0)").foregroundColor(.secondary) } ?? Text(""))
        }
        .menuStyle(.button)
        .fixedSize()
        .help("Model for the new chat")
    }

    /// Whether the chat about to start runs in orchestra (orchestration) mode.
    private var isOrchestra: Bool { ModelCatalog.isOrchestra(effectiveEffort) }

    /// Whether the selected project is under Nucleic Control — the gate for Orchestra and the
    /// cue for the composer's lavender accent. Resolved from the summary so a Control project
    /// on a peer Mac themes and gates identically to a local one (its owner applies the same
    /// control gate when the chat actually starts).
    private var isControlProject: Bool { selectedSummary?.isNucleicControlled == true }

    /// Whether the selected project is governed by nvrsion (shared trunk). When it is, the new
    /// chat lands on the shared trunk and merges by project-level promotion, so a per-chat Merge
    /// toggle is meaningless and hidden — mirrors the in-session composer.
    private var nvrsionActive: Bool { selectedProject?.nvrsionActive == true }

    /// The accent palette for the composer: lavender when the selected project is Nucleic
    /// Control (matching how that project's own session is themed), the app default (teal)
    /// otherwise. Recomputed as the project picker changes, so the accent tracks the selection.
    private var composerPalette: AppPalette {
        AppPalette.make(ColorVisionMode(rawValue: colorVisionRaw) ?? .standard, controlled: isControlProject)
    }

    private var effortMenu: some View {
        Menu {
            ForEach(ModelCatalog.efforts(for: effectiveModel), id: \.self) { level in
                Button { effortOverride = level } label: {
                    choiceLabel(ModelCatalog.effortDisplayName(level),
                                isSelected: level == effectiveEffort, isDefault: level == defaultEffort)
                }
            }
            // Orchestra sits below the API levels, set apart: it isn't an effort level but an
            // orchestration mode (xhigh + standing consent to fan out to subagents). It's a
            // Nucleic Control capability, so it's only selectable for a Control project — shown
            // but disabled (with the reason) otherwise so the requirement is discoverable.
            Divider()
            Button { effortOverride = ModelCatalog.orchestraEffort } label: {
                orchestraMenuItem(available: isControlProject)
            }
            .disabled(!isControlProject)
            .help(isControlProject ? ModelCatalog.orchestraBlurb : ModelCatalog.orchestraRequiresControlHelp)
        } label: {
            if isOrchestra {
                OrchestraEffortLabel()
            } else {
                Text("\(ModelCatalog.effortNoun(for: effectiveModel)): \(ModelCatalog.effortDisplayName(effectiveEffort))")
            }
        }
        .menuStyle(.button)
        .fixedSize()
        .help(isOrchestra ? ModelCatalog.orchestraBlurb : "Reasoning effort for the new chat")
    }

    /// The Orchestra row in an effort menu: a sparkles glyph (a checkmark when it's the selected
    /// mode), "Orchestra", and either the "(default)" suffix, or — when unavailable because the
    /// project isn't under Nucleic Control — a grayed "Requires Nucleic Control" note.
    @ViewBuilder
    private func orchestraMenuItem(available: Bool) -> some View {
        let selected = isOrchestra
        let isDefault = ModelCatalog.isOrchestra(defaultEffort)
        Label {
            Text("Orchestra")
                + (available
                    ? (isDefault ? Text("  (default)") : Text(""))
                    : Text("  — \(ModelCatalog.orchestraRequiresControlNote)").foregroundColor(.secondary))
        } icon: {
            Image(systemName: selected ? "checkmark" : OrchestraStyle.symbol)
        }
    }

    /// Auto-approve mode for the chat about to be started; defaults to the app-wide setting
    /// until the user toggles it, but is forced on for a Nucleic Control project — those chats
    /// run autonomously and Auto is locked on there. Mirrors the in-session button.
    private var effectiveAuto: Bool { isControlProject || (autoOverride ?? store.defaultAuto) }

    private var autoToggle: some View {
        // Auto is locked on for Nucleic Control projects (nvrsion can't function without it), so
        // disable the toggle there. Off-control it stays a normal toggle.
        let locked = isControlProject
        let lockedHelp = "Auto-approve is always on for Nucleic Control chats — they run autonomously."
        return Button {
            autoOverride = !effectiveAuto
        } label: {
            Label("Auto", systemImage: effectiveAuto ? "bolt.fill" : "bolt.slash")
                .foregroundStyle(effectiveAuto ? composerPalette.accent : Color.secondary)
        }
        .buttonStyle(.bordered)
        .fixedSize()
        .disabled(locked)
        .help(locked
            ? lockedHelp
            : (effectiveAuto
                ? "Auto-approve mode on: the new chat auto-approves safe actions; destructive ones still ask."
                : "Manual approvals: every gated tool in the new chat asks first."))
        // macOS suppresses .help on disabled controls, so overlay a transparent hit area to
        // carry the explanation when Auto is locked on under Nucleic Control.
        .overlay {
            if locked {
                Color.clear
                    .contentShape(Rectangle())
                    .help(lockedHelp)
            }
        }
    }

    /// Whether autoship can be enabled for the new chat: only Nucleic Control projects may
    /// autoship (the hardened, sandboxed path). Mirrors the in-session gate.
    private var shipAvailable: Bool { selectedProject?.isNucleicControlled == true }

    /// Autoship for the chat about to be started; defaults to the app-wide setting, but is
    /// clamped off for a non-Control project so the UI never promises a merge Core won't run.
    private var effectiveShip: Bool { (shipOverride ?? store.defaultAutoShip) && shipAvailable }

    private var shipToggle: some View {
        Button {
            guard shipAvailable else { return }
            shipOverride = !effectiveShip
        } label: {
            Label("Ship", systemImage: effectiveShip ? "shippingbox.fill" : "shippingbox")
                .foregroundStyle(effectiveShip ? composerPalette.accent : Color.secondary)
        }
        .buttonStyle(.bordered)
        .fixedSize()
        .disabled(!shipAvailable)
        .help(shipAvailable
            ? (effectiveShip
                ? "Autoship on: when the new chat's agent finishes, squash-merge its branch into the destination branch via the merge queue (keeps Auto on)."
                : "Autoship off: finished work waits for a manual merge.")
            : "Autoship requires Nucleic Control — clone or move this project under Nucleic Control to enable it.")
        // macOS suppresses .help tooltips on disabled controls, so overlay an enabled,
        // transparent hit area to carry the explanation when Merge is grayed out off-control.
        .overlay {
            if !shipAvailable {
                Color.clear
                    .contentShape(Rectangle())
                    .help("Autoship/automerge is only available for Nucleic Control projects — clone or move this project under Nucleic Control to enable it.")
            }
        }
    }

    private var branchMenu: some View {
        Menu {
            ForEach(branches, id: \.self) { name in
                Button { selectedBranch = name } label: {
                    choiceLabel(name, isSelected: name == effectiveBranch,
                                isDefault: name == selectedProject?.defaultBranch.value)
                }
            }
        } label: {
            // Mirror the project selector to its left (icon + name + chevron) so the
            // two read as the same control; a plain Text label rendered as a near-
            // invisible white field against the chat bar.
            HStack(spacing: 4) {
                Image(systemName: "arrow.triangle.branch")
                Text(effectiveBranch)
                Image(systemName: "chevron.down").font(.caption2)
            }
        }
        .menuStyle(.button)
        // See the project selector above: hide the built-in disclosure chevron so
        // the custom one in the label isn't doubled up.
        .menuIndicator(.hidden)
        .fixedSize()
        .disabled(branches.isEmpty)
        .help("Base branch the new chat starts from")
    }

    /// Whether the new chat can start: a typed prompt, or staged attachments on their own (an
    /// attachment-only first message is allowed — the references become its body). A remote
    /// project needs text: attachments can't ride the mesh start verb, so they must never be
    /// the thing that lights the send button.
    private var canStart: Bool {
        let hasText = !draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
        return isRemoteProject ? hasText : hasText || !attachments.isEmpty
    }

    /// Start the chat. `background` (⌘⇧Return, or the ⌘N button default) fires it off without
    /// navigating; the default (⌘Return) follows into the new session.
    private func start(background: Bool) {
        let trimmed = draft.trimmingCharacters(in: .whitespacesAndNewlines)
        // A project on a peer Mac (mesh session sync): start the chat on its owner over the
        // mesh — the same wire verb a phone's composer uses. The local-only affordances
        // (branch, worktree, attachments, ship) are hidden for a remote selection; the new
        // chat appears in the sidebar through the owner's broadcasts. Attachments staged
        // before the picker moved to a remote project stay staged (they can't ride the wire
        // and must not be silently thrown away) — the user is told, and they're still there
        // if the picker returns to a local project.
        if isRemoteProject {
            guard let summary = selectedSummary, !trimmed.isEmpty else { return }
            draft = ""
            if !attachments.isEmpty {
                store.lastError = "Attachments can't be sent to a project on another Mac yet — "
                    + "the chat started without them."
            }
            let text = SingularityPreparation.prepare(trimmed)
            let model = effectiveModel
            let effort = effectiveEffort
            let auto = effectiveAuto
            Task {
                await store.startRemoteChat(
                    in: summary, message: text, model: model, effort: effort, auto: auto)
            }
            if floating { dismiss() }
            return
        }
        guard let project = selectedProject, !trimmed.isEmpty || !attachments.isEmpty else { return }
        // Hand the staged attachments to the start Task and clear the composer immediately.
        let staged = attachments
        draft = ""
        attachments = []
        let text = SingularityPreparation.prepare(trimmed)
        let base = GitRef(effectiveBranch)
        let worktree = useWorktree
        let auto = effectiveAuto
        let ship = effectiveShip
        // The inline home bar always reveals the new chat; the floating panel follows into the new
        // session on ⌘Return and stays put (background) on ⌘⇧Return.
        let reveal = !floating || !background
        Task {
            // Read each attachment's bytes off the main thread — media can be large.
            let pending = await Task.detached { staged.compactMap { try? $0.pending() } }.value
            await store.startChat(
                in: project, message: text, model: effectiveModel, effort: effectiveEffort,
                base: base, useWorktree: worktree, auto: auto, autoShip: ship, attachments: pending,
                reveal: reveal)
        }
        // Close the floating panel once started. In reveal mode startChat opens the new chat so
        // it's revealed underneath; in background mode it stays on the current view.
        if floating { dismiss() }
    }
}

/// Floating ⌘⇧N presentation of the new-chat composer. Owns a transient draft (so a
/// quick-launch message never bleeds into the home bar's persisted draft) and hands
/// `NewChatComposer` the `floating` flag that adds its titled header and self-dismiss.
struct NewChatComposerSheet: View {
    @Environment(AppStore.self) private var store
    @Environment(\.dismiss) private var dismiss
    @State private var draft = ""

    var body: some View {
        NewChatComposer(draft: $draft, floating: true, background: store.newChatComposerStartsInBackground)
            // The panel keeps its own rounded card chrome; the sheet's own background is
            // cleared below so the window shows through the empty space around it.
            .background(.regularMaterial, in: .rect(cornerRadius: 16))
            // Fill the window so a click in the empty space around the panel lands on the dismiss
            // backdrop below — the click-outside-to-close feel of a popover, which a plain modal
            // sheet doesn't offer. The panel's own material intercepts its clicks, so only the
            // surrounding space dismisses. Center it (no top padding) — an added top inset renders
            // as a blank "forehead" band above the header once the sheet dims the window behind it.
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background {
                Rectangle().fill(.black.opacity(0.001))
                    .contentShape(Rectangle())
                    .onTapGesture { dismiss() }
            }
            // Drop the sheet's opaque material so the backdrop (and the window behind it) shows
            // around the floating panel instead of a full-window sheet fill.
            .presentationBackground(.clear)
    }
}
