//
//  MessageRow.swift
//  Confab
//
//  Individual message display
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of Confab.
//
// Confab is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling Confab or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://confab.no>.


import SwiftUI
#if canImport(AppKit)
import AppKit
#endif

struct MessageRow: View {
    let message: Message
    let viewModel: ChatViewModel?
    private let settings = SettingsService.shared

    @State private var isExpanded = false
    @State private var isThinkingExpanded = true  // auto-expand while streaming, collapse after

    #if os(macOS)
    @State private var isHovering = false
    @State private var showCopied = false
    @State private var isStarred = false
    #endif

    init(message: Message, viewModel: ChatViewModel? = nil) {
        self.message = message
        self.viewModel = viewModel
    }

    var body: some View {
        // Compact layout for system messages (tool calls)
        if message.role == .system && !isErrorMessage {
            compactSystemMessage
        } else {
            standardMessageLayout
        }
    }

    @ViewBuilder
    private var standardMessageLayout: some View {
        HStack(alignment: .top, spacing: 12) {
            // Role icon
            roleIcon
                .frame(square: 32)

            VStack(alignment: .leading, spacing: 8) {
                // Header
                HStack {
                    Text(message.role.displayName)
                        .font(.headline)
                        .foregroundColor(Color.messageColor(for: message.role))

                    Spacer()

                    #if os(macOS)
                    // Star button (user/assistant messages only, visible on hover)
                    if (message.role == .user || message.role == .assistant) && isHovering {
                        Button(action: toggleStar) {
                            Image(systemName: isStarred ? "star.fill" : "star")
                                .font(.system(size: 11))
                                .foregroundColor(isStarred ? .yellow : .confabSecondary)
                        }
                        .buttonStyle(.plain)
                        .transition(.opacity)
                        .help("Star this message to always include it in context")
                    }

                    // Copy button (user + assistant messages, visible on hover)
                    if (message.role == .assistant || message.role == .user) && isHovering && !message.content.isEmpty {
                        Button(action: copyContent) {
                            HStack(spacing: 3) {
                                Image(systemName: showCopied ? "checkmark" : "doc.on.doc")
                                    .font(.system(size: 11))
                                if showCopied {
                                    Text("Copied!")
                                        .font(.system(size: 11))
                                }
                            }
                            .foregroundColor(showCopied ? .green : .confabSecondary)
                        }
                        .buttonStyle(.plain)
                        .transition(.opacity)
                    }
                    #endif

                    Text(message.timestamp, style: .time)
                        .font(.caption2)
                        .foregroundColor(.confabSecondary)
                }

                // Thinking / reasoning block (collapsible)
                if let thinking = message.thinkingContent, !thinking.isEmpty {
                    thinkingBlock(thinking)
                        .onChange(of: message.content) { _, newContent in
                            // Auto-collapse when response content starts arriving
                            if !newContent.isEmpty && isThinkingExpanded && !message.isStreaming {
                                withAnimation(.easeInOut(duration: 0.2)) {
                                    isThinkingExpanded = false
                                }
                            }
                        }
                        .onChange(of: message.isStreaming) { _, streaming in
                            // Collapse when streaming finishes
                            if !streaming && !message.content.isEmpty {
                                withAnimation(.easeInOut(duration: 0.3)) {
                                    isThinkingExpanded = false
                                }
                            }
                        }
                }

                // Content
                if !message.content.isEmpty {
                    messageContent
                }

                // Generated images
                if let images = message.generatedImages, !images.isEmpty {
                    GeneratedImagesView(images: images)
                }

                // File attachments
                if let attachments = message.attachments, !attachments.isEmpty {
                    VStack(alignment: .leading, spacing: 4) {
                        ForEach(attachments.indices, id: \.self) { index in
                            HStack(spacing: 6) {
                                Image(systemName: "paperclip")
                                    .font(.caption)
                                Text(attachments[index].path)
                                    .font(.caption)
                                    .foregroundColor(.confabSecondary)
                            }
                        }
                    }
                    .padding(.top, 4)
                }

                // Token/cost/time info
                if message.role == .assistant && (message.tokens != nil || message.cost != nil || message.responseTime != nil || message.wasInterrupted) {
                    HStack(spacing: 8) {
                        if let tokens = message.tokens {
                            Label("\(tokens)", systemImage: "chart.bar.xaxis")
                            if message.cost != nil || message.responseTime != nil || message.wasInterrupted {
                                Text("\u{2022}")
                            }
                        }
                        if let cost = message.cost {
                            Text(String(format: "$%.4f", cost))
                            if message.responseTime != nil || message.wasInterrupted {
                                Text("\u{2022}")
                            }
                        }
                        if let responseTime = message.responseTime {
                            Text(String(format: "%.1fs", responseTime))
                            if message.wasInterrupted {
                                Text("\u{2022}")
                            }
                        }
                        if message.wasInterrupted {
                            Text("⚠️ interrupted")
                                .foregroundColor(.orange)
                        }
                    }
                    .font(.caption2)
                    .foregroundColor(.confabSecondary)
                }
            }
            .frame(maxWidth: .infinity, alignment: .leading)
        }
        .padding(16)
        .background(Color.messageBackground(for: message.role))
        .cornerRadius(8)
        .overlay(
            RoundedRectangle(cornerRadius: 8)
                .strokeBorder(messageBorderColor, lineWidth: isErrorMessage ? 2 : 2)
        )
        #if os(macOS)
        .onHover { hovering in
            withAnimation(.easeInOut(duration: 0.15)) {
                isHovering = hovering
            }
        }
        .onAppear {
            loadStarredState()
        }
        #endif
    }

    // Close standardMessageLayout - the above closing braces close it
    // The body: some View now handles the split between compact and standard

    // MARK: - Thinking Block

    @ViewBuilder
    private func thinkingBlock(_ thinking: String) -> some View {
        VStack(alignment: .leading, spacing: 0) {
            // Header button
            Button(action: {
                withAnimation(.easeInOut(duration: 0.18)) { isThinkingExpanded.toggle() }
            }) {
                HStack(spacing: 6) {
                    if message.isStreaming && message.content.isEmpty {
                        ProgressView()
                            .scaleEffect(0.5)
                            .frame(width: 12, height: 12)
                        Text("Thinking…")
                            .font(.system(size: 11))
                            .foregroundStyle(.secondary)
                    } else {
                        Image(systemName: "brain")
                            .font(.system(size: 11))
                            .foregroundStyle(.secondary)
                        Text("Reasoning")
                            .font(.system(size: 11))
                            .foregroundStyle(.secondary)
                    }
                    Spacer()
                    Image(systemName: isThinkingExpanded ? "chevron.up" : "chevron.down")
                        .font(.system(size: 9, weight: .medium))
                        .foregroundStyle(.secondary.opacity(0.5))
                }
                .padding(.horizontal, 10)
                .padding(.vertical, 6)
                .contentShape(Rectangle())
            }
            .buttonStyle(.plain)

            if isThinkingExpanded {
                Divider()
                    .padding(.horizontal, 6)
                ScrollView {
                    Text(thinking)
                        .font(.system(size: 12))
                        .foregroundStyle(.secondary)
                        .textSelection(.enabled)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .padding(10)
                }
                .frame(maxHeight: 220)
            }
        }
        .background(Color.secondary.opacity(0.07))
        .cornerRadius(6)
        .overlay(
            RoundedRectangle(cornerRadius: 6)
                .strokeBorder(Color.secondary.opacity(0.15), lineWidth: 1)
        )
    }

    // MARK: - Compact System Message

    @ViewBuilder
    private var compactSystemMessage: some View {
        let expandable = message.toolCalls != nil
        VStack(alignment: .leading, spacing: 0) {
            Button(action: {
                if expandable {
                    withAnimation(.easeInOut(duration: 0.18)) { isExpanded.toggle() }
                }
            }) {
                HStack(spacing: 8) {
                    Image(systemName: "wrench.and.screwdriver")
                        .font(.system(size: 11))
                        .foregroundColor(.secondary)

                    Text(message.content)
                        .font(.system(size: 11))
                        .foregroundColor(.secondary)

                    Spacer()

                    if expandable {
                        Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
                            .font(.system(size: 9, weight: .medium))
                            .foregroundColor(.secondary.opacity(0.5))
                    }

                    Text(message.timestamp, style: .time)
                        .font(.system(size: 10))
                        .foregroundColor(.secondary.opacity(0.7))
                }
                .padding(.horizontal, 12)
                .padding(.vertical, 6)
                .contentShape(Rectangle())
            }
            .buttonStyle(.plain)

            if isExpanded, let calls = message.toolCalls {
                Divider()
                    .padding(.horizontal, 8)
                toolCallsDetailView(calls)
            }
        }
        .background(Color.secondary.opacity(0.08))
        .cornerRadius(6)
    }

    @ViewBuilder
    private func toolCallsDetailView(_ calls: [ToolCallDetail]) -> some View {
        VStack(alignment: .leading, spacing: 0) {
            ForEach(calls.indices, id: \.self) { i in
                let call = calls[i]
                VStack(alignment: .leading, spacing: 6) {
                    // Tool name + status
                    HStack(spacing: 6) {
                        Image(systemName: "function")
                            .font(.system(size: 10, weight: .semibold))
                            .foregroundColor(.secondary)
                        Text(call.name)
                            .font(.system(size: 11, weight: .semibold))
                            .foregroundColor(.secondary)
                        Spacer()
                        if call.result == nil {
                            ProgressView()
                                .scaleEffect(0.5)
                                .frame(width: 12, height: 12)
                        } else {
                            Image(systemName: "checkmark.circle.fill")
                                .font(.system(size: 10))
                                .foregroundColor(.green.opacity(0.8))
                        }
                    }

                    // Input
                    if !call.input.isEmpty && call.input != "{}" {
                        toolDetailSection(label: "Input", text: prettyJSON(call.input), maxHeight: 100)
                    }

                    // Result
                    if let result = call.result {
                        toolDetailSection(label: "Result", text: prettyJSON(result), maxHeight: 180)
                    }
                }
                .padding(.horizontal, 12)
                .padding(.vertical, 8)

                if i < calls.count - 1 {
                    Divider().padding(.horizontal, 12)
                }
            }
        }
    }

    @ViewBuilder
    private func toolDetailSection(label: String, text: String, maxHeight: CGFloat) -> some View {
        VStack(alignment: .leading, spacing: 2) {
            Text(label.uppercased())
                .font(.system(size: 9, weight: .semibold))
                .foregroundColor(.secondary.opacity(0.6))
            ScrollView([.vertical, .horizontal]) {
                Text(text)
                    .font(.system(size: 10, design: .monospaced))
                    .foregroundColor(.secondary)
                    .textSelection(.enabled)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .fixedSize(horizontal: false, vertical: true)
            }
            .frame(maxHeight: maxHeight)
            .background(Color.secondary.opacity(0.06))
            .cornerRadius(4)
        }
    }

    private func prettyJSON(_ raw: String) -> String {
        guard let data = raw.data(using: .utf8),
              let obj = try? JSONSerialization.jsonObject(with: data),
              let pretty = try? JSONSerialization.data(withJSONObject: obj, options: [.prettyPrinted, .sortedKeys]),
              let str = String(data: pretty, encoding: .utf8) else {
            return raw
        }
        return str
    }

    // MARK: - Message Content

    @ViewBuilder
    private var messageContent: some View {
        switch message.role {
        case .assistant:
            MarkdownContentView(content: message.content, fontSize: settings.dialogTextSize)
        case .system:
            if isErrorMessage {
                HStack(alignment: .top, spacing: 8) {
                    Image(systemName: "exclamationmark.triangle.fill")
                        .foregroundColor(.confabError)
                        .font(.system(size: settings.dialogTextSize))
                    Text(message.content)
                        .font(.system(size: settings.dialogTextSize))
                        .foregroundColor(.confabPrimary)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .textSelection(.enabled)
                }
            } else {
                Text(message.content)
                    .font(.system(size: settings.dialogTextSize))
                    .foregroundColor(.confabPrimary)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .textSelection(.enabled)
            }
        case .user:
            // User messages: preserve line breaks as-is (plain text, not markdown)
            Text(message.content)
                .font(.system(size: settings.dialogTextSize))
                .foregroundColor(.confabPrimary)
                .lineSpacing(4)
                .frame(maxWidth: .infinity, alignment: .leading)
                .textSelection(.enabled)
        }
    }

    // MARK: - Error Detection

    private var isErrorMessage: Bool {
        message.role == .system && message.content.hasPrefix("\u{274C}")
    }

    private var messageBorderColor: Color {
        if isErrorMessage {
            return .confabError.opacity(0.5)
        }
        return Color.messageColor(for: message.role).opacity(0.3)
    }

    // MARK: - Copy

    #if os(macOS)
    private func copyContent() {
        let pasteboard = NSPasteboard.general
        pasteboard.clearContents()
        pasteboard.setString(message.content, forType: .string)
        withAnimation {
            showCopied = true
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            withAnimation {
                showCopied = false
            }
        }
    }

    private func loadStarredState() {
        if let metadata = try? DatabaseService.shared.getMessageMetadata(messageId: message.id) {
            isStarred = metadata.user_starred == 1
        } else {
            // Unsaved message — use in-memory flag on the Message struct
            isStarred = message.isStarred
        }
    }

    private func toggleStar() {
        viewModel?.toggleMessageStar(messageId: message.id)
        isStarred.toggle()
    }
    #endif

    private var roleIcon: some View {
        Image(systemName: message.role.iconName)
            .font(.title3)
            .foregroundColor(Color.messageColor(for: message.role))
            .frame(width: 32, height: 32)
            .background(
                Circle()
                    .fill(Color.messageColor(for: message.role).opacity(0.15))
            )
    }
}

// MARK: - Generated Images Display

struct GeneratedImagesView: View {
    let images: [Data]
    @State private var savedMessage: String?

    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            ForEach(images.indices, id: \.self) { index in
                if let nsImage = platformImage(from: images[index]) {
                    VStack(alignment: .leading, spacing: 4) {
                        #if os(macOS)
                        Image(nsImage: nsImage)
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(maxWidth: 512, maxHeight: 512)
                            .cornerRadius(8)
                            .shadow(color: .black.opacity(0.3), radius: 4)
                            .contextMenu {
                                Button("Save to Downloads") {
                                    saveImage(data: images[index], index: index)
                                }
                                Button("Copy Image") {
                                    copyImage(nsImage)
                                }
                            }
                        #else
                        Image(uiImage: nsImage)
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(maxWidth: 512, maxHeight: 512)
                            .cornerRadius(8)
                        #endif
                    }
                }
            }

            if let msg = savedMessage {
                Text(msg)
                    .font(.caption2)
                    .foregroundColor(.green)
                    .transition(.opacity)
            }
        }
    }

    #if os(macOS)
    private func platformImage(from data: Data) -> NSImage? {
        NSImage(data: data)
    }

    private func saveImage(data: Data, index: Int) {
        let downloads = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first
            ?? FileManager.default.temporaryDirectory
        let timestamp = Int(Date().timeIntervalSince1970)
        let filename = "oai_image_\(timestamp)_\(index).png"
        let fileURL = downloads.appendingPathComponent(filename)

        do {
            try data.write(to: fileURL)
            withAnimation { savedMessage = "Saved to \(filename)" }
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                withAnimation { savedMessage = nil }
            }
        } catch {
            withAnimation { savedMessage = "Failed to save: \(error.localizedDescription)" }
        }
    }

    private func copyImage(_ image: NSImage) {
        let pasteboard = NSPasteboard.general
        pasteboard.clearContents()
        pasteboard.writeObjects([image])
    }
    #else
    private func platformImage(from data: Data) -> UIImage? {
        UIImage(data: data)
    }
    #endif
}

#Preview {
    VStack(spacing: 12) {
        MessageRow(message: Message.mockUser1)
        MessageRow(message: Message.mockAssistant1)
        MessageRow(message: Message.mockSystem)
    }
    .padding()
    .background(Color.confabBackground)
}
