import SwiftUI
import AppKit
import NucleicCore

/// Register a git repository as a project. Two modes, keyed off the Git URL field:
/// empty → register a repo already on disk (pick its path); filled → clone the URL. The
/// "Nucleic Control" toggle (clone-only) forces the clone under `~/.nucleic/control/`,
/// disabling the path picker.
struct AddProjectSheet: View {
    @Environment(AppStore.self) private var store
    @Environment(\.dismiss) private var dismiss

    @State private var name = ""
    @State private var path = ""
    @State private var gitURL = ""
    @State private var defaultBranch = "main"
    // Seeded from the app-wide "Enable Nucleic Control on all projects" default (which is itself
    // gated on the container service). Only takes effect when cloning (`controlActive` gates on
    // `cloneMode`); the user can still flip it per clone.
    @State private var nucleicControl = ContainerServiceSettings.controlByDefault

    /// Nucleic Control requires the container service (the shared sandbox + git interceptor).
    @AppStorage(ContainerServiceSettings.serviceEnabledKey) private var containerServiceEnabled = false

    /// A git URL was entered → we clone instead of registering an on-disk path.
    private var cloneMode: Bool {
        !gitURL.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    }

    /// Control only applies to clones and requires the container service; it's force-off (and
    /// disabled) without a URL or with the service off.
    private var controlActive: Bool { cloneMode && nucleicControl && containerServiceEnabled }

    /// Preview of where a controlled clone lands, so the user sees the managed location.
    private var controlDestination: String {
        let folder = ProjectCloner.repoFolderName(fromGitURL: gitURL)
        return (ProjectCloner.controlBase as NSString).appendingPathComponent(folder)
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Add Project").font(.title2).bold()

            VStack(alignment: .leading, spacing: 4) {
                TextField("Git URL to clone (optional)", text: $gitURL)
                Text("Leave blank to register a repo already on disk. With a URL, Nucleic "
                    + "clones it for you.")
                    .font(.caption).foregroundStyle(.secondary)
            }

            Toggle(isOn: $nucleicControl) {
                VStack(alignment: .leading, spacing: 2) {
                    Text("Nucleic Control")
                    Text("Clone into ~/.nucleic/control/ — managed by Nucleic and kept out of "
                        + "iCloud, so it isn't edited or synced behind your back.")
                        .font(.caption).foregroundStyle(.secondary)
                }
            }
            .disabled(!cloneMode || !containerServiceEnabled)

            if cloneMode && !containerServiceEnabled {
                Text("Nucleic Control needs the container service — turn it on in "
                    + "Settings → Sandbox.")
                    .font(.caption).foregroundStyle(.secondary)
            }

            if controlActive {
                Text("Destination: \(controlDestination)")
                    .font(.caption).foregroundStyle(.secondary)
                    .lineLimit(1).truncationMode(.middle)
            } else {
                HStack {
                    TextField(cloneMode ? "Clone into folder" : "Repository path", text: $path)
                    Button("Choose…") { chooseFolder() }
                }
            }

            TextField("Name (optional when cloning)", text: $name)
            if !cloneMode {
                TextField("Default branch", text: $defaultBranch)
            }

            HStack {
                if store.cloning {
                    ProgressView().controlSize(.small)
                    Text("Cloning…").font(.caption).foregroundStyle(.secondary)
                }
                Spacer()
                Button("Cancel") { dismiss() }.disabled(store.cloning)
                Button(cloneMode ? "Clone" : "Add") { Task { await add() } }
                    .keyboardShortcut(.defaultAction)
                    .buttonStyle(.borderedProminent)
                    .disabled(!canSubmit)
            }
        }
        .padding(20)
        .frame(width: 460)
    }

    private var canSubmit: Bool {
        if store.cloning { return false }
        if cloneMode {
            // Name is derived from the URL when blank; a destination is only needed when
            // not under Nucleic Control.
            return controlActive || !path.isEmpty
        }
        return !name.isEmpty && !path.isEmpty && !defaultBranch.isEmpty
    }

    private func chooseFolder() {
        let panel = NSOpenPanel()
        panel.canChooseDirectories = true
        panel.canChooseFiles = false
        panel.allowsMultipleSelection = false
        if panel.runModal() == .OK, let url = panel.url {
            path = url.path
            if name.isEmpty && !cloneMode { name = url.lastPathComponent }
        }
    }

    private func add() async {
        if cloneMode {
            _ = await store.addClonedProject(
                name: name, gitURL: gitURL, nucleicControl: nucleicControl,
                destinationParent: controlActive ? nil : path)
        } else {
            _ = await store.addProject(name: name, rootPath: path, defaultBranch: GitRef(defaultBranch))
        }
        dismiss()
    }
}

/// Per-project settings — currently the execution sandbox. When enabled, the project's
/// sessions run `claude` inside an isolated Apple `container` (a Linux VM) with the
/// worktree bind-mounted, instead of directly on the host.
struct ProjectSettingsSheet: View {
    @Environment(AppStore.self) private var store
    @Environment(\.dismiss) private var dismiss

    let project: Project

    @State private var defaultBranch: String
    @State private var autoShipBranch: String
    @State private var sandboxEnabled: Bool
    @State private var image: String
    @State private var idleMinutes: Int
    @State private var allowHostExec: Bool
    // nvrsion (Beta) — shared-trunk versioning; control-only (NVRSION §10).
    @State private var nvrsionEnabled: Bool
    @State private var nvrsionKeepWarm: Int
    @State private var nvrsionPrelandHook: String
    @State private var nvrsionPromoting = false
    @State private var showMove = false
    /// Drives the "Convert to Nucleic Control" confirmation popup (the conversion is permanent —
    /// a Control project can't be released — so it always asks first).
    @State private var confirmingConvert = false

    /// App-wide gate: the per-project sandbox controls are only available when the container
    /// service is enabled in Settings → Sandbox.
    @AppStorage(ContainerServiceSettings.serviceEnabledKey) private var containerServiceEnabled = false

    /// Nucleic Control projects are sandboxed project-wide by default (in Nucleic's shared
    /// primary container), so the sandbox toggle is forced on and locked.
    private var controlled: Bool { project.isNucleicControlled }

    init(project: Project) {
        self.project = project
        _defaultBranch = State(initialValue: project.defaultBranch.value)
        _autoShipBranch = State(initialValue: project.autoShipBranch?.value ?? "")
        let sandbox = project.sandbox ?? ProjectSandbox()
        // A control project is always sandboxed (project-wide), regardless of any stored flag.
        _sandboxEnabled = State(initialValue: project.isNucleicControlled || sandbox.enabled)
        _image = State(initialValue: sandbox.image ?? "")
        _idleMinutes = State(initialValue: max(1, sandbox.idleTimeoutSeconds / 60))
        _allowHostExec = State(initialValue: sandbox.allowHostExec)
        let nvrsion = project.nvrsion ?? ProjectNvrsion()
        _nvrsionEnabled = State(initialValue: nvrsion.enabled)
        _nvrsionKeepWarm = State(initialValue: nvrsion.keepWarmIdleSeconds)
        _nvrsionPrelandHook = State(initialValue: nvrsion.prelandHook ?? "")
    }

    /// nvrsion is offered only on control projects using the shared container, with the service on.
    /// (Per-session containers aren't supported in v0; see NVRSION §15.)
    private var nvrsionAvailable: Bool {
        controlled && containerServiceEnabled && !(project.sandbox?.perSessionContainers ?? false)
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("\(project.name) Settings").font(.title2).bold()

            VStack(alignment: .leading, spacing: 10) {
                HStack {
                    Text("Default branch")
                    TextField("main", text: $defaultBranch)
                        .textFieldStyle(.roundedBorder)
                }
                Text("New sessions branch their worktree from this ref unless overridden "
                    + "when starting the session.")
                    .font(.caption).foregroundStyle(.secondary)

                Divider()

                HStack {
                    Text("Autoship branch")
                    TextField(defaultBranchPlaceholder, text: $autoShipBranch)
                        .textFieldStyle(.roundedBorder)
                }
                Text("Where autoship merges finished chats. Leave blank to use the project "
                    + "root repo (the default branch above). Individual chats can override this.")
                    .font(.caption).foregroundStyle(.secondary)
            }
            .padding(14)
            .background(AppTheme.surface, in: .rect(cornerRadius: 12))
            .overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(AppTheme.hairline, lineWidth: 1))

            VStack(alignment: .leading, spacing: 10) {
                Toggle(isOn: $sandboxEnabled) {
                    VStack(alignment: .leading, spacing: 2) {
                        Text("Run sessions in a sandbox container")
                        Text("Executes Claude inside an isolated Linux VM (Apple `container`) "
                            + "with this repo's worktree mounted. Requires Apple Silicon + the "
                            + "`container` tool installed.")
                            .font(.caption).foregroundStyle(.secondary)
                    }
                    .fixedSize(horizontal: false, vertical: true)
                }
                // Control projects are sandboxed project-wide; the toggle is forced on + locked.
                .disabled(!containerServiceEnabled || controlled)

                if controlled {
                    // Nucleic Control: sandboxed project-wide in Nucleic's shared primary
                    // container, plus a grayed-out placeholder for the future per-session option.
                    Text("This project is sandboxed by Nucleic Control — its sessions run in "
                        + "Nucleic's primary managed container. Still respects the container "
                        + "service in Settings → Sandbox (off ⇒ sessions run on the host).")
                        .font(.caption).foregroundStyle(.secondary)

                    Divider()
                    Toggle(isOn: .constant(project.sandbox?.perSessionContainers ?? false)) {
                        VStack(alignment: .leading, spacing: 2) {
                            Text("Per-session sandboxing")
                            Text("Give each session its own container instead of the shared "
                                + "primary one. Chosen at project creation; not yet available.")
                                .font(.caption).foregroundStyle(.secondary)
                        }
                        .fixedSize(horizontal: false, vertical: true)
                    }
                    .disabled(true)
                } else if !containerServiceEnabled {
                    Text("Turn on the container service in Settings → Sandbox to enable "
                        + "sandboxing for this project.")
                        .font(.caption).foregroundStyle(.secondary)
                }

                if sandboxEnabled && containerServiceEnabled {
                    Divider()
                    // The shared primary control container always uses the bundled default
                    // image (it serves many projects), so the per-project image override only
                    // applies to non-control (per-session) sandboxes.
                    if !controlled {
                        HStack {
                            Text("Image")
                            TextField(ProjectSandbox.defaultImage, text: $image)
                                .textFieldStyle(.roundedBorder)
                        }
                        Text("Leave blank to use the bundled default image (built on first use).")
                            .font(.caption).foregroundStyle(.secondary)
                    }
                    Stepper(
                        "Stop container after \(idleMinutes) min idle",
                        value: $idleMinutes, in: 1...240)

                    Divider()
                    Toggle(isOn: $allowHostExec) {
                        VStack(alignment: .leading, spacing: 2) {
                            Text("Allow host build/run")
                            Text("Lets the sandboxed agent request to build and run executables "
                                + "on the host machine (outside the container) — e.g. compiling "
                                + "and running a macOS binary. Every host command needs your "
                                + "explicit approval and is never auto-approved.")
                                .font(.caption).foregroundStyle(.secondary)
                        }
                        .fixedSize(horizontal: false, vertical: true)
                    }

                    Divider()
                    VStack(alignment: .leading, spacing: 2) {
                        Text("Authentication")
                        // Placeholder: the per-project auth picker isn't wired yet (the choice is
                        // app-wide for now). Hint at it so the project surface foreshadows it.
                        Text("Choosing Claude subscription vs. an API key for just this project is "
                            + "coming in a later version. For now, Nucleic Control sessions use the "
                            + "app-wide choice in Settings → Control.")
                            .font(.caption).foregroundStyle(.secondary)
                    }
                }
            }
            .padding(14)
            .background(AppTheme.surface, in: .rect(cornerRadius: 12))
            .overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(AppTheme.hairline, lineWidth: 1))

            if controlled {
                VStack(alignment: .leading, spacing: 10) {
                    Toggle(isOn: $nvrsionEnabled) {
                        VStack(alignment: .leading, spacing: 2) {
                            HStack(spacing: 6) {
                                Text("nvrsion")
                                Text("BETA")
                                    .font(.caption2).bold()
                                    .padding(.horizontal, 5).padding(.vertical, 1)
                                    .background(AppTheme.hairline, in: .capsule)
                            }
                            Text("nvrsion is a version-control system built for agentic workflows. "
                                + "It lets Nucleic Control operate orders of magnitude faster and "
                                + "integrates with your existing Git repos, but may complicate some "
                                + "workflows.")
                                .font(.caption).foregroundStyle(.secondary)
                        }
                        .fixedSize(horizontal: false, vertical: true)
                    }
                    .disabled(!nvrsionAvailable)

                    if !nvrsionAvailable {
                        Text(project.sandbox?.perSessionContainers ?? false
                            ? "Unavailable with per-session containers — nvrsion needs the shared "
                                + "primary container in this version."
                            : "Turn on the container service in Settings → Sandbox to use nvrsion.")
                            .font(.caption).foregroundStyle(.secondary)
                    }

                    if nvrsionEnabled && nvrsionAvailable {
                        Divider()
                        Stepper(
                            "Release an idle file after \(nvrsionKeepWarm)s",
                            value: $nvrsionKeepWarm, in: 1...60)
                        Text("Keep-warm window: a file you've stopped editing is handed to a "
                            + "waiting session after this long, even mid-chat. Lower = faster "
                            + "hand-off, more re-reads.")
                            .font(.caption).foregroundStyle(.secondary)

                        Divider()
                        HStack {
                            Text("Pre-land check")
                            TextField("optional command — e.g. nvrsion-check", text: $nvrsionPrelandHook)
                                .textFieldStyle(.roundedBorder)
                        }
                        Text("Runs on the edited files before each lands in trunk. Non-zero exit "
                            + "rejects that edit back to the agent. Leave blank to land immediately.")
                            .font(.caption).foregroundStyle(.secondary)

                        Divider()
                        // Block promotion while any chat in this project is mid-turn — promoting a
                        // half-written trunk out from under a running agent would land partial work.
                        let chatsRunning = store.isAnySessionRunning(in: project.id)
                        HStack(spacing: 8) {
                            Button {
                                Task {
                                    nvrsionPromoting = true
                                    await store.promoteNvrsionTrunk(project.id)
                                    nvrsionPromoting = false
                                }
                            } label: {
                                if nvrsionPromoting {
                                    ProgressView().controlSize(.small)
                                } else {
                                    Text("Promote trunk → \(defaultBranchPlaceholder)")
                                }
                            }
                            .disabled(nvrsionPromoting || chatsRunning)
                            Spacer()
                        }
                        Text(chatsRunning
                            ? "Finish or stop this project's running chats before promoting."
                            : "Squash everything currently on the trunk into your real branch as one "
                                + "commit. Trunk reaches your real branch only when you promote.")
                            .font(.caption).foregroundStyle(.secondary)
                    }
                }
                .padding(14)
                .background(AppTheme.surface, in: .rect(cornerRadius: 12))
                .overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(AppTheme.hairline, lineWidth: 1))
            }

            VStack(alignment: .leading, spacing: 8) {
                HStack(alignment: .firstTextBaseline) {
                    VStack(alignment: .leading, spacing: 2) {
                        HStack(spacing: 6) {
                            Text("Repository location")
                            if project.isNucleicControlled {
                                Label("Nucleic Control", systemImage: "lock.shield")
                                    .font(.caption2).labelStyle(.titleAndIcon)
                                    .padding(.horizontal, 6).padding(.vertical, 1)
                                    .background(AppTheme.hairline, in: .capsule)
                            }
                        }
                        Text(project.rootPath)
                            .font(.caption).foregroundStyle(.secondary)
                            .lineLimit(1).truncationMode(.middle)
                    }
                    Spacer()
                    // A Nucleic Control repo can't be moved or converted — both are permanent
                    // once a project is under Nucleic Control, so these only show off-control.
                    if !project.isNucleicControlled {
                        Button("Enable Nucleic Control…") { confirmingConvert = true }
                            .fixedSize()
                            .disabled(!containerServiceEnabled)
                        Button("Move…") { showMove = true }
                    }
                }
                Text(project.isNucleicControlled
                    ? "This repo is permanently managed by Nucleic under ~/.nucleic/control/ — "
                        + "outside iCloud and your own folders. A Nucleic Control project can't "
                        + "be moved or released."
                    : "Relocate this repo to a new folder — e.g. out of iCloud — or bring it "
                        + "permanently under Nucleic Control. Live chats pause and resume "
                        + "automatically; an original is removed only after the move is verified.")
                    .font(.caption).foregroundStyle(.secondary)
                if !project.isNucleicControlled && !containerServiceEnabled {
                    Text("Converting to Nucleic Control needs the container service — turn it on "
                        + "in Settings → Sandbox.")
                        .font(.caption).foregroundStyle(.secondary)
                }
            }
            .padding(14)
            .background(AppTheme.surface, in: .rect(cornerRadius: 12))
            .overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(AppTheme.hairline, lineWidth: 1))

            HStack {
                Spacer()
                Button("Cancel") { dismiss() }
                Button("Save") { Task { await save() } }
                    .keyboardShortcut(.defaultAction)
                    .buttonStyle(.borderedProminent)
            }
        }
        .padding(20)
        .frame(width: 480)
        .sheet(isPresented: $showMove) { MoveProjectSheet(project: project) }
        .confirmationDialog(
            "Convert “\(project.name)” to Nucleic Control?",
            isPresented: $confirmingConvert, titleVisibility: .visible
        ) {
            Button("Keep a Copy & Convert") {
                Task { await store.cloneProjectIntoControl(project.id) }
                dismiss()
            }
            Button("Move Repo In & Convert", role: .destructive) {
                Task { await store.convertProjectToControl(project.id) }
                dismiss()
            }
            Button("Cancel", role: .cancel) {}
        } message: {
            Text("This permanently brings the project under Nucleic Control — managed in "
                + "~/.nucleic/control/, out of iCloud, using the shared sandbox container, the "
                + "git interceptor, and autoship. It can't be undone: a Nucleic Control project "
                + "can't be released or moved back.\n\n"
                + "“Keep a Copy” clones it into Control and leaves your original folder exactly "
                + "where it is. “Move Repo In” relocates the whole repo (including uncommitted "
                + "work) and removes the original once the move is verified. Either way runs in "
                + "the background; the project is locked until it finishes.")
        }
    }

    /// Placeholder for the autoship-branch field: the default branch (resolved live from the
    /// field above), since a blank autoship branch means "ship to the project root repo".
    private var defaultBranchPlaceholder: String {
        let trimmed = defaultBranch.trimmingCharacters(in: .whitespacesAndNewlines)
        return trimmed.isEmpty ? project.defaultBranch.value : trimmed
    }

    private func save() async {
        var updated = project
        let branch = defaultBranch.trimmingCharacters(in: .whitespacesAndNewlines)
        if !branch.isEmpty {
            updated.defaultBranch = GitRef(branch)
        }
        // Blank, or the same as the default branch, means "use the project root repo" — store
        // nil so the resolution falls back to `defaultBranch` (and nested children to their parent).
        let ship = autoShipBranch.trimmingCharacters(in: .whitespacesAndNewlines)
        updated.autoShipBranch = (ship.isEmpty || ship == updated.defaultBranch.value) ? nil : GitRef(ship)
        let trimmed = image.trimmingCharacters(in: .whitespacesAndNewlines)
        updated.sandbox = ProjectSandbox(
            // A control project is sandboxed project-wide regardless, but persist `enabled` so
            // the stored model matches the locked-on UI; its image is ignored (the shared
            // primary container always uses the default), so don't persist a stray override.
            enabled: controlled || sandboxEnabled,
            image: (controlled || trimmed.isEmpty) ? nil : trimmed,
            idleTimeoutSeconds: idleMinutes * 60,
            allowHostExec: allowHostExec,
            // Per-session containers aren't user-settable yet; preserve any existing choice.
            perSessionContainers: project.sandbox?.perSessionContainers ?? false)
        // nvrsion (Beta) — only meaningful on control projects; store nil when off so the model
        // stays clean. Preserve the trunk-branch name from any existing config.
        let hook = nvrsionPrelandHook.trimmingCharacters(in: .whitespacesAndNewlines)
        if controlled && nvrsionEnabled {
            updated.nvrsion = ProjectNvrsion(
                enabled: true,
                trunkBranch: project.nvrsion?.trunkBranch ?? ProjectNvrsion.defaultTrunkBranch,
                prelandHook: hook.isEmpty ? nil : hook,
                keepWarmIdleSeconds: nvrsionKeepWarm)
        } else {
            updated.nvrsion = nil
        }
        await store.updateProject(updated)
        dismiss()
    }
}

/// Safe-move a project's repository to a new location (typically out of iCloud). Picks a
/// destination *parent* folder; the repo keeps its current folder name. Delegates the
/// stop-sessions → copy → repair → verify → rewrite → delete-source work to
/// `AppStore.moveProject`.
struct MoveProjectSheet: View {
    @Environment(AppStore.self) private var store
    @Environment(\.dismiss) private var dismiss

    let project: Project

    @State private var destParent = ""

    private var folderName: String { (project.rootPath as NSString).lastPathComponent }
    /// The typed destination with a leading `~` expanded to the home directory;
    /// `URL(fileURLWithPath:)` does not do this for us.
    private var expandedParent: String { (destParent as NSString).expandingTildeInPath }
    private var resolvedPath: String {
        destParent.isEmpty ? "" : (expandedParent as NSString).appendingPathComponent(folderName)
    }
    private var canMove: Bool { !destParent.isEmpty }

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Move Repository").font(.title2).bold()
            Text("Copies “\(project.name)” into the chosen folder, repairs its git "
                + "worktrees, updates Nucleic, then removes the original. Any running "
                + "chats are paused and become resumable. The move runs in the "
                + "background — you can keep using Nucleic; “\(project.name)” is locked "
                + "until it finishes.")
                .font(.caption).foregroundStyle(.secondary)
            HStack {
                TextField("Destination folder", text: $destParent)
                Button("Choose…") { chooseFolder() }
            }
            if !resolvedPath.isEmpty {
                Text("New location: \(resolvedPath)")
                    .font(.caption).foregroundStyle(.secondary)
                    .lineLimit(2).truncationMode(.middle)
            }
            HStack {
                Spacer()
                Button("Cancel") { dismiss() }
                Button("Move") { move() }
                    .keyboardShortcut(.defaultAction)
                    .buttonStyle(.borderedProminent)
                    .disabled(!canMove)
            }
        }
        .padding(20)
        .frame(width: 480)
    }

    private func chooseFolder() {
        let panel = NSOpenPanel()
        panel.canChooseDirectories = true
        panel.canChooseFiles = false
        panel.allowsMultipleSelection = false
        panel.prompt = "Choose Destination"
        if panel.runModal() == .OK, let url = panel.url { destParent = url.path }
    }

    /// Kick the move off in the background and close the sheet immediately — progress and
    /// the project lock are surfaced in the sidebar, so there's nothing to wait on here.
    private func move() {
        let parent = URL(fileURLWithPath: expandedParent)
        Task { await store.moveProject(project.id, toParent: parent) }
        dismiss()
    }
}

/// Convert a non-controlled project into a Nucleic Control repo, the direct entry point from
/// the project's context menu. Offers the two migration modes: **keep a copy** (clone into
/// `~/.nucleic/control/`, leaving the user's original in place — the default) or **move**
/// (relocate the tree into control and delete the original). Both run in the background and
/// surface their lock + progress on the sidebar; the sheet closes immediately.
struct ConvertToControlSheet: View {
    @Environment(AppStore.self) private var store
    @Environment(\.dismiss) private var dismiss

    let project: Project

    /// Keep the original working copy (clone into control) vs move the tree into control
    /// (destructive — removes the original). Defaults to the non-destructive keep-a-copy.
    @State private var keepOriginal = true

    /// Nucleic Control requires the container service (the shared sandbox + git interceptor).
    @AppStorage(ContainerServiceSettings.serviceEnabledKey) private var containerServiceEnabled = false

    private var resolvedPath: String {
        let folder = (project.rootPath as NSString).lastPathComponent
        return (ProjectCloner.controlBase as NSString).appendingPathComponent(folder)
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 14) {
            VStack(alignment: .leading, spacing: 4) {
                Text("Convert to Nucleic Control").font(.title2).bold()
                Text("Bring “\(project.name)” under Nucleic Control — managed in "
                    + "~/.nucleic/control/, kept out of iCloud and away from manual edits, so it "
                    + "can use the shared sandbox container, the git interceptor, and autoship.")
                    .font(.caption).foregroundStyle(.secondary)
            }

            Picker("Migration mode", selection: $keepOriginal) {
                Text("Keep my copy").tag(true)
                Text("Move it in").tag(false)
            }
            .pickerStyle(.segmented)
            .labelsHidden()

            Text(keepOriginal
                ? "Clones the repo into Nucleic Control and leaves your original folder exactly "
                    + "where it is. The managed copy starts from your committed history; "
                    + "uncommitted changes stay in your original. Best if you still edit this "
                    + "repo by hand."
                : "Relocates the whole repository — including any uncommitted work — into Nucleic "
                    + "Control, then removes the original once the move is verified. Live chats "
                    + "pause and resume automatically.")
                .font(.caption).foregroundStyle(.secondary)
                .frame(maxWidth: .infinity, alignment: .leading)

            Text("New location: \(resolvedPath)")
                .font(.caption).foregroundStyle(.secondary)
                .lineLimit(2).truncationMode(.middle)

            if !containerServiceEnabled {
                Text("Nucleic Control needs the container service — turn it on in "
                    + "Settings → Sandbox.")
                    .font(.caption).foregroundStyle(.orange)
            }

            HStack {
                Spacer()
                Button("Cancel") { dismiss() }
                Button("Convert") { convert() }
                    .keyboardShortcut(.defaultAction)
                    .buttonStyle(.borderedProminent)
                    .disabled(!containerServiceEnabled)
            }
        }
        .padding(20)
        .frame(width: 460)
    }

    /// Kick the chosen conversion off in the background and close immediately — progress and
    /// the project lock are surfaced in the sidebar, so there's nothing to wait on here.
    private func convert() {
        let id = project.id
        if keepOriginal {
            Task { await store.cloneProjectIntoControl(id) }
        } else {
            Task { await store.convertProjectToControl(id) }
        }
        dismiss()
    }
}
