import SwiftUI

// MARK: - The git-mode section's state

/// **What the popover's git section shows on a git-mode board** — a pure function of four facts, so
/// the surface 06-history-undo.md describes is assertable without a popover on screen.
///
/// It exists for the same reason `BoardGitSection.resolve` does one level up: the *posture* is the
/// part worth pinning, and the SwiftUI that renders it is not. Three rules live here —
///
/// - **A paused repository names its state and disables the controls** (06 ▸ Rules ▸ Abnormal repo
///   states: "Undo/Redo and the branch controls disable … the popover's git section names the state
///   plainly … and says resolving it belongs to the tool that created it").
/// - **A read-only board disables them too** (02-architecture.md ▸ The lock's scope, which names "the
///   popover's git controls" outright).
/// - **A switch in flight disables them**, so a second click cannot start a second checkout.
/// - **An unreadable repository reads as broken, never as still loading** (06 ▸ Rules, the
///   corrupt-`.git` loud failure, ruled 2026-07-31: "Never a silent placeholder discovered only in
///   the popover").
struct BoardGitBranchSurface: Equatable {

    /// What the branch line reads — the branch name, the short hash on a detached HEAD
    /// (`GitRepository.branchName` decides which), the placeholder while the first read is in
    /// flight, or `unavailableLabel` when there is no readable repository for it to name.
    let branchLabel: String

    /// Whether that label is the placeholder rather than an answer.
    let isReadingBranch: Bool

    /// The pause's own sentence (`GitRepositoryPause.explanation`), or `nil` when the surface is live.
    let pauseExplanation: String?

    /// **Whether the pause is the unopenable repository** — the one pause whose surface is not the
    /// pause note: nothing is in progress and no tool is coming to finish it, so the section says
    /// its own sentence instead (`unreadableNote`), and the branch line has no answer to wait for.
    let isRepositoryUnreadable: Bool

    /// Whether the branch controls accept a click — the popover's switch picker, and the settings
    /// sheet's create field (`BoardSettingsSheet`), which resolves this same surface so that a
    /// paused repository, a read-only board and a switch in flight close both by one rule.
    let controlsEnabled: Bool

    /// The line the branch display is read as by VoiceOver.
    ///
    /// The broken case is spelled out rather than left to fall through "Branch \(label)": the label
    /// is a *state* there, not a name, and "Branch Unavailable" would read as a branch somebody
    /// called Unavailable.
    var accessibilityLabel: String {
        if isRepositoryUnreadable { return "Branch unavailable" }
        return isReadingBranch ? "Reading branch" : "Branch \(branchLabel)"
    }

    static let placeholder = "…"

    /// **What the branch line reads when the repository will not open** — an answer, not a
    /// placeholder, which is the whole of the ruling's "fails loudly" at this one control: the
    /// placeholder means "still reading" and would go on meaning it forever here.
    static let unavailableLabel = "Unavailable"

    /// The second half of the paused sentence — 06's "says resolving it belongs to the tool that
    /// created it", said in the app's own voice and paired with the promise that makes it safe to
    /// wait: the app is not going to touch the repository behind the user's back.
    static let pauseCaption =
        "Finishing it belongs to the tool that started it; Lanework leaves the repository untouched."

    /// **The popover's own sentence for an unreadable repository** (06 ▸ Rules, the corrupt-`.git`
    /// loud failure, ruled 2026-07-31: "with the whole git surface paused … and the popover's git
    /// section naming the state").
    ///
    /// A sibling of the nested and unverifiable notes (`BoardInfoPopover`) and written in their
    /// register — one sentence, the state first and the consequence after — rather than the pause
    /// note's two lines, because both of *those* lines would be wrong here: nothing is "in
    /// progress", and there is no tool whose job it is to finish it. What it keeps from the pause
    /// note is the promise that matters most on a repository the app cannot read, in the ruling's
    /// own words.
    ///
    /// It is deliberately **not** the banner's sentence (`BannerCenter.repositoryUnreadableMessage`)
    /// re-used: the strip announces a condition to somebody who has not asked, and this answers
    /// somebody looking straight at the git section — the register the neighbouring notes set.
    static let unreadableNote =
        "Lanework can't read this board's git repository, so history is paused; the repository is left untouched."

    static func resolve(
        branch: String?,
        pause: GitRepositoryPause?,
        isSwitching: Bool,
        isWritable: Bool
    ) -> BoardGitBranchSurface {
        // Derived from the pause rather than passed in beside it: the pause *is* how this state is
        // carried everywhere else (`GitRepositoryPause.unreadable`, seeded by the detection-time
        // probe and refreshed by every later read), so a second parameter would be a second answer
        // to one question — and a caller could hold them apart.
        let unreadable = pause == .unreadable
        return BoardGitBranchSurface(
            branchLabel: unreadable ? unavailableLabel : (branch ?? placeholder),
            // Never "reading" on an unreadable repository: there is nothing in flight, and the line
            // the ruling forbids is exactly the one that says otherwise forever.
            isReadingBranch: !unreadable && branch == nil,
            pauseExplanation: pause?.explanation,
            isRepositoryUnreadable: unreadable,
            controlsEnabled: pause == nil && isWritable && !isSwitching
        )
    }
}

// MARK: - The git-mode section

/// **The popover's git section on a board that has a repository** (03-board-ui.md ▸ Board popover;
/// 06-history-undo.md ▸ Branch switching).
///
/// **The daily face, and only that** (the 2026-07-31 popover/sheet split): the branch display with
/// its **switch** picker, and the pause explanation when the surface is held. Branch *creation* and
/// the commit-identity fields left with the split — they are setup, and setup's home is the board
/// settings sheet (`BoardSettingsSheet`), which the section's Board Settings… row opens.
///
/// **Shaped for the half that is not here yet.** Remote tracking, Pull/Push and the status badges are
/// 07-sync-collab.md's own cards, and this section is arranged so they join as one more block under
/// the branch controls — nothing here is nested inside anything they would have to be pulled out of,
/// and nothing about the branch controls assumes there is no upstream to show beside them.
struct BoardGitControls: View {

    let git: HistoryStore

    /// The read-only lock's reach (02-architecture.md ▸ The lock's scope): a board that refuses writes
    /// refuses a checkout most of all — it rewrites the tree the lock exists to stop describing.
    let isEnabled: Bool

    private var surface: BoardGitBranchSurface {
        BoardGitBranchSurface.resolve(
            branch: git.branch,
            pause: git.committer?.pause,
            isSwitching: git.switcher?.isSwitching ?? false,
            isWritable: isEnabled
        )
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            branchRow

            // **The unreadable repository names itself in its own sentence** (06 ▸ Rules, the
            // corrupt-`.git` loud failure) — checked before the pause note because it *is* a pause,
            // and the pause note's second line ("finishing it belongs to the tool that started it")
            // would be advice about an operation nobody started.
            if surface.isRepositoryUnreadable {
                caption(BoardGitBranchSurface.unreadableNote, tone: .primary)
            } else if let explanation = surface.pauseExplanation {
                pauseNote(explanation)
            }

            if let failure = git.switcher?.lastFailure {
                caption(failure.message, tone: .red)
            }
        }
        // Every read the section needs, taken when it appears rather than held live: the popover is
        // built fresh on each open (`BoardInfoWidget`), and none of these is a fact the board's
        // watcher could deliver — `.git` is filtered out of the watch by design.
        .task {
            await git.refreshBranch()
            await git.committer?.refreshPause()
            await git.switcher?.refreshBranches()
        }
    }

    // MARK: The branch line

    /// The branch display and the switch, as one control: the line *is* the picker, which is what
    /// makes "branch/source display and switching" one affordance rather than a label with a button
    /// beside it.
    ///
    /// **Switch entries and nothing else** since the 2026-07-31 split — the "New Branch…" entry that
    /// used to close the menu is the settings sheet's standing Create field now. On a board with one
    /// branch the menu is therefore *empty*, and that is left honest rather than papered over: the
    /// label still reads the current branch and still disables with the rest of the git surface, so
    /// the line goes on saying the one thing it is here to say.
    private var branchRow: some View {
        HStack(spacing: 6) {
            Image(systemName: "arrow.triangle.branch")
                .imageScale(.small)
                .foregroundStyle(.secondary)

            Menu {
                ForEach(otherBranches, id: \.self) { name in
                    Button(name) {
                        Task { await git.switcher?.switchTo(name) }
                    }
                }
            } label: {
                Text(surface.branchLabel)
                    .font(.callout)
                    .foregroundStyle(surface.isReadingBranch ? .secondary : .primary)
            }
            .menuStyle(.borderlessButton)
            .fixedSize()
            .disabled(!surface.controlsEnabled)
            .accessibilityLabel(surface.accessibilityLabel)
            .accessibilityHint("Switch branches")

            if git.switcher?.isSwitching == true {
                ProgressView()
                    .controlSize(.small)
                    .accessibilityLabel("Switching branches")
            }
        }
    }

    /// Every local branch except the one already checked out — a picker offering the current branch
    /// would be offering a no-op, and the switch refuses one anyway.
    private var otherBranches: [String] {
        (git.switcher?.branches ?? []).filter { $0 != git.branch }
    }

    // MARK: The pause

    /// **The abnormal-state surface** (06 ▸ Rules ▸ Abnormal repo states) — deferred here from the
    /// auto-commit card, which built the hold this explains.
    ///
    /// Two sentences, both of them the design's: what the repository is doing, and whose job it is to
    /// finish. Never a Repair button — "the app never mutates repo state it didn't create".
    private func pauseNote(_ explanation: String) -> some View {
        VStack(alignment: .leading, spacing: 2) {
            Text(explanation)
                .font(.caption)
                .foregroundStyle(.primary)
                .fixedSize(horizontal: false, vertical: true)
            Text(BoardGitBranchSurface.pauseCaption)
                .font(.caption)
                .foregroundStyle(.secondary)
                .fixedSize(horizontal: false, vertical: true)
        }
        .accessibilityElement(children: .combine)
    }

    private func caption(_ text: String, tone: Color) -> some View {
        Text(text)
            .font(.caption)
            .foregroundStyle(tone)
            .fixedSize(horizontal: false, vertical: true)
    }
}
