//
//  Preferences.swift
//  TuskerPreferences
//
//  Created by Shadowfacts on 8/28/18.
//  Copyright © 2018 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm
import Combine

public final class Preferences: Codable, ObservableObject {

    public static var shared: Preferences = load()
    
    private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    private static var appGroupDirectory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.space.vaccor.Tusker")!
    private static var archiveURL = appGroupDirectory.appendingPathComponent("preferences").appendingPathExtension("plist")
    
    public static func save() {
        let encoder = PropertyListEncoder()
        let data = try? encoder.encode(shared)
        try? data?.write(to: archiveURL, options: .noFileProtection)
    }
    
    public static func load() -> Preferences {
        let decoder = PropertyListDecoder()
        if let data = try? Data(contentsOf: archiveURL),
            let preferences = try? decoder.decode(Preferences.self, from: data) {
            return preferences
        }
        return Preferences()
    }
    
    public static func migrate(from url: URL) -> Result<Void, any Error> {
        do {
            try? FileManager.default.removeItem(at: archiveURL)
            try FileManager.default.moveItem(at: url, to: archiveURL)
        } catch {
            return .failure(error)
        }
        shared = load()
        return .success(())
    }

    private init() {}
    
    public required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        self.theme = try container.decode(UIUserInterfaceStyle.self, forKey: .theme)
        self.pureBlackDarkMode = try container.decodeIfPresent(Bool.self, forKey: .pureBlackDarkMode) ?? true
        self.accentColor = try container.decodeIfPresent(AccentColor.self, forKey: .accentColor) ?? .default
        self.avatarStyle = try container.decode(AvatarStyle.self, forKey: .avatarStyle)
        self.hideCustomEmojiInUsernames = try container.decode(Bool.self, forKey: .hideCustomEmojiInUsernames)
        self.showIsStatusReplyIcon = try container.decode(Bool.self, forKey: .showIsStatusReplyIcon)
        self.alwaysShowStatusVisibilityIcon = try container.decode(Bool.self, forKey: .alwaysShowStatusVisibilityIcon)
        self.hideActionsInTimeline = try container.decodeIfPresent(Bool.self, forKey: .hideActionsInTimeline) ?? false
        self.showLinkPreviews = try container.decodeIfPresent(Bool.self, forKey: .showLinkPreviews) ?? true
        self.leadingStatusSwipeActions = try container.decodeIfPresent([StatusSwipeAction].self, forKey: .leadingStatusSwipeActions) ?? leadingStatusSwipeActions
        self.trailingStatusSwipeActions = try container.decodeIfPresent([StatusSwipeAction].self, forKey: .trailingStatusSwipeActions) ?? trailingStatusSwipeActions
        self.widescreenNavigationMode = try container.decodeIfPresent(WidescreenNavigationMode.self, forKey: .widescreenNavigationMode) ?? Self.defaultWidescreenNavigationMode
        self.underlineTextLinks = try container.decodeIfPresent(Bool.self, forKey: .underlineTextLinks) ?? false
        
        if let existing = try? container.decode(Visibility.self, forKey: .defaultPostVisibility) {
            self.defaultPostVisibility = .visibility(existing)
        } else {
            self.defaultPostVisibility = try container.decode(PostVisibility.self, forKey: .defaultPostVisibility)
        }
        self.defaultReplyVisibility = try container.decodeIfPresent(ReplyVisibility.self, forKey: .defaultReplyVisibility) ?? .sameAsPost
        self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
        self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
        self.mentionReblogger = try container.decode(Bool.self, forKey: .mentionReblogger)
        self.useTwitterKeyboard = try container.decodeIfPresent(Bool.self, forKey: .useTwitterKeyboard) ?? false
        
        if let blurAllMedia = try? container.decodeIfPresent(Bool.self, forKey: .blurAllMedia) {
            self.attachmentBlurMode = blurAllMedia ? .always : .useStatusSetting
        } else {
            self.attachmentBlurMode = try container.decode(AttachmentBlurMode.self, forKey: .attachmentBlurMode)
        }
        self.blurMediaBehindContentWarning = try container.decodeIfPresent(Bool.self, forKey: .blurMediaBehindContentWarning) ?? true
        self.automaticallyPlayGifs = try container.decode(Bool.self, forKey: .automaticallyPlayGifs)
        self.showUncroppedMediaInline = try container.decodeIfPresent(Bool.self, forKey: .showUncroppedMediaInline) ?? true
        self.showAttachmentBadges = try container.decodeIfPresent(Bool.self, forKey: .showAttachmentBadges) ?? true
        
        self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps)
        self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
        self.inAppSafariAutomaticReaderMode = try container.decode(Bool.self, forKey: .inAppSafariAutomaticReaderMode)
        self.expandAllContentWarnings = try container.decodeIfPresent(Bool.self, forKey: .expandAllContentWarnings) ?? false
        self.collapseLongPosts = try container.decodeIfPresent(Bool.self, forKey: .collapseLongPosts) ?? true
        self.oppositeCollapseKeywords = try container.decodeIfPresent([String].self, forKey: .oppositeCollapseKeywords) ?? []
        self.confirmBeforeReblog = try container.decodeIfPresent(Bool.self, forKey: .confirmBeforeReblog) ?? false
        self.timelineStateRestoration = try container.decodeIfPresent(Bool.self, forKey: .timelineStateRestoration) ?? true
        self.timelineSyncMode = try container.decodeIfPresent(TimelineSyncMode.self, forKey: .timelineSyncMode) ?? .icloud
        self.hideReblogsInTimelines = try container.decodeIfPresent(Bool.self, forKey: .hideReblogsInTimelines) ?? false
        self.hideRepliesInTimelines = try container.decodeIfPresent(Bool.self, forKey: .hideRepliesInTimelines) ?? false
        
        self.showFavoriteAndReblogCounts = try container.decode(Bool.self, forKey: .showFavoriteAndReblogCounts)
        self.defaultNotificationsMode = try container.decode(NotificationsMode.self, forKey: .defaultNotificationsType)
        self.grayscaleImages = try container.decodeIfPresent(Bool.self, forKey: .grayscaleImages) ?? false
        self.disableInfiniteScrolling = try container.decodeIfPresent(Bool.self, forKey: .disableInfiniteScrolling) ?? false
        self.hideTrends = try container.decodeIfPresent(Bool.self, forKey: .hideTrends) ?? false
        
        self.statusContentType = try container.decode(StatusContentType.self, forKey: .statusContentType)
        self.reportErrorsAutomatically = try container.decodeIfPresent(Bool.self, forKey: .reportErrorsAutomatically) ?? true
        let featureFlagNames = (try? container.decodeIfPresent([String].self, forKey: .enabledFeatureFlags)) ?? []
        self.enabledFeatureFlags = Set(featureFlagNames.compactMap(FeatureFlag.init))
        
        self.hasShownLocalTimelineDescription = try container.decodeIfPresent(Bool.self, forKey: .hasShownLocalTimelineDescription) ?? false
        self.hasShownFederatedTimelineDescription = try container.decodeIfPresent(Bool.self, forKey: .hasShownFederatedTimelineDescription) ?? false
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        try container.encode(theme, forKey: .theme)
        try container.encode(pureBlackDarkMode, forKey: .pureBlackDarkMode)
        try container.encode(accentColor, forKey: .accentColor)
        try container.encode(avatarStyle, forKey: .avatarStyle)
        try container.encode(hideCustomEmojiInUsernames, forKey: .hideCustomEmojiInUsernames)
        try container.encode(showIsStatusReplyIcon, forKey: .showIsStatusReplyIcon)
        try container.encode(alwaysShowStatusVisibilityIcon, forKey: .alwaysShowStatusVisibilityIcon)
        try container.encode(hideActionsInTimeline, forKey: .hideActionsInTimeline)
        try container.encode(showLinkPreviews, forKey: .showLinkPreviews)
        try container.encode(leadingStatusSwipeActions, forKey: .leadingStatusSwipeActions)
        try container.encode(trailingStatusSwipeActions, forKey: .trailingStatusSwipeActions)
        try container.encode(widescreenNavigationMode, forKey: .widescreenNavigationMode)
        try container.encode(underlineTextLinks, forKey: .underlineTextLinks)
        
        try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
        try container.encode(defaultReplyVisibility, forKey: .defaultReplyVisibility)
        try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
        try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
        try container.encode(mentionReblogger, forKey: .mentionReblogger)
        try container.encode(useTwitterKeyboard, forKey: .useTwitterKeyboard)
        
        try container.encode(attachmentBlurMode, forKey: .attachmentBlurMode)
        try container.encode(blurMediaBehindContentWarning, forKey: .blurMediaBehindContentWarning)
        try container.encode(automaticallyPlayGifs, forKey: .automaticallyPlayGifs)
        try container.encode(showUncroppedMediaInline, forKey: .showUncroppedMediaInline)
        try container.encode(showAttachmentBadges, forKey: .showAttachmentBadges)
        
        try container.encode(openLinksInApps, forKey: .openLinksInApps)
        try container.encode(useInAppSafari, forKey: .useInAppSafari)
        try container.encode(inAppSafariAutomaticReaderMode, forKey: .inAppSafariAutomaticReaderMode)
        try container.encode(expandAllContentWarnings, forKey: .expandAllContentWarnings)
        try container.encode(collapseLongPosts, forKey: .collapseLongPosts)
        try container.encode(oppositeCollapseKeywords, forKey: .oppositeCollapseKeywords)
        try container.encode(confirmBeforeReblog, forKey: .confirmBeforeReblog)
        try container.encode(timelineStateRestoration, forKey: .timelineStateRestoration)
        try container.encode(timelineSyncMode, forKey: .timelineSyncMode)
        try container.encode(hideReblogsInTimelines, forKey: .hideReblogsInTimelines)
        try container.encode(hideRepliesInTimelines, forKey: .hideRepliesInTimelines)
        
        try container.encode(showFavoriteAndReblogCounts, forKey: .showFavoriteAndReblogCounts)
        try container.encode(defaultNotificationsMode, forKey: .defaultNotificationsType)
        try container.encode(grayscaleImages, forKey: .grayscaleImages)
        try container.encode(disableInfiniteScrolling, forKey: .disableInfiniteScrolling)
        try container.encode(hideTrends, forKey: .hideTrends)
        
        try container.encode(statusContentType, forKey: .statusContentType)
        try container.encode(reportErrorsAutomatically, forKey: .reportErrorsAutomatically)
        try container.encode(enabledFeatureFlags, forKey: .enabledFeatureFlags)
        
        try container.encode(hasShownLocalTimelineDescription, forKey: .hasShownLocalTimelineDescription)
        try container.encode(hasShownFederatedTimelineDescription, forKey: .hasShownFederatedTimelineDescription)
    }
    
    // MARK: Appearance
    @Published public var theme = UIUserInterfaceStyle.unspecified
    @Published public var pureBlackDarkMode = true
    @Published public var accentColor = AccentColor.default
    @Published public var avatarStyle = AvatarStyle.roundRect
    @Published public var hideCustomEmojiInUsernames = false
    @Published public var showIsStatusReplyIcon = false
    @Published public var alwaysShowStatusVisibilityIcon = false
    @Published public var hideActionsInTimeline = false
    @Published public var showLinkPreviews = true
    @Published public var leadingStatusSwipeActions: [StatusSwipeAction] = [.favorite, .reblog]
    @Published public var trailingStatusSwipeActions: [StatusSwipeAction] = [.reply, .share]
    private static var defaultWidescreenNavigationMode = WidescreenNavigationMode.splitScreen
    @Published public var widescreenNavigationMode  = Preferences.defaultWidescreenNavigationMode
    @Published public var underlineTextLinks = false
    
    // MARK: Composing
    @Published public var defaultPostVisibility = PostVisibility.serverDefault
    @Published public var defaultReplyVisibility = ReplyVisibility.sameAsPost
    @Published public var requireAttachmentDescriptions = false
    @Published public var contentWarningCopyMode = ContentWarningCopyMode.asIs
    @Published public var mentionReblogger = false
    @Published public var useTwitterKeyboard = false
    
    // MARK: Media
    @Published public var attachmentBlurMode = AttachmentBlurMode.useStatusSetting {
        didSet {
            if attachmentBlurMode == .always {
                blurMediaBehindContentWarning = true
            } else if attachmentBlurMode == .never {
                blurMediaBehindContentWarning = false
            }
        }
    }
    @Published public var blurMediaBehindContentWarning = true
    @Published public var automaticallyPlayGifs = true
    @Published public var showUncroppedMediaInline = true
    @Published public var showAttachmentBadges = true
    
    // MARK: Behavior
    @Published public var openLinksInApps = true
    @Published public var useInAppSafari = true
    @Published public var inAppSafariAutomaticReaderMode = false
    @Published public var expandAllContentWarnings = false
    @Published public var collapseLongPosts = true
    @Published public var oppositeCollapseKeywords: [String] = []
    @Published public var confirmBeforeReblog = false
    @Published public var timelineStateRestoration = true
    @Published public var timelineSyncMode = TimelineSyncMode.icloud
    @Published public var hideReblogsInTimelines = false
    @Published public var hideRepliesInTimelines = false
    
    // MARK: Digital Wellness
    @Published public var showFavoriteAndReblogCounts = true
    @Published public var defaultNotificationsMode = NotificationsMode.allNotifications
    @Published public var grayscaleImages = false
    @Published public var disableInfiniteScrolling = false
    @Published public var hideTrends = false
    
    // MARK: Advanced
    @Published public var statusContentType: StatusContentType = .plain
    @Published public var reportErrorsAutomatically = true
    @Published public var enabledFeatureFlags: Set<FeatureFlag> = []
    
    // MARK:
    @Published public var hasShownLocalTimelineDescription = false
    @Published public var hasShownFederatedTimelineDescription = false
    
    public func hasFeatureFlag(_ flag: FeatureFlag) -> Bool {
        enabledFeatureFlags.contains(flag)
    }
    
    private enum CodingKeys: String, CodingKey {
        case theme
        case pureBlackDarkMode
        case accentColor
        case avatarStyle
        case hideCustomEmojiInUsernames
        case showIsStatusReplyIcon
        case alwaysShowStatusVisibilityIcon
        case hideActionsInTimeline
        case showLinkPreviews
        case leadingStatusSwipeActions
        case trailingStatusSwipeActions
        case widescreenNavigationMode
        case underlineTextLinks
        
        case defaultPostVisibility
        case defaultReplyVisibility
        case requireAttachmentDescriptions
        case contentWarningCopyMode
        case mentionReblogger
        case useTwitterKeyboard
        
        case blurAllMedia // only used for migration
        case attachmentBlurMode
        case blurMediaBehindContentWarning
        case automaticallyPlayGifs
        case showUncroppedMediaInline
        case showAttachmentBadges
        
        case openLinksInApps
        case useInAppSafari
        case inAppSafariAutomaticReaderMode
        case expandAllContentWarnings
        case collapseLongPosts
        case oppositeCollapseKeywords
        case confirmBeforeReblog
        case timelineStateRestoration
        case timelineSyncMode
        case hideReblogsInTimelines
        case hideRepliesInTimelines
        
        case showFavoriteAndReblogCounts
        case defaultNotificationsType
        case grayscaleImages
        case disableInfiniteScrolling
        case hideTrends = "hideDiscover"
        
        case statusContentType
        case reportErrorsAutomatically
        case enabledFeatureFlags
        
        case hasShownLocalTimelineDescription
        case hasShownFederatedTimelineDescription
    }
    
}

extension Preferences {
    public enum AttachmentBlurMode: Codable, Hashable, CaseIterable {
        case useStatusSetting
        case always
        case never
        
        public var displayName: String {
            switch self {
            case .useStatusSetting:
                return "Default"
            case .always:
                return "Always"
            case .never:
                return "Never"
            }
        }
    }
}

extension UIUserInterfaceStyle: Codable {}

extension Preferences {
    public enum AccentColor: String, Codable, CaseIterable {
        case `default`
        case purple
        case indigo
        case blue
        case cyan
        case teal
        case mint
        case green
//        case yellow
        case orange
        case red
        case pink
//        case brown
        
        public var color: UIColor? {
            switch self {
            case .default:
                return nil
            case .blue:
                return .systemBlue
//            case .brown:
//                return .systemBrown
            case .cyan:
                return .systemCyan
            case .green:
                return .systemGreen
            case .indigo:
                return .systemIndigo
            case .mint:
                return .systemMint
            case .orange:
                return .systemOrange
            case .pink:
                return .systemPink
            case .purple:
                return .systemPurple
            case .red:
                return .systemRed
            case .teal:
                return .systemTeal
//            case .yellow:
//                return .systemYellow
            }
        }
        
        public var name: String {
            switch self {
            case .default:
                return "Default"
            case .blue:
                return "Blue"
//            case .brown:
//                return "Brown"
            case .cyan:
                return "Cyan"
            case .green:
                return "Green"
            case .indigo:
                return "Indigo"
            case .mint:
                return "Mint"
            case .orange:
                return "Orange"
            case .pink:
                return "Pink"
            case .purple:
                return "Purple"
            case .red:
                return "Red"
            case .teal:
                return "Teal"
//            case .yellow:
//                return "Yellow"
            }
        }
    }
}

extension Preferences {
    public enum TimelineSyncMode: String, Codable {
        case mastodon
        case icloud
    }
}

extension Preferences {
    public enum FeatureFlag: String, Codable {
        case iPadMultiColumn = "ipad-multi-column"
        case iPadBrowserNavigation = "ipad-browser-navigation"
    }
}

extension Preferences {
    public enum WidescreenNavigationMode: String, Codable {
        case stack
        case splitScreen
        case multiColumn
    }
}
