import SwiftUI
import NucleicProtocol

/// Read-friendly rendering of one projected transcript row (UX_IOS §4) — the mobile echo of the
/// Mac transcript. Assistant/user prose as Markdown bubbles, tool calls as collapsible cards,
/// Orchestra spawns in gold, plus the informational rows (usage/cost, rate limits, turn
/// boundaries, run outcome) the older remote dropped.
struct TranscriptRow: View {
    let item: TranscriptItem

    var body: some View {
        switch item.kind {
        case .message(let role, let text):
            MessageBubble(role: role, text: text)
        case .thinking(let text):
            ThinkingRow(text: text)
        case .tool(let group):
            // A lone subagent spawn reads as its own gold subagent card (type, task, status, and
            // its nested activity on expand); a host_exec call reads as a structured host card
            // (purpose + command + breakdown); every other tool is a plain collapsible card.
            if group.isOrchestration {
                SubagentCard(group: group)
            } else if group.isHostExec {
                HostExecToolCard(group: group)
            } else {
                ToolCallCard(group: group)
            }
        case .toolBlock(let groups):
            // A run made entirely of subagent spawns is a multi-agent fan-out — the gold
            // orchestration card; any other run is one contiguous tool block.
            if groups.allSatisfy(\.isOrchestration) {
                OrchestrationCard(groups: groups)
            } else {
                ToolBlockCard(groups: groups)
            }
        case .sessionStarted(let model, let cwd):
            InfoLine(icon: "play.circle", text: "Session started · \(modelLabel(model))", detail: cwd)
        case .usage(let usage):
            UsageRow(usage: usage)
        case .rateLimit(let limit):
            RateLimitRow(limit: limit)
        case .turnBoundary:
            TurnBoundaryRow()
        case .approval(let toolName):
            Label("Approval requested: \(toolName)", systemImage: "exclamationmark.triangle.fill")
                .font(.caption).foregroundStyle(Palette.attention)
        case .runFinished(let outcome):
            RunFinishedRow(outcome: outcome)
        case .error(let message):
            Label(message, systemImage: "xmark.octagon.fill")
                .font(.caption).foregroundStyle(Palette.danger)
        case .note(let text, let icon, _):
            Label(text, systemImage: icon ?? "arrow.triangle.branch")
                .font(.caption).foregroundStyle(.secondary)
        case .raw(let type, let body):
            Text("[\(type)] \(body)").font(.caption2.monospaced()).foregroundStyle(.secondary).lineLimit(2)
        }
    }

    private func modelLabel(_ model: String) -> String { model.isEmpty ? "agent" : model }
}

/// Render an assistant/user message as Markdown (inline bold/italic/code/links), preserving the
/// line breaks of multi-paragraph replies. Falls back to plain text if parsing fails.
struct MessageBubble: View {
    let role: TranscriptItem.Role
    let text: String

    var body: some View {
        HStack {
            if role == .user { Spacer(minLength: 40) }
            Text(Self.markdown(text))
                .textSelection(.enabled)
                .padding(10)
                .background(role == .user ? Palette.accent.opacity(0.15) : Color(.secondarySystemBackground),
                            in: RoundedRectangle(cornerRadius: 12))
                .frame(maxWidth: .infinity, alignment: role == .user ? .trailing : .leading)
            if role == .assistant { Spacer(minLength: 40) }
        }
    }

    static func markdown(_ string: String) -> AttributedString {
        (try? AttributedString(markdown: string, options: .init(
            interpretedSyntax: .inlineOnlyPreservingWhitespace,
            failurePolicy: .returnPartiallyParsedIfPossible))) ?? AttributedString(string)
    }
}

struct ThinkingRow: View {
    let text: String
    var body: some View {
        Text(text)
            .font(.callout.italic())
            .foregroundStyle(.secondary)
            .frame(maxWidth: .infinity, alignment: .leading)
    }
}

/// A muted single-line note with an optional trailing detail (e.g. session-started cwd).
struct InfoLine: View {
    let icon: String
    let text: String
    var detail: String? = nil
    var body: some View {
        HStack(spacing: 6) {
            Image(systemName: icon)
            Text(text)
            if let detail { Text(detail).lineLimit(1).truncationMode(.middle).foregroundStyle(.tertiary) }
            Spacer(minLength: 0)
        }
        .font(.caption).foregroundStyle(.secondary)
    }
}

/// Per-turn token accounting + cost — the signal the desktop shows and the old remote dropped.
struct UsageRow: View {
    let usage: Usage
    var body: some View {
        HStack(spacing: 10) {
            if let input = usage.inputTokens { metric("arrow.down", tokens(input)) }
            if let output = usage.outputTokens { metric("arrow.up", tokens(output)) }
            if let cost = usage.costUSD, cost > 0 {
                Text(String(format: "$%.4f", cost)).foregroundStyle(.secondary)
            }
            Spacer(minLength: 0)
        }
        .font(.caption2.monospacedDigit()).foregroundStyle(.secondary)
    }
    private func metric(_ icon: String, _ value: String) -> some View {
        HStack(spacing: 2) { Image(systemName: icon); Text(value) }
    }
    private func tokens(_ n: Int) -> String {
        n >= 1000 ? String(format: "%.1fk", Double(n) / 1000) : "\(n)"
    }
}

/// A rate-limit warning so the user sees a window tightening before a turn fails — first-class,
/// not buried as a raw event.
struct RateLimitRow: View {
    let limit: RateLimit
    private var warn: Bool { (limit.status ?? "").contains("warning") || limit.status == "rejected" }
    var body: some View {
        Label(text, systemImage: warn ? "exclamationmark.circle.fill" : "clock.arrow.circlepath")
            .font(.caption).foregroundStyle(warn ? Palette.attention : .secondary)
            .frame(maxWidth: .infinity, alignment: .leading)
    }
    private var text: String {
        var parts = ["Rate limit"]
        if let type = limit.rateLimitType { parts.append(type.replacingOccurrences(of: "_", with: " ")) }
        if let status = limit.status { parts.append(status.replacingOccurrences(of: "_", with: " ")) }
        if let resets = limit.resetsAt {
            parts.append("· resets \(resets.formatted(date: .omitted, time: .shortened))")
        }
        return parts.joined(separator: " ")
    }
}

struct TurnBoundaryRow: View {
    var body: some View {
        HStack(spacing: 8) {
            Rectangle().fill(Color.primary.opacity(0.08)).frame(height: 1)
            Text("turn").font(.caption2).foregroundStyle(.tertiary)
            Rectangle().fill(Color.primary.opacity(0.08)).frame(height: 1)
        }
    }
}

struct RunFinishedRow: View {
    let outcome: RunFinished.Outcome
    var body: some View {
        Label(label, systemImage: icon).font(.caption).foregroundStyle(color)
    }
    private var label: String { "Run \(outcome.rawValue)" }
    private var icon: String {
        switch outcome {
        case .completed: "flag.checkered"
        case .interrupted: "stop.circle"
        case .errored: "xmark.octagon"
        case .maxTurns: "flag.checkered"
        }
    }
    private var color: Color {
        switch outcome {
        case .completed, .maxTurns: Palette.success
        case .interrupted: Palette.paused
        case .errored: Palette.danger
        }
    }
}

extension JSONValue {
    /// A one-line gist of a tool input/result for the collapsed row.
    var compactSummary: String {
        switch self {
        case .string(let s): return s
        case .object(let o):
            if let cmd = o["command"]?.stringValue { return cmd }
            if let path = o["file_path"]?.stringValue ?? o["path"]?.stringValue { return path }
            return o.keys.sorted().joined(separator: ", ")
        case .array(let a): return "[\(a.count) items]"
        case .number(let n): return String(n)
        case .bool(let b): return String(b)
        case .null: return "null"
        }
    }

    /// The full, **untruncated** content of a tool input for the approval card —
    /// the user must see exactly what they are granting before allowing. Unlike
    /// `compactSummary` (a one-line gist that drops values for complex objects) this
    /// returns the primary field verbatim, falling back to the pretty-printed input
    /// so nothing is hidden. The caller scrolls; it never clips.
    var approvalDetail: String {
        switch self {
        case .string(let s): return s
        case .object(let o):
            if let primary = o["command"]?.stringValue
                ?? o["file_path"]?.stringValue
                ?? o["path"]?.stringValue
                ?? o["notebook_path"]?.stringValue
                ?? o["url"]?.stringValue
                ?? o["query"]?.stringValue {
                return primary
            }
            return o.isEmpty ? "" : prettyString()
        case .null: return ""
        default: return prettyString()
        }
    }
}
