//
//  AutocompleteEmojisController.swift
//  ComposeUI
//
//  Created by Shadowfacts on 3/26/23.
//

import SwiftUI
import Pachyderm
import Combine

class AutocompleteEmojisController: ViewController {
    unowned let composeController: ComposeController
    var mastodonController: ComposeMastodonContext { composeController.mastodonController }
    
    private var stateCancellable: AnyCancellable?
    private var searchTask: Task<Void, Never>?
    
    @Published var expanded = false
    @Published var emojis: [Emoji] = []
    @Published var emojisBySection: [String: [Emoji]] = [:]
    
    init(composeController: ComposeController) {
        self.composeController = composeController
        
        stateCancellable = composeController.$currentInput
            .compactMap { $0 }
            .flatMap { $0.autocompleteStatePublisher }
            .compactMap {
                if case .emoji(let s) = $0 {
                    return s
                } else {
                    return nil
                }
            }
            .removeDuplicates()
            .sink { [unowned self] query in
                self.searchTask?.cancel()
                self.searchTask = Task { [weak self] in
                    await self?.queryChanged(query)
                }
            }
    }
    
    @MainActor
    private func queryChanged(_ query: String) async {
        var emojis = await withCheckedContinuation { continuation in
            composeController.mastodonController.getCustomEmojis {
                continuation.resume(returning: $0)
            }
        }
        guard !Task.isCancelled else {
            return
        }
        
        if !query.isEmpty {
            emojis =
            emojis.map { emoji -> (Emoji, (matched: Bool, score: Int)) in
                (emoji, FuzzyMatcher.match(pattern: query, str: emoji.shortcode))
            }
            .filter(\.1.matched)
            .sorted { $0.1.score > $1.1.score }
            .map(\.0)
        }
        
        var shortcodes = Set<String>()
        var newEmojis = [Emoji]()
        var newEmojisBySection = [String: [Emoji]]()
        for emoji in emojis where !shortcodes.contains(emoji.shortcode) {
            newEmojis.append(emoji)
            shortcodes.insert(emoji.shortcode)
            
            let category = emoji.category ?? ""
            if newEmojisBySection.keys.contains(category) {
                newEmojisBySection[category]!.append(emoji)
            } else {
                newEmojisBySection[category] = [emoji]
            }
        }
        self.emojis = newEmojis
        self.emojisBySection = newEmojisBySection
    }
    
    private func toggleExpanded() {
        withAnimation {
            expanded.toggle()
        }
    }
    
    private func autocomplete(with emoji: Emoji) {
        guard let input = composeController.currentInput else { return }
        input.autocomplete(with: ":\(emoji.shortcode):")
    }
    
    var view: some View {
        AutocompleteEmojisView()
    }
    
    struct AutocompleteEmojisView: View {
        @EnvironmentObject private var composeController: ComposeController
        @EnvironmentObject private var controller: AutocompleteEmojisController
        @ScaledMetric private var emojiSize = 30
        
        var body: some View {
            // When exapnded, the toggle button should be at the top. When collapsed, it should be centered.
            HStack(alignment: controller.expanded ? .top : .center, spacing: 0) {
                emojiList
                    .transition(.move(edge: .bottom))
                
                toggleExpandedButton
                    .padding(.trailing, 8)
                    .padding(.top, controller.expanded ? 8 : 0)
            }
        }
        
        @ViewBuilder
        private var emojiList: some View {
            if controller.expanded {
                verticalGrid
                    .frame(height: 150)
            } else {
                horizontalScrollView
            }
        }
        
        private var verticalGrid: some View {
            ScrollView {
                LazyVGrid(columns: [GridItem(.adaptive(minimum: emojiSize), spacing: 4)]) {
                    ForEach(controller.emojisBySection.keys.sorted(), id: \.self) { section in
                        Section {
                            ForEach(controller.emojisBySection[section]!, id: \.shortcode) { emoji in
                                Button(action: { controller.autocomplete(with: emoji) }) {
                                    composeController.emojiImageView(emoji)
                                        .frame(height: emojiSize)
                                }
                                .accessibilityLabel(emoji.shortcode)
                            }
                        } header: {
                            if !section.isEmpty {
                                VStack(alignment: .leading, spacing: 2) {
                                    Text(section)
                                        .font(.caption)
                                    
                                    Divider()
                                }
                                .padding(.top, 4)
                            }
                        }
                    }
                }
                .padding(.all, 8)
                // the spacing between the grid sections doesn't seem to be taken into account by the ScrollView?
                .padding(.bottom, CGFloat(controller.emojisBySection.keys.count) * 4)
            }
            .frame(maxWidth: .infinity)
        }
        
        private var horizontalScrollView: some View {
            ScrollView(.horizontal) {
                LazyHStack(spacing: 8) {
                    ForEach(controller.emojis, id: \.shortcode) { emoji in
                        Button(action: { controller.autocomplete(with: emoji) }) {
                            HStack(spacing: 4) {
                                composeController.emojiImageView(emoji)
                                    .frame(height: emojiSize)
                                Text(verbatim: ":\(emoji.shortcode):")
                                    .foregroundColor(.primary)
                            }
                        }
                        .accessibilityLabel(emoji.shortcode)
                        .frame(height: emojiSize)
                    }
                    .animation(.linear(duration: 0.2), value: controller.emojis)
                }
                .padding(.horizontal, 8)
                .frame(height: emojiSize + 16)
            }
        }
        
        private var toggleExpandedButton: some View {
            Button(action: controller.toggleExpanded) {
                Image(systemName: "chevron.down")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .rotationEffect(controller.expanded ? .zero : .degrees(180))
            }
            .accessibilityLabel(controller.expanded ? "Collapse" : "Expand")
            .frame(width: 20, height: 20)
        }
    }
}
