// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen

import Foundation

// MARK: - Server Configuration

struct ExternalMCPServer: Codable, Identifiable, Sendable {
    var id: UUID
    var name: String
    var command: String
    var args: [String]
    var isEnabled: Bool
    var timeout: TimeInterval
    var createdAt: Date

    init(id: UUID = UUID(), name: String, command: String, args: [String] = [],
         isEnabled: Bool = true, timeout: TimeInterval = 30, createdAt: Date = Date()) {
        self.id = id
        self.name = name
        self.command = command
        self.args = args
        self.isEnabled = isEnabled
        self.timeout = timeout
        self.createdAt = createdAt
    }

    var slug: String { Self.makeSlug(from: name) }

    static func makeSlug(from name: String) -> String {
        let s = name
            .lowercased()
            .components(separatedBy: CharacterSet.alphanumerics.inverted)
            .filter { !$0.isEmpty }
            .joined(separator: "_")
        return s.isEmpty ? "ext" : s
    }

    /// Splits a raw arguments string into tokens, respecting single/double-quoted
    /// segments so arguments containing spaces (e.g. `--root "/Users/x/My Documents"`)
    /// survive intact instead of being split on every space.
    static func parseArguments(_ input: String) -> [String] {
        var args: [String] = []
        var current = ""
        var inSingleQuotes = false
        var inDoubleQuotes = false

        for char in input {
            if char == "'" && !inDoubleQuotes {
                inSingleQuotes.toggle()
            } else if char == "\"" && !inSingleQuotes {
                inDoubleQuotes.toggle()
            } else if char.isWhitespace && !inSingleQuotes && !inDoubleQuotes {
                if !current.isEmpty {
                    args.append(current)
                    current = ""
                }
            } else {
                current.append(char)
            }
        }
        if !current.isEmpty { args.append(current) }
        return args
    }

    static let reservedSlugs: Set<String> = [
        "anytype", "paperless", "calendar", "reminders",
        "contacts", "location", "maps", "bash", "web", "read", "write",
        "list", "search", "edit", "delete", "create", "move", "copy", "spawn"
    ]

    var isSlugReserved: Bool { Self.reservedSlugs.contains(slug) }
}

// MARK: - Client State

enum MCPClientState: Equatable {
    case idle
    case connecting
    case ready
    case error(String)
    case crashed
    case stopped
}

// MARK: - State Delegate (all callbacks on MainActor)

@MainActor
protocol ExternalMCPStateDelegate: AnyObject {
    func clientDidBecomeReady(id: UUID, tools: [MCPToolDefinition], server: ExternalMCPServer)
    func clientDidChangeState(id: UUID, state: MCPClientState)
}

// MARK: - Client Errors

enum MCPClientError: LocalizedError {
    case notConnected
    case invalidResponse(String)
    case timeout
    case processLaunchFailed(String)
    case handshakeFailed(String)
    case writeFailed

    var errorDescription: String? {
        switch self {
        case .notConnected:               return "MCP server is not connected"
        case .invalidResponse(let s):     return "Invalid MCP response: \(s)"
        case .timeout:                    return "MCP request timed out"
        case .processLaunchFailed(let s): return "Failed to launch MCP server: \(s)"
        case .handshakeFailed(let s):     return "MCP handshake failed: \(s)"
        case .writeFailed:                return "Failed to write to MCP server stdin"
        }
    }
}

// MARK: - MCP Protocol Types

struct MCPInitializeResult: Decodable {
    let protocolVersion: String
    let capabilities: MCPCapabilities
    let serverInfo: MCPServerInfo?
}

struct MCPCapabilities: Decodable {
    let tools: MCPToolsCapability?
    struct MCPToolsCapability: Decodable { let listChanged: Bool? }
}

struct MCPServerInfo: Decodable {
    let name: String
    let version: String?
}

struct MCPToolsListResult: Decodable {
    let tools: [MCPToolDefinition]
    let nextCursor: String?
}

struct MCPToolDefinition: Decodable {
    let name: String
    let description: String?
    let inputSchema: MCPInputSchema
}

struct MCPInputSchema: Decodable {
    let type: String
    let properties: [String: MCPPropertySchema]?
    let required: [String]?
}

struct MCPPropertySchema: Decodable {
    let type: String?
    let description: String?
    let `enum`: [String]?
    let items: MCPItemsSchema?
    struct MCPItemsSchema: Decodable { let type: String? }
}

struct MCPToolCallResult: Decodable {
    let content: [MCPContent]
    let isError: Bool?
}

struct MCPContent: Decodable {
    let type: String
    let text: String?
    let data: String?
    let mimeType: String?
    let uri: String?
}
