//
//  LanguagePicker.swift
//  ComposeUI
//
//  Created by Shadowfacts on 5/4/23.
//

import SwiftUI

@available(iOS 16.0, *)
struct LanguagePicker: View {
    @Binding var draftLanguage: String?
    @Binding var hasChangedSelection: Bool
    @State private var isShowingSheet = false
    
    private var codeFromDraft: Locale.LanguageCode? {
        draftLanguage.map(Locale.LanguageCode.init(_:))
    }
    
    private var codeFromActiveInputMode: Locale.LanguageCode? {
        UITextInputMode.activeInputModes.first.flatMap(Self.codeFromInputMode(_:))
    }
    
    static func codeFromInputMode(_ mode: UITextInputMode) -> Locale.LanguageCode? {
        guard let bcp47Lang = mode.primaryLanguage,
              !bcp47Lang.isEmpty else {
            return nil
        }
        var maybeIso639Code = bcp47Lang[..<bcp47Lang.index(bcp47Lang.startIndex, offsetBy: min(3, bcp47Lang.count))]
        if maybeIso639Code.last == "-" {
            maybeIso639Code = maybeIso639Code[..<maybeIso639Code.index(before: maybeIso639Code.endIndex)]
        }
        let identifier = String(maybeIso639Code)
        // mul (for multiple languages) and unk (unknown) are ISO codes, but not ones that akkoma permits, so we ignore them on all platforms
        guard identifier != "mul",
              identifier != "und" else {
            return nil
        }
        let code = Locale.LanguageCode(identifier)
        if code.isISOLanguage {
            return code
        } else {
            return nil
        }
    }
    
    private var codeFromPreferredLanguages: Locale.LanguageCode? {
        if let identifier = Locale.preferredLanguages.first,
           case let code = Locale.LanguageCode(identifier),
           code.isISOLanguage {
            return code
        } else {
            return nil
        }
    }
    
    private var languageCode: Binding<Locale.LanguageCode> {
        Binding {
            return codeFromDraft ?? codeFromActiveInputMode ?? codeFromPreferredLanguages ?? .english
        } set: { newValue in
            draftLanguage = newValue.identifier
        }
    }
    
    var body: some View {
        Button {
            isShowingSheet = true
        } label: {
            Text((languageCode.wrappedValue.identifier(.alpha2) ?? languageCode.wrappedValue.identifier).uppercased())
        }
        .accessibilityLabel("Post Language")
        .padding(5)
        .hoverEffect()
        .sheet(isPresented: $isShowingSheet) {
            NavigationStack {
                LanguagePickerList(languageCode: languageCode, hasChangedSelection: $hasChangedSelection, isPresented: $isShowingSheet)
            }
            .presentationDetents([.large, .medium])
        }
    }
}

@available(iOS 16.0, *)
private struct LanguagePickerList: View {
    @Binding var languageCode: Locale.LanguageCode
    @Binding var hasChangedSelection: Bool
    @Binding var isPresented: Bool
    @Environment(\.composeUIConfig.groupedBackgroundColor) private var groupedBackgroundColor
    @Environment(\.composeUIConfig.groupedCellBackgroundColor) private var groupedCellBackgroundColor
    @State private var recentLangs: [Lang] = []
    @State private var langs: [Lang] = []
    @State private var filteredLangs: [Lang]?
    @State private var query = ""
    
    private var defaults: UserDefaults {
        UserDefaults(suiteName: "group.space.vaccor.Tusker") ?? .standard
    }
    
    private var recentIdentifiers: [String] {
        get {
            defaults.object(forKey: "LanguagePickerRecents") as? [String] ?? []
        }
        nonmutating set {
            defaults.set(newValue, forKey: "LanguagePickerRecents")
        }
    }
    
    var body: some View {
        List {
            Section {
                ForEach(recentLangs) { lang in
                    button(for: lang)
                }
                .listRowBackground(groupedCellBackgroundColor)
            } header: {
                Text("Recently Used")
            }
            
            Section {
                ForEach(filteredLangs ?? langs) { lang in
                    button(for: lang)
                }
                .listRowBackground(groupedCellBackgroundColor)
            } header: {
                Text("All Languages")
            }
        }
        .listStyle(.insetGrouped)
        .scrollContentBackground(.hidden)
        .background(groupedBackgroundColor.edgesIgnoringSafeArea(.all))
        .searchable(text: $query)
        .scrollDismissesKeyboard(.interactively)
        .navigationTitle("Post Language")
        .navigationBarTitleDisplayMode(.inline)
        .toolbar {
            ToolbarItem(placement: .confirmationAction) {
                Button("Done") {
                    isPresented = false
                }
            }
        }
        .onAppear {
            // make sure recents always contains the currently selected lang
            let recents = addRecentLang(languageCode)
            recentLangs = recents
                .filter { $0 != "mul" && $0 != "und" }
                .map { Lang(code: .init($0)) }
                .sorted { $0.name < $1.name }
            
            langs = Locale.LanguageCode.isoLanguageCodes
                .filter { $0.identifier != "mul" && $0.identifier != "und" }
                .map { Lang(code: $0) }
                .sorted { $0.name < $1.name }
        }
        .onChange(of: query) { newValue in
            if newValue.isEmpty {
                filteredLangs = nil
            } else {
                filteredLangs = langs.filter {
                    $0.name.localizedCaseInsensitiveContains(newValue) || $0.code.identifier.localizedCaseInsensitiveContains(newValue)
                }
            }
        }
    }
    
    @discardableResult
    private func addRecentLang(_ code: Locale.LanguageCode) -> [String] {
        var recents = recentIdentifiers
        if !recents.contains(languageCode.identifier) {
            recents.insert(languageCode.identifier, at: 0)
            if recents.count > 5 {
                recents = Array(recents[..<5])
            }
            recentIdentifiers = recents
        }
        return recents
    }
    
    private func button(for lang: Lang) -> some View {
        Button {
            languageCode = lang.code
            hasChangedSelection = true
            isPresented = false
            addRecentLang(lang.code)
        } label: {
            HStack {
                Text(lang.name)
                Spacer()
                if lang.code == languageCode {
                    Image(systemName: "checkmark")
                }
            }
        }
    }
    
    struct Lang: Identifiable {
        let code: Locale.LanguageCode
        let name: String
        
        var id: String {
            code.identifier
        }
        
        init(code: Locale.LanguageCode) {
            self.code = code
            self.name = Locale.current.localizedString(forLanguageCode: code.identifier) ?? code.identifier
        }
    }
}
