//
//  Message.swift
//  oAI
//
//  Core message model for chat conversations
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// oAI is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
// Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with oAI. If not, see <https://www.gnu.org/licenses/>.


import Foundation

enum MessageRole: String, Codable {
    case user
    case assistant
    case system
}

/// Detail for a single tool call within a system "🔧 Calling:" message.
/// Not persisted — in-memory only.
struct ToolCallDetail {
    let name: String
    let input: String       // raw JSON string of arguments
    var result: String?     // raw JSON string of result (nil while pending)
}

struct Message: Identifiable, Codable, Equatable {
    let id: UUID
    let role: MessageRole
    var content: String
    var tokens: Int?
    var cost: Double?
    let timestamp: Date
    let attachments: [FileAttachment]?
    var responseTime: TimeInterval? // Time taken to generate response in seconds
    var wasInterrupted: Bool = false // Whether generation was cancelled
    var modelId: String? // Model ID that generated this message (e.g., "gpt-4", "claude-3-sonnet")

    // Streaming state (not persisted)
    var isStreaming: Bool = false

    // Star state (not persisted in messages table — tracked via message_metadata for saved conversations)
    var isStarred: Bool = false

    // Generated images from image-output models (base64-decoded PNG/JPEG data)
    var generatedImages: [Data]? = nil

    // Tool call details (not persisted — in-memory only for expandable display)
    var toolCalls: [ToolCallDetail]? = nil

    // Reasoning/thinking content (not persisted — in-memory only)
    var thinkingContent: String? = nil

    init(
        id: UUID = UUID(),
        role: MessageRole,
        content: String,
        tokens: Int? = nil,
        cost: Double? = nil,
        timestamp: Date = Date(),
        attachments: [FileAttachment]? = nil,
        responseTime: TimeInterval? = nil,
        wasInterrupted: Bool = false,
        modelId: String? = nil,
        isStreaming: Bool = false,
        generatedImages: [Data]? = nil
    ) {
        self.id = id
        self.role = role
        self.content = content
        self.tokens = tokens
        self.cost = cost
        self.timestamp = timestamp
        self.attachments = attachments
        self.responseTime = responseTime
        self.wasInterrupted = wasInterrupted
        self.modelId = modelId
        self.isStreaming = isStreaming
        self.generatedImages = generatedImages
    }

    enum CodingKeys: String, CodingKey {
        case id, role, content, tokens, cost, timestamp, attachments, responseTime, wasInterrupted, modelId
    }

    static func == (lhs: Message, rhs: Message) -> Bool {
        lhs.id == rhs.id &&
        lhs.content == rhs.content &&
        lhs.tokens == rhs.tokens &&
        lhs.cost == rhs.cost &&
        lhs.responseTime == rhs.responseTime &&
        lhs.wasInterrupted == rhs.wasInterrupted &&
        lhs.isStreaming == rhs.isStreaming &&
        lhs.generatedImages == rhs.generatedImages
    }
}

struct FileAttachment: Codable, Equatable {
    let path: String
    let type: AttachmentType
    let data: Data? // file contents: raw bytes for images/PDFs, UTF-8 for text

    enum AttachmentType: String, Codable {
        case image
        case pdf
        case text
    }

    /// Detect attachment type from file extension
    static func typeFromExtension(_ path: String) -> AttachmentType {
        let ext = (path as NSString).pathExtension.lowercased()
        switch ext {
        case "png", "jpg", "jpeg", "gif", "webp", "bmp", "svg":
            return .image
        case "pdf":
            return .pdf
        default:
            return .text
        }
    }

    /// MIME type string for the file (used in base64 data URLs)
    var mimeType: String {
        let ext = (path as NSString).pathExtension.lowercased()
        switch ext {
        case "png": return "image/png"
        case "jpg", "jpeg": return "image/jpeg"
        case "gif": return "image/gif"
        case "webp": return "image/webp"
        case "bmp": return "image/bmp"
        case "svg": return "image/svg+xml"
        case "pdf": return "application/pdf"
        default: return "text/plain"
        }
    }
}

// MARK: - Display Helpers

extension MessageRole {
    var displayName: String {
        switch self {
        case .user: return "You"
        case .assistant: return "Assistant"
        case .system: return "System"
        }
    }
    
    var iconName: String {
        switch self {
        case .user: return "person.circle.fill"
        case .assistant: return "cpu"
        case .system: return "info.circle.fill"
        }
    }
}
