import SwiftUI
import NucleicCore

/// Renders a `mcp__nucleic__host_exec` approval — the agent asking to run a shell command on
/// the macOS **host**, OUTSIDE the sandbox container — as a structured card instead of the raw
/// `{"command": …}` JSON blob the generic detail box would show.
///
/// Two things matter when granting this gate, so the card surfaces both: the header names the
/// tool being called (a monospaced badge of the fully-qualified tool name) and flags that it
/// escapes the sandbox, and the command itself is shown in a pretty, bounded monospaced block —
/// wrapping to fit and scrolling only once it's genuinely tall — so the user can read exactly
/// what will run on their machine before allowing it.
struct HostExecCard: View {
    @Environment(\.appPalette) private var palette
    /// The shell command the agent wants to run on the host.
    let command: String
    /// The fully-qualified tool being invoked (e.g. `mcp__nucleic__host_exec`), shown verbatim
    /// so it's unambiguous which tool the approval gates.
    var toolName: String
    /// The card's accent — `attention` in a pending approval.
    var tint: Color? = nil

    /// Host execution always carries the attention accent: it's never the calm default path.
    private var accent: Color { tint ?? palette.attention }

    /// Past this height the command block scrolls instead of growing, so a long command can't
    /// push the Allow/Deny buttons off-screen. Mirrors `ApprovalDetailBox`.
    private let maxHeight: CGFloat = 220
    @State private var contentHeight: CGFloat = 0

    /// The deterministic, command-derived breakdown (program, actions, flags, inferred purpose),
    /// parsed from the command string alone — never from anything the agent claimed it would do.
    private var parsed: HostCommandSummary.Summary? { HostCommandSummary.summary(for: command) }

    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            header
            // What this looks like, laid out so the user can actually check it: the inferred
            // purpose and the program/actions/flags, then the exact command beneath as the
            // ground truth the breakdown is derived from.
            if let parsed {
                HostCommandBreakdown(summary: parsed, accent: accent, showPurpose: true)
                Text("Exact command")
                    .font(.caption2.weight(.semibold)).foregroundStyle(.secondary)
            }
            commandBlock
        }
        .padding(.horizontal, 12).padding(.vertical, 10)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(accent.opacity(0.06))
        .overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(accent.opacity(0.16), lineWidth: 1))
        .clipShape(RoundedRectangle(cornerRadius: 8))
    }

    /// Names what's being gated: a host-machine glyph, a plain-language line, and the
    /// fully-qualified tool name as a monospaced badge so the exact tool is never in doubt.
    private var header: some View {
        HStack(alignment: .firstTextBaseline, spacing: 9) {
            Image(systemName: "desktopcomputer")
                .font(.callout).foregroundStyle(accent).frame(width: 16)
            VStack(alignment: .leading, spacing: 4) {
                Text("Run on host machine")
                    .font(.callout.weight(.semibold))
                    .foregroundStyle(AppTheme.primaryText)
                HStack(spacing: 6) {
                    Text(toolName)
                        .font(.system(.caption2, design: .monospaced))
                        .foregroundStyle(accent)
                        .padding(.horizontal, 6).padding(.vertical, 2)
                        .background(accent.opacity(0.12), in: Capsule())
                        .textSelection(.enabled)
                    Text("outside the sandbox")
                        .font(.caption).foregroundStyle(.secondary)
                }
            }
            Spacer(minLength: 0)
        }
    }

    /// The command itself, pretty-printed in a bounded monospaced block. The text wraps and the
    /// box sizes to its content — a short command sits in a snug block — and only begins
    /// scrolling once the content exceeds `maxHeight`.
    private var commandBlock: some View {
        ScrollView(.vertical) {
            HStack(alignment: .firstTextBaseline, spacing: 8) {
                Text("$")
                    .font(.system(.callout, design: .monospaced))
                    .foregroundStyle(accent.opacity(0.7))
                Text(command)
                    .font(.system(.callout, design: .monospaced))
                    .foregroundStyle(AppTheme.primaryText)
                    .textSelection(.enabled)
                    .multilineTextAlignment(.leading)
                    .frame(maxWidth: .infinity, alignment: .leading)
            }
            .padding(10)
            .background(GeometryReader { geo in
                Color.clear.preference(key: HostExecHeightKey.self, value: geo.size.height)
            })
        }
        // Until the first measurement lands, show the content at full height so it never
        // flashes as a zero-height sliver.
        .frame(height: contentHeight == 0 ? nil : min(contentHeight, maxHeight))
        .onPreferenceChange(HostExecHeightKey.self) { contentHeight = $0 }
        .background(accent.opacity(0.05), in: .rect(cornerRadius: 6))
        .overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(accent.opacity(0.12), lineWidth: 1))
    }
}

private struct HostExecHeightKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = max(value, nextValue())
    }
}

// MARK: - In-chat host_exec card

/// Renders a `mcp__nucleic__host_exec` tool call *in the transcript* as a structured card: a
/// host-machine glyph, the shell command pretty-printed with a `$` prompt, and the command's
/// output revealed on tap. Replaces the generic `mcp__nucleic__host_exec {"command": …}` JSON
/// row the catch-all tool card would otherwise show, so a host call reads as cleanly as the
/// agent's other activity — just flagged, by the desktop glyph and "Host" tag, as running on
/// the macOS host *outside the sandbox*. `HostExecCard` is the approval-time prompt; this is its
/// settled, in-chat form, and it mirrors `ToolCallRow`'s collapse behavior so the two read alike.
struct HostExecToolCard: View {
    @Environment(\.appPalette) private var palette
    /// The shell command the agent ran on the host.
    let command: String
    /// The command's output, revealed on tap once it has run; nil while still running or empty.
    var output: String? = nil
    /// Whether the command has finished — a spinner shows in the header until it has.
    let finished: Bool
    /// Whether the result came back an error, which tints the output and its glyph `danger`.
    var isError: Bool = false

    /// In-chat host calls carry the calm transcript accent (the desktop glyph and "Host" tag do
    /// the distinguishing); the loud `attention` accent is reserved for the pending approval.
    private var accent: Color { palette.accent }
    @State private var expanded = false

    /// The deterministic, command-derived breakdown (program, actions, flags, inferred purpose) —
    /// parsed from the command string alone, so the collapsed line can lead with what the call
    /// *does* rather than the raw command. Recomputed cheaply from the command on each render.
    private var parsed: HostCommandSummary.Summary? { HostCommandSummary.summary(for: command) }

    /// Whether there's output to reveal.
    private var hasOutput: Bool { !(output ?? "").isEmpty }

    /// Whether the parse carries structure worth revealing (a working dir, more than one step, or
    /// any flags/args/env) — so a richer command is expandable even before it has output.
    private var hasBreakdown: Bool {
        guard let parsed else { return false }
        if parsed.workingDirectory != nil || parsed.invocations.count > 1 { return true }
        return parsed.invocations.contains {
            !$0.flags.isEmpty || !$0.arguments.isEmpty || !$0.env.isEmpty
        }
    }

    private var canExpand: Bool { hasOutput || hasBreakdown }

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            header
                .contentShape(Rectangle())
                .onTapGesture {
                    guard canExpand else { return }
                    withAnimation(.easeInOut(duration: 0.15)) { expanded.toggle() }
                }
            if expanded {
                if hasBreakdown, let parsed {
                    Divider().overlay(accent.opacity(0.14))
                    HostCommandBreakdown(summary: parsed, accent: accent, showPurpose: false)
                        .padding(.horizontal, 10).padding(.vertical, 8)
                }
                if let output, !output.isEmpty {
                    Divider().overlay(accent.opacity(0.14))
                    outputBody(output)
                }
            }
        }
        .background(accent.opacity(0.06))
        .overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(accent.opacity(0.14), lineWidth: 1))
        .clipShape(RoundedRectangle(cornerRadius: 8))
    }

    /// The host glyph, a "Host" tag, the inferred purpose (the easy-to-read headline), and the
    /// `$`-prefixed command beneath it as the literal — plus a running spinner and, when there's
    /// detail to reveal, a disclosure chevron.
    private var header: some View {
        HStack(alignment: .top, spacing: 8) {
            Image(systemName: "desktopcomputer")
                .font(.caption).foregroundStyle(accent).frame(width: 15)
                .padding(.top, 1)
            VStack(alignment: .leading, spacing: 3) {
                HStack(alignment: .firstTextBaseline, spacing: 6) {
                    Text("HOST")
                        .font(.caption2.weight(.bold)).foregroundStyle(accent)
                    if let parsed {
                        // The command-derived purpose leads as the bold headline — what the call
                        // looks like it does — rendered with its `code` spans; "HOST" is just a
                        // quiet tag beside it (the desktop glyph already marks it as a host call).
                        Text(TranscriptRow.inlineMarkdown(parsed.purpose))
                            .font(.callout.weight(.semibold)).foregroundStyle(AppTheme.primaryText)
                            .lineLimit(1).truncationMode(.tail)
                    } else {
                        Text("Host")
                            .font(.callout.weight(.semibold)).foregroundStyle(AppTheme.primaryText)
                    }
                }
                commandLine
            }
            Spacer(minLength: 0)
            if !finished {
                ProgressView().controlSize(.small).scaleEffect(0.7)
            }
            if canExpand {
                Image(systemName: "chevron.right")
                    .font(.caption2.weight(.semibold))
                    .foregroundStyle(.tertiary)
                    .rotationEffect(.degrees(expanded ? 90 : 0))
                    .padding(.top, 2)
            }
        }
        .padding(.horizontal, 10).padding(.vertical, 6)
        .animation(.easeInOut(duration: 0.15), value: expanded)
    }

    /// The literal command in a `$`-prefixed monospaced run — one line, truncated, when
    /// collapsed; wrapping in full once expanded — so the exact command stays visible beneath the
    /// human-readable purpose.
    private var commandLine: some View {
        HStack(alignment: .firstTextBaseline, spacing: 6) {
            Text("$")
                .font(.system(.caption, design: .monospaced))
                .foregroundStyle(accent.opacity(0.7))
            Text(command)
                .font(.system(.caption, design: .monospaced))
                .foregroundStyle(.secondary)
                // Expanded, the literal wraps but stays bounded — a long chain's command string
                // is itself long, and the compact step list above carries the structure.
                .lineLimit(expanded ? 6 : 1)
                .truncationMode(.middle)
                .textSelection(.enabled)
                .fixedSize(horizontal: false, vertical: expanded)
        }
    }

    /// The command's output, revealed on expand: a return glyph and the text in a monospaced
    /// block, tinted `danger` when the call errored. Mirrors `ToolCallRow`'s result body.
    private func outputBody(_ output: String) -> some View {
        HStack(alignment: .top, spacing: 8) {
            Image(systemName: isError ? "exclamationmark.triangle.fill" : "arrow.turn.down.right")
                .font(.caption2)
                .foregroundStyle(isError ? AnyShapeStyle(palette.danger) : AnyShapeStyle(.tertiary))
                .frame(width: 15)
            Text(output)
                .font(.system(.caption, design: .monospaced))
                .foregroundStyle(isError ? AnyShapeStyle(palette.danger) : AnyShapeStyle(.secondary))
                .textSelection(.enabled)
            Spacer(minLength: 0)
        }
        .padding(.horizontal, 10).padding(.vertical, 6)
    }
}

// MARK: - Command breakdown

/// The shared, deterministic breakdown of a `host_exec` command — driven entirely by
/// ``HostCommandSummary`` (parsed from the command string, never from the agent). It lays out
/// the inferred purpose (optionally), the working directory, and each program invocation as its
/// program + actions + flags + operands, so the user can verify *exactly* what's about to run.
/// Used by ``HostExecCard`` (the approval, with the purpose) and ``HostExecToolCard`` (the
/// in-chat call, where the purpose already heads the row so it's shown without it).
struct HostCommandBreakdown: View {
    @Environment(\.appPalette) private var palette
    let summary: HostCommandSummary.Summary
    /// The card's accent (the program/action chips and glyphs).
    let accent: Color
    /// Whether to render the prominent "Likely purpose" row and any `sudo`/destructive banner.
    /// The chat card heads its row with the purpose already, so it passes `false`.
    var showPurpose: Bool = true

    var body: some View {
        VStack(alignment: .leading, spacing: 9) {
            if showPurpose {
                purposeRow
                if summary.isElevated || summary.isDestructive { riskBanner }
            }
            if let dir = summary.workingDirectory {
                detailRow(icon: "folder", lines: ["in \(dir)"])
            }
            steps
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }

    /// At most this many steps render in the compact chain list before the rest fold into a
    /// "+N more" tail — so a long `cmd1 && cmd2 && …` pipeline can't grow the card unbounded
    /// (the exact command shown alongside is always the complete source of truth).
    private static let maxVisibleSteps = 7

    /// The invocations, rendered to fit the chain length: a lone command gets the full block
    /// (program + actions + every env/flag/operand spelled out for verification); a *chain*
    /// collapses to one compact line per command so a long pipeline stays short, not a tall
    /// stack of blocks.
    @ViewBuilder private var steps: some View {
        if summary.invocations.count <= 1 {
            if let only = summary.invocations.first { invocationView(only) }
        } else {
            VStack(alignment: .leading, spacing: 6) {
                ForEach(Array(summary.invocations.prefix(Self.maxVisibleSteps))) { inv in
                    stepRow(inv)
                }
                let hidden = summary.invocations.count - Self.maxVisibleSteps
                if hidden > 0 {
                    Text("+\(hidden) more step\(hidden == 1 ? "" : "s")")
                        .font(.caption2).foregroundStyle(.tertiary)
                        .padding(.leading, 22)
                }
            }
        }
    }

    /// One command in a chain, compacted to a single line: a step glyph, the program + actions
    /// emphasized, then a truncated tail of its env/flags/operands — so each command reads at a
    /// glance without the multi-line block. A destructive/elevated step is tinted and glyphed in
    /// the danger color so it still stands out in the list.
    private func stepRow(_ inv: HostCommandSummary.Invocation) -> some View {
        let risky = inv.destructive || inv.elevated
        let tail = stepTail(inv)
        return HStack(alignment: .firstTextBaseline, spacing: 8) {
            Image(systemName: stepGlyph(inv))
                .font(.caption2).foregroundStyle(risky ? palette.danger : accent).frame(width: 14)
            (Text(inv.headline)
                .font(.system(.caption, design: .monospaced).weight(.semibold))
                .foregroundColor(AppTheme.primaryText)
             + Text(tail.isEmpty ? "" : " " + tail)
                .font(.system(.caption, design: .monospaced))
                .foregroundColor(.secondary))
                .lineLimit(1).truncationMode(.middle)
                .textSelection(.enabled)
            Spacer(minLength: 0)
        }
    }

    /// The trailing detail of a compacted step — its env, flags, and operands joined on one line,
    /// truncated by the row. The literal command block carries the untruncated form.
    private func stepTail(_ inv: HostCommandSummary.Invocation) -> String {
        (inv.env.map(\.display) + inv.flags.map(\.display) + inv.arguments).joined(separator: " ")
    }

    /// The leading glyph for a compacted step: a delete/elevation marker takes priority so the
    /// risky step is unmistakable; otherwise a plain terminal glyph.
    private func stepGlyph(_ inv: HostCommandSummary.Invocation) -> String {
        if inv.destructive { return "trash" }
        if inv.elevated { return "lock.shield" }
        return "terminal"
    }

    /// The deterministic, command-derived purpose — the card's focal point, set in a prominent
    /// accent banner so the user reads *what this does* first, with a quiet caption clarifying
    /// it's *Nucleic's* reading of the command, not the agent's claim about it.
    private var purposeRow: some View {
        HStack(alignment: .top, spacing: 10) {
            Image(systemName: "sparkles")
                .font(.title3).foregroundStyle(accent).frame(width: 22)
            VStack(alignment: .leading, spacing: 3) {
                Text(TranscriptRow.inlineMarkdown(summary.purpose))
                    .font(.title3.weight(.semibold))
                    .foregroundStyle(AppTheme.primaryText)
                    .fixedSize(horizontal: false, vertical: true)
                    .textSelection(.enabled)
                Text("Likely purpose · inferred from the command")
                    .font(.caption2.weight(.medium)).foregroundStyle(.secondary)
            }
            Spacer(minLength: 0)
        }
        .padding(.horizontal, 11).padding(.vertical, 9)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(accent.opacity(0.12), in: .rect(cornerRadius: 8))
        .overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(accent.opacity(0.22), lineWidth: 1))
        .help("Inferred by Nucleic from the command itself — not from anything the agent said.")
    }

    /// A red banner for the two things a host command most needs flagged before "Allow": running
    /// as root (`sudo`) and deleting files (`rm`).
    private var riskBanner: some View {
        let icon = summary.isElevated ? "lock.shield" : "trash"
        let text = summary.isElevated
            ? "Runs with elevated privileges (sudo)."
            : "Deletes files on the host."
        return HStack(alignment: .firstTextBaseline, spacing: 7) {
            Image(systemName: icon).font(.caption).foregroundStyle(palette.danger).frame(width: 16)
            Text(text).font(.caption.weight(.medium)).foregroundStyle(palette.danger)
            Spacer(minLength: 0)
        }
        .padding(.horizontal, 8).padding(.vertical, 5)
        .background(palette.danger.opacity(0.08), in: .rect(cornerRadius: 6))
    }

    /// One program invocation: the program name as a filled badge, its actions as outlined chips,
    /// and — beneath — its env, flags, and operands each as a labeled, monospaced list so every
    /// part of the command is individually legible.
    private func invocationView(_ inv: HostCommandSummary.Invocation) -> some View {
        VStack(alignment: .leading, spacing: 5) {
            HStack(spacing: 6) {
                if inv.elevated { chip("sudo", color: palette.danger, filled: true) }
                chip(inv.program, color: accent, filled: true)
                ForEach(inv.actions, id: \.self) { chip($0, color: accent, filled: false) }
                if inv.destructive { chip("deletes", color: palette.danger, filled: false) }
                Spacer(minLength: 0)
            }
            if !inv.env.isEmpty {
                detailRow(icon: "leaf", lines: inv.env.map(\.display))
            }
            if !inv.flags.isEmpty {
                detailRow(icon: "slider.horizontal.3", lines: inv.flags.map(\.display))
            }
            if !inv.arguments.isEmpty {
                detailRow(icon: "chevron.right", lines: [inv.arguments.joined(separator: " ")])
            }
        }
    }

    /// A small chip — a filled program badge or an outlined action/marker pill.
    private func chip(_ text: String, color: Color, filled: Bool) -> some View {
        Text(text)
            .font(.system(.caption2, design: .monospaced).weight(.medium))
            .foregroundStyle(color)
            .padding(.horizontal, 6).padding(.vertical, 2)
            .background(color.opacity(filled ? 0.16 : 0), in: Capsule())
            .overlay(Capsule().strokeBorder(color.opacity(filled ? 0 : 0.4), lineWidth: 0.75))
    }

    /// A labeled detail block: a small tertiary glyph and a monospaced list (env vars, one flag
    /// per line, or the operands) — the granular pieces the user scans to verify the command.
    private func detailRow(icon: String, lines: [String]) -> some View {
        HStack(alignment: .top, spacing: 7) {
            Image(systemName: icon).font(.caption2).foregroundStyle(.tertiary)
                .frame(width: 14).padding(.top, 1)
            VStack(alignment: .leading, spacing: 2) {
                ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
                    Text(line)
                        .font(.system(.caption, design: .monospaced))
                        .foregroundStyle(.secondary)
                        .textSelection(.enabled)
                        .fixedSize(horizontal: false, vertical: true)
                        .frame(maxWidth: .infinity, alignment: .leading)
                }
            }
        }
    }
}

// MARK: - Host-command conflict (HOST_EXEC §concurrency)

/// View-model for the host-command conflict override prompt: the command this session is about to
/// run on the shared host, plus the other session(s) already running something the fuzzy detector
/// judged *similar* (the simultaneous-builds case). Built from the approval's `host_command_conflict`
/// payload in `ApprovalBar`.
struct HostCommandConflictInfo: Equatable {
    struct Other: Equatable, Identifiable {
        /// The conflicting session's title — so the user knows exactly *which* session clashes.
        let session: String
        /// The command that session is currently running.
        let command: String
        /// Why the two are considered similar (e.g. "both are swift build/compile commands").
        let reason: String
        var id: String { session + "\u{1}" + command }
    }
    /// The command this session wants to run on the host.
    let command: String
    /// The already-running command(s) it would collide with — never empty when this is shown.
    let others: [Other]
}

/// Renders the host-command conflict override prompt as a structured card: it names *which* session
/// is already running a similar command, shows both commands, and explains why running them at once
/// could collide — so the user can either let the other finish ("Cancel") or, if it's a false
/// positive, proceed ("Run anyway"). The buttons live in `ApprovalBar`; this card is the rationale.
struct HostCommandConflictCard: View {
    @Environment(\.appPalette) private var palette
    let info: HostCommandConflictInfo
    /// The card's accent — `attention` in a pending approval.
    var tint: Color? = nil

    private var accent: Color { tint ?? palette.attention }

    var body: some View {
        VStack(alignment: .leading, spacing: 10) {
            header
            // What this session wants to run.
            labelledCommand("This session wants to run", info.command)
            // Each session it would collide with: who, why, and what they're running.
            ForEach(info.others) { other in
                VStack(alignment: .leading, spacing: 6) {
                    HStack(alignment: .firstTextBaseline, spacing: 6) {
                        Image(systemName: "person.fill.questionmark")
                            .font(.caption).foregroundStyle(accent).frame(width: 14)
                        Text(other.session)
                            .font(.callout.weight(.semibold))
                            .foregroundStyle(AppTheme.primaryText)
                            .textSelection(.enabled)
                        Text("is already running")
                            .font(.caption).foregroundStyle(.secondary)
                    }
                    if !other.command.isEmpty { commandBlock(other.command) }
                    if !other.reason.isEmpty {
                        Text(other.reason)
                            .font(.caption).foregroundStyle(.secondary)
                            .fixedSize(horizontal: false, vertical: true)
                    }
                }
                .padding(.top, 2)
            }
            Text("Running both on the host at once can collide — e.g. two builds contending for the "
                + "toolchain, or sharing one trunk's build directory. Run anyway if this isn't a real "
                + "conflict.")
                .font(.caption).foregroundStyle(.secondary)
                .fixedSize(horizontal: false, vertical: true)
        }
        .padding(.horizontal, 12).padding(.vertical, 10)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(accent.opacity(0.06))
        .overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(accent.opacity(0.16), lineWidth: 1))
        .clipShape(RoundedRectangle(cornerRadius: 8))
    }

    private var header: some View {
        HStack(alignment: .firstTextBaseline, spacing: 9) {
            Image(systemName: "exclamationmark.arrow.triangle.2.circlepath")
                .font(.callout).foregroundStyle(accent).frame(width: 16)
            VStack(alignment: .leading, spacing: 2) {
                Text("Possible host-command conflict")
                    .font(.callout.weight(.semibold))
                    .foregroundStyle(AppTheme.primaryText)
                Text("another session is running a similar command")
                    .font(.caption).foregroundStyle(.secondary)
            }
            Spacer(minLength: 0)
        }
    }

    /// A captioned command line — `label` above a monospaced command block.
    private func labelledCommand(_ label: String, _ command: String) -> some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(label).font(.caption).foregroundStyle(.secondary)
            commandBlock(command)
        }
    }

    /// A single command in a snug monospaced block with a `$` prompt — mirrors `HostExecCard`.
    private func commandBlock(_ command: String) -> some View {
        HStack(alignment: .firstTextBaseline, spacing: 8) {
            Text("$")
                .font(.system(.caption, design: .monospaced))
                .foregroundStyle(accent.opacity(0.7))
            Text(command)
                .font(.system(.caption, design: .monospaced))
                .foregroundStyle(AppTheme.primaryText)
                .textSelection(.enabled)
                .multilineTextAlignment(.leading)
                .frame(maxWidth: .infinity, alignment: .leading)
        }
        .padding(8)
        .background(accent.opacity(0.05), in: .rect(cornerRadius: 6))
        .overlay(RoundedRectangle(cornerRadius: 6).strokeBorder(accent.opacity(0.12), lineWidth: 1))
    }
}
