//
//  Instance.swift
//  Pachyderm
//
//  Created by Shadowfacts on 9/9/18.
//  Copyright © 2018 Shadowfacts. All rights reserved.
//

import Foundation

public struct Instance: Decodable, Sendable {
    public let uri: String
    public let title: String
    public let description: String
    public let shortDescription: String?
    public let email: String?
    public let version: String
    public let urls: [String: URL]
    public let thumbnail: URL?
    public let languages: [String]?
    public let stats: Stats?
    public let configuration: Configuration?
    public let rules: [Rule]?

    // pleroma doesn't currently implement these
    public let contactAccount: Account?
    
    // superseded by mastodon's configuration.statuses.max_characters, still used by older instances & pleroma
    let maxTootCharacters: Int?
    let pollLimits: PollsConfiguration?
    
    public var maxStatusCharacters: Int? {
        configuration?.statuses.maxCharacters ?? maxTootCharacters
    }
    public var pollsConfiguration: PollsConfiguration? {
        configuration?.polls ?? pollLimits
    }
    
    // we need a custom decoder, because all API-compatible implementations don't return some data in the same format
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.uri = try container.decode(String.self, forKey: .uri)
        self.title = try container.decode(String.self, forKey: .title)
        self.description = try container.decode(String.self, forKey: .description)
        self.shortDescription = try container.decodeIfPresent(String.self, forKey: .shortDescription)
        self.email = try container.decodeIfPresent(String.self, forKey: .email)
        self.version = try container.decode(String.self, forKey: .version)
        if let urls = try? container.decodeIfPresent([String: URL].self, forKey: .urls) {
            self.urls = urls
        } else {
            self.urls = [:]
        }
        
        self.languages = try? container.decodeIfPresent([String].self, forKey: .languages)
        self.contactAccount = try? container.decodeIfPresent(Account.self, forKey: .contactAccount)
        
        self.stats = try? container.decodeIfPresent(Stats.self, forKey: .stats)
        self.thumbnail = try? container.decodeIfPresent(URL.self, forKey: .thumbnail)
        
        self.configuration = try? container.decodeIfPresent(Configuration.self, forKey: .configuration)
        self.rules = try? container.decodeIfPresent([Rule].self, forKey: .rules)
        
        if let maxTootCharacters = try? container.decodeIfPresent(Int.self, forKey: .maxTootCharacters) {
            self.maxTootCharacters = maxTootCharacters
        } else if let str = try? container.decodeIfPresent(String.self, forKey: .maxTootCharacters),
            let maxTootCharacters = Int(str, radix: 10) {
            self.maxTootCharacters = maxTootCharacters
        } else {
            self.maxTootCharacters = nil
        }
        self.pollLimits = try? container.decodeIfPresent(PollsConfiguration.self, forKey: .pollLimits)
        
    }
    
    private enum CodingKeys: String, CodingKey {
        case uri
        case title
        case description
        case shortDescription = "short_description"
        case email
        case version
        case urls
        case thumbnail
        case languages
        case stats
        case configuration
        case contactAccount = "contact_account"
        case rules
        
        case maxTootCharacters = "max_toot_chars"
        case pollLimits = "poll_limits"
    }
}

extension Instance {
    public struct Stats: Decodable, Sendable {
        public let domainCount: Int?
        public let statusCount: Int?
        public let userCount: Int?
        
        private enum CodingKeys: String, CodingKey {
            case domainCount = "domain_count"
            case statusCount = "status_count"
            case userCount = "user_count"
        }
    }
}

extension Instance {
    public struct Configuration: Codable, Sendable {
        public let statuses: StatusesConfiguration
        public let mediaAttachments: MediaAttachmentsConfiguration
        /// Use Instance.pollsConfiguration to support older instance that don't have this nested
        let polls: PollsConfiguration
        
        private enum CodingKeys: String, CodingKey {
            case statuses
            case mediaAttachments = "media_attachments"
            case polls
        }
    }
}

extension Instance {
    public struct StatusesConfiguration: Codable, Sendable {
        public let maxCharacters: Int
        public let maxMediaAttachments: Int
        public let charactersReservedPerURL: Int
        
        private enum CodingKeys: String, CodingKey {
            case maxCharacters = "max_characters"
            case maxMediaAttachments = "max_media_attachments"
            case charactersReservedPerURL = "characters_reserved_per_url"
        }
    }
}

extension Instance {
    public struct MediaAttachmentsConfiguration: Codable, Sendable {
        public let supportedMIMETypes: [String]
        public let imageSizeLimit: Int
        public let imageMatrixLimit: Int
        public let videoSizeLimit: Int
        public let videoFrameRateLimit: Int
        public let videoMatrixLimit: Int
        
        private enum CodingKeys: String, CodingKey {
            case supportedMIMETypes = "supported_mime_types"
            case imageSizeLimit = "image_size_limit"
            case imageMatrixLimit = "image_matrix_limit"
            case videoSizeLimit = "video_size_limit"
            case videoFrameRateLimit = "video_frame_rate_limit"
            case videoMatrixLimit = "video_matrix_limit"
        }
    }
}

extension Instance {
    public struct PollsConfiguration: Codable, Sendable {
        public let maxOptions: Int
        public let maxCharactersPerOption: Int
        public let minExpiration: TimeInterval
        public let maxExpiration: TimeInterval
        
        private enum CodingKeys: String, CodingKey {
            case maxOptions = "max_options"
            case maxCharactersPerOption = "max_characters_per_option"
            case minExpiration = "min_expiration"
            case maxExpiration = "max_expiration"
        }
    }
}

extension Instance {
    public struct Rule: Decodable, Identifiable, Sendable {
        public let id: String
        public let text: String
    }
}
