import SwiftUI

@MainActor
public struct ClawdisChatView: View {
    public enum Style {
        case standard
        case onboarding
    }

    @State private var viewModel: ClawdisChatViewModel
    @State private var scrollerBottomID = UUID()
    @State private var showSessions = false
    @State private var hasPerformedInitialScroll = false
    private let showsSessionSwitcher: Bool
    private let style: Style

    private enum Layout {
        #if os(macOS)
        static let outerPadding: CGFloat = 6
        static let stackSpacing: CGFloat = 6
        static let messageSpacing: CGFloat = 6
        static let messageListPaddingTop: CGFloat = 2
        static let messageListPaddingBottom: CGFloat = 4
        static let messageListPaddingHorizontal: CGFloat = 6
        #else
        static let outerPadding: CGFloat = 6
        static let stackSpacing: CGFloat = 6
        static let messageSpacing: CGFloat = 12
        static let messageListPaddingTop: CGFloat = 4
        static let messageListPaddingBottom: CGFloat = 6
        static let messageListPaddingHorizontal: CGFloat = 8
        #endif
    }

    public init(
        viewModel: ClawdisChatViewModel,
        showsSessionSwitcher: Bool = false,
        style: Style = .standard)
    {
        self._viewModel = State(initialValue: viewModel)
        self.showsSessionSwitcher = showsSessionSwitcher
        self.style = style
    }

    public var body: some View {
        ZStack {
            ClawdisChatTheme.background
                .ignoresSafeArea()

            VStack(spacing: Layout.stackSpacing) {
                self.messageList
                ClawdisChatComposer(viewModel: self.viewModel, style: self.style)
            }
            .padding(.horizontal, Layout.outerPadding)
            .padding(.vertical, Layout.outerPadding)
            .frame(maxWidth: .infinity)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
        .onAppear { self.viewModel.load() }
        .sheet(isPresented: self.$showSessions) {
            if self.showsSessionSwitcher {
                ChatSessionsSheet(viewModel: self.viewModel)
            } else {
                EmptyView()
            }
        }
    }

    private var messageList: some View {
        ScrollViewReader { proxy in
            ZStack {
                ScrollView {
                    LazyVStack(spacing: Layout.messageSpacing) {
                        ForEach(self.visibleMessages) { msg in
                            ChatMessageBubble(message: msg, style: self.style)
                                .frame(
                                    maxWidth: .infinity,
                                    alignment: msg.role.lowercased() == "user" ? .trailing : .leading)
                        }

                        if self.viewModel.pendingRunCount > 0 {
                            HStack {
                                ChatTypingIndicatorBubble(style: self.style)
                                    .equatable()
                                Spacer(minLength: 0)
                            }
                        }

                        if !self.viewModel.pendingToolCalls.isEmpty {
                            ChatPendingToolsBubble(toolCalls: self.viewModel.pendingToolCalls)
                                .equatable()
                                .frame(maxWidth: .infinity, alignment: .leading)
                        }

                        if let text = self.viewModel.streamingAssistantText, !text.isEmpty {
                            ChatStreamingAssistantBubble(text: text)
                                .frame(maxWidth: .infinity, alignment: .leading)
                        }

                        Color.clear
                            .frame(height: 1)
                            .id(self.scrollerBottomID)
                    }
                    .padding(.top, Layout.messageListPaddingTop)
                    .padding(.bottom, Layout.messageListPaddingBottom)
                    .padding(.horizontal, Layout.messageListPaddingHorizontal)
                }

                if self.viewModel.isLoading {
                    ProgressView()
                        .controlSize(.large)
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                }
            }
            .onChange(of: self.viewModel.isLoading) { _, isLoading in
                guard !isLoading, !self.hasPerformedInitialScroll else { return }
                proxy.scrollTo(self.scrollerBottomID, anchor: .bottom)
                self.hasPerformedInitialScroll = true
            }
            .onChange(of: self.viewModel.messages.count) { _, _ in
                guard self.hasPerformedInitialScroll else { return }
                withAnimation(.snappy(duration: 0.22)) {
                    proxy.scrollTo(self.scrollerBottomID, anchor: .bottom)
                }
            }
            .onChange(of: self.viewModel.pendingRunCount) { _, _ in
                guard self.hasPerformedInitialScroll else { return }
                withAnimation(.snappy(duration: 0.22)) {
                    proxy.scrollTo(self.scrollerBottomID, anchor: .bottom)
                }
            }
        }
    }

    private var visibleMessages: [ClawdisChatMessage] {
        let base: [ClawdisChatMessage]
        if self.style == .onboarding {
            guard let first = self.viewModel.messages.first else { return [] }
            base = first.role.lowercased() == "user" ? Array(self.viewModel.messages.dropFirst()) : self.viewModel
                .messages
        } else {
            base = self.viewModel.messages
        }
        return self.mergeToolResults(in: base)
    }

    private func mergeToolResults(in messages: [ClawdisChatMessage]) -> [ClawdisChatMessage] {
        var result: [ClawdisChatMessage] = []
        result.reserveCapacity(messages.count)

        for message in messages {
            guard self.isToolResultMessage(message) else {
                result.append(message)
                continue
            }

            guard let toolCallId = message.toolCallId,
                  let last = result.last,
                  self.toolCallIds(in: last).contains(toolCallId)
            else {
                result.append(message)
                continue
            }

            let toolText = self.toolResultText(from: message)
            if toolText.isEmpty {
                continue
            }

            var content = last.content
            content.append(
                ClawdisChatMessageContent(
                    type: "tool_result",
                    text: toolText,
                    thinking: nil,
                    thinkingSignature: nil,
                    mimeType: nil,
                    fileName: nil,
                    content: nil,
                    id: toolCallId,
                    name: message.toolName,
                    arguments: nil))

            let merged = ClawdisChatMessage(
                id: last.id,
                role: last.role,
                content: content,
                timestamp: last.timestamp,
                toolCallId: last.toolCallId,
                toolName: last.toolName,
                usage: last.usage,
                stopReason: last.stopReason)
            result[result.count - 1] = merged
        }

        return result
    }

    private func isToolResultMessage(_ message: ClawdisChatMessage) -> Bool {
        let role = message.role.lowercased()
        return role == "toolresult" || role == "tool_result"
    }

    private func toolCallIds(in message: ClawdisChatMessage) -> Set<String> {
        var ids = Set<String>()
        for content in message.content {
            let kind = (content.type ?? "").lowercased()
            let isTool =
                ["toolcall", "tool_call", "tooluse", "tool_use"].contains(kind) ||
                (content.name != nil && content.arguments != nil)
            if isTool, let id = content.id {
                ids.insert(id)
            }
        }
        if let toolCallId = message.toolCallId {
            ids.insert(toolCallId)
        }
        return ids
    }

    private func toolResultText(from message: ClawdisChatMessage) -> String {
        let parts = message.content.compactMap { content -> String? in
            let kind = (content.type ?? "text").lowercased()
            guard kind == "text" || kind.isEmpty else { return nil }
            return content.text
        }
        return parts.joined(separator: "\n").trimmingCharacters(in: .whitespacesAndNewlines)
    }
}
