//
//  MVMCoreUIWebView.swift
//  MVMCoreUI
//
//  Created by Ryan on 8/29/19.
//  Copyright © 2019 Verizon Wireless. All rights reserved.
//

import UIKit
import WebKit

@objcMembers open class WebView: View, MVMCoreUIViewConstrainingProtocol {
    
    let mvmWebViewMessageHandler = "mvmWebViewMessageHandler"
    
    var webviewModel: WebViewModel? {
        return model as? WebViewModel
    }
    open var webView: WKWebView?
    open var overLayer = MVMCoreUICommonViewsUtility.commonView()
    public let loadingSpinner = MFLoadingSpinner(frame: .zero)
    var delegateObject: MVMCoreUIDelegateObject?
    var webViewHeight: NSLayoutConstraint?
    var dynamicHeight: Bool = true
    
    
    override open func setupView() {
        super.setupView()
        createWebView(messageHandler: mvmWebViewMessageHandler)
        setupOverLayer()
        pinSpinnerView()
        
        //init height for loading spinner
        webViewHeight = webView?.heightAnchor.constraint(equalToConstant: 44)
        webViewHeight?.isActive = true
    }
    
    func createWebView(messageHandler: String?) {
        let wkUserController = WKUserContentController()
        if let messageHandlerName = messageHandler {
            wkUserController.add(self, name: messageHandlerName)
        }
        let wkConfig = WKWebViewConfiguration()
        wkConfig.userContentController = wkUserController
        let webView = WKWebView(frame: .zero, configuration: wkConfig)
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.uiDelegate = self
        webView.navigationDelegate = self
        self.webView = webView
        addSubview(webView)
        NSLayoutConstraint.constraintPinSubview(toSuperview: webView)
    }

    // MARK: - MVMCoreUIMoleculeViewProtocol
    override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) {
        
        //store the previous webview properties
        let previousHtmlString = webviewModel?.htmlString
        let previousURL = webView?.url
        
        super.set(with: model, delegateObject, additionalData)
        self.delegateObject = delegateObject
        var webViewHeightConstant: CGFloat = 44
        if let height = webviewModel?.height {
            webViewHeightConstant = height
            webViewHeight?.constant = height
            dynamicHeight = false
        }
        if let url = webviewModel?.url {
            if let previousUrl = previousURL, url == previousUrl {
                //dont load the new
            } else {
                webView?.stopLoading()
                webView?.load(URLRequest(url: url))
                webViewHeight?.constant = webViewHeightConstant
                overLayer.isHidden = false
                loadingSpinner.resumeSpinner()
            }
        } else if let htmlString = webviewModel?.htmlString {
            if let previousHTML = previousHtmlString, previousHTML == htmlString {
                    //dont load the new html since they are the same html string as preivous
            } else {
                webView?.stopLoading()
                webViewHeight?.constant = webViewHeightConstant
                webView?.loadHTMLString(htmlString, baseURL: nil)
                overLayer.isHidden = false
                loadingSpinner.resumeSpinner()
            }
        }
        
        if let borderColor = webviewModel?.borderColor?.cgColor {
            webView?.layer.borderWidth = 1.0
            webView?.layer.borderColor = borderColor
        } else {
            webView?.layer.borderWidth = 0.0
            webView?.layer.borderColor = UIColor.clear.cgColor
        }
    }
    
    func pinSpinnerView() {
        addSubview(loadingSpinner)
        // Setup spinner.
        loadingSpinner.clipsToBounds = true
        loadingSpinner.translatesAutoresizingMaskIntoConstraints = false
        
        loadingSpinner.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
        loadingSpinner.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
        loadingSpinner.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
        loadingSpinner.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
    }
    
    func setupOverLayer() {
        addSubview(overLayer)
        overLayer.backgroundColor = .white
        NSLayoutConstraint.constraintPinSubview(toSuperview: overLayer)
    }
}


// MARK: - WKUIDelegate
extension WebView : WKUIDelegate {
    public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // hide loading
        overLayer.isHidden = true
        loadingSpinner.pause()
        
        //update webview's heigth when webview is ready
        if !dynamicHeight {
            return
        }
        /*
            was using "document.readyState" to check the state,
            while evaluateJavaScript "document.readyState",only works when webview contains userscrpt.otherwise, it would return WKErrorDomain Code=4 "A JavaScript exception occurred".
            so webView.isLoading to check load finished state
        */
        if !webView.isLoading {
            webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { [weak self] (result, error) in
                guard let self = self else {
                    return
                }
                MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in
                    guard let self = self else {
                        return
                    }
                    if let height = result as? CGFloat {
                        self.webViewHeight?.constant = height
                    } else {
                        //if failed to get height from javascript, using scrollview.contensize's height
                        self.webViewHeight?.constant = webView.scrollView.contentSize.height
                    }
                    self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self)
                })
            })
        }
    }

    
    public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        //actually no error handle page show in webview. We can handle the error display view by our self.
        //or stop loading by default
        overLayer.isHidden = true
        loadingSpinner.pause()
    }
    
}


// MARK: - WKNavigationDelegate
extension WebView : WKNavigationDelegate {
    public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        //validate request url
        //all validated link should be open in safari
        if (navigationAction.navigationType == .linkActivated), let urlString = navigationAction.request.url?.absoluteString.removingPercentEncoding, !urlString.contains("#") {
            MVMCoreActionHandler.shared()?.openURL(inWebView: navigationAction.request.url, actionInformation: nil, additionalData: nil, delegateObject: nil)
            decisionHandler(.cancel)
        } else {
            decisionHandler(.allow)
        }
    }
}


// MARK: - WKScriptMessageHandler
extension WebView: WKScriptMessageHandler {
    public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == mvmWebViewMessageHandler, let text = message.body as? String {
            /*
             receiving JavaScript func webkit.messageHandlers.{callHandler}.postMessage(body);
             if body is string, will decode to actionmodel.
             use legacy MVMCoreActionHanlder handleAction method for now
             */
            if let data = text.data(using: .utf8) {
                do {
                    let actionMap = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                    MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: nil, delegateObject: self.delegateObject)
                } catch {
                    //actionModel should report error when actionhandler is finshed with actionmodel
                }
            }
        }
    }
}
