//
//  MastodonSearchController.swift
//  Tusker
//
//  Created by Shadowfacts on 10/1/23.
//  Copyright © 2023 Shadowfacts. All rights reserved.
//

import UIKit
import Pachyderm

private let acctRegex = try! NSRegularExpression(pattern: "[a-z0-9_]+(@[a-z0-9\\-\\.]+[a-z0-9]+)?$", options: .caseInsensitive)
private let dateLikeRegex = try! NSRegularExpression(pattern: "\\d{4}(-\\d{2}(-\\d{2})?)?$")
private let languageRegex = try! NSRegularExpression(pattern: "(?:language:)?(\\w{2,3})$", options: .caseInsensitive)

class MastodonSearchController: UISearchController {
    override var delegate: UISearchControllerDelegate? {
        willSet {
            precondition(newValue === self)
        }
    }
    
    override var searchResultsController: SearchResultsViewController {
        super.searchResultsController as! SearchResultsViewController
    }
    
    init(searchResultsController: SearchResultsViewController) {
        super.init(searchResultsController: searchResultsController)
        
        searchResultsController.tokenHandler = { [unowned self] token, op in
            self.addToken(token, operator: op)
        }
        
        delegate = self
        searchResultsUpdater = searchResultsController
        automaticallyShowsSearchResultsController = false
        showsSearchResultsController = true
        if #available(iOS 16.0, *) {
            scopeBarActivation = .onSearchActivation
        }
        
        searchBar.autocapitalizationType = .none
        searchBar.delegate = self
        searchBar.scopeButtonTitles = SearchResultsViewController.Scope.allCases.map(\.title)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func updateTokenSuggestions(searchText: String, animated: Bool) {
        guard searchResultsController.mastodonController.instanceFeatures.searchOperators else {
            return
        }
        
        let searchText = searchText.trimmingCharacters(in: .whitespaces)
        
        var suggestions: [(SearchOperatorType, [String])] = []
        
        suggestions.append((.has, ["has:media", "has:poll", "has:embed"].filter {
            searchText.isEmpty || $0.contains(searchText)
        }))
        
        suggestions.append((.is, ["is:reply", "is:sensitive"].filter {
            searchText.isEmpty || $0.contains(searchText)
        }))
        
        var langSuggestions = [String]()
        let defaultLanguage = searchResultsController.mastodonController.accountPreferences.serverDefaultLanguage ?? "en"
        let languageToken = "language:\(defaultLanguage)"
        if searchText.isEmpty || languageToken.contains(searchText) {
            langSuggestions.append(languageToken)
        }
        if searchText != defaultLanguage,
           let match = languageRegex.firstMatch(in: searchText, range: NSRange(location: 0, length: searchText.utf16.count)) {
            let identifier = (searchText as NSString).substring(with: match.range(at: 1))
            if #available(iOS 16.0, *) {
                if Locale.LanguageCode.isoLanguageCodes.contains(where: { $0.identifier == identifier }) {
                    langSuggestions.append("language:\(identifier)")
                }
            } else if searchText != "en" {
                langSuggestions.append("language:\(searchText)")
            }
        }
        suggestions.append((.language, langSuggestions))
        
        var fromSuggestions = [String]()
        if searchText.isEmpty || "from:me".contains(searchText) {
            fromSuggestions.append("from:me")
        }
        if searchText != "me",
           let match = acctRegex.firstMatch(in: searchText, range: NSRange(location: 0, length: searchText.utf16.count)) {
            let matched = (searchText as NSString).substring(with: match.range)
            fromSuggestions.append("from:\(matched)")
        }
        suggestions.append((.from, fromSuggestions))
        
        let components = Calendar.current.dateComponents([.year, .month], from: Date())
        for op in [SearchOperatorType.before, .during, .after] {
            if searchText.isEmpty {
                suggestions.append((op, ["\(op.rawValue):\(components.year!)-\(components.month!)"]))
            } else if let match = dateLikeRegex.firstMatch(in: searchText, range: NSRange(location: 0, length: searchText.utf16.count)) {
                let matched = (searchText as NSString).substring(with: match.range)
                suggestions.append((op, ["\(op.rawValue):\(matched)"]))
            }
        }
        
        suggestions.append((.in, ["in:all", "in:library"].filter {
            searchText.isEmpty || $0.contains(searchText)
        }))
        
        searchResultsController.updateTokenSuggestions(suggestions, animated: animated)
    }
    
    private func addToken(_ token: String, operator: SearchOperatorType) {
        let field = searchBar.searchTextField
        if field.tokens.contains(where: { ($0.representedObject as? String) == token }) {
            return
        }
        let searchToken = UISearchToken(icon: nil, text: token)
        searchToken.representedObject = token
        field.insertToken(searchToken, at: field.tokens.count)
        field.text = ""
        let tokenPos = field.positionOfToken(at: field.tokens.count - 1)
        field.selectedTextRange = field.textRange(from: tokenPos, to: field.endOfDocument)
        
        if let requiredScope = `operator`.requiredScope,
           let index = searchBar.scopeButtonTitles?.firstIndex(of: requiredScope.title) {
            searchBar.selectedScopeButtonIndex = index
            searchBar(searchBar, selectedScopeButtonIndexDidChange: index)
        }
    }
}

extension MastodonSearchController: UISearchControllerDelegate {
    func willPresentSearchController(_ searchController: UISearchController) {
        updateTokenSuggestions(searchText: "", animated: false)
    }
}

extension MastodonSearchController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        updateTokenSuggestions(searchText: searchText, animated: true)
    }
    
    func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
        searchResultsController.searchBarTextDidEndEditing(searchBar)
    }
    
    func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
        searchResultsController.searchBar(searchBar, selectedScopeButtonIndexDidChange: selectedScope)
    }
}

extension UISearchBar {
    var searchQueryWithOperators: String {
        var parts = searchTextField.tokens.compactMap { $0.representedObject as? String }
        let query = text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
        if !query.isEmpty {
            parts.append(query)
        }
        return parts.joined(separator: " ")
    }
}

private extension SearchOperatorType {
    var requiredScope: SearchResultsViewController.Scope? {
        switch self {
        case .is, .from, .in:
            return .posts
        default:
            return nil
        }
    }
}
