import SwiftUI
import WebKit
import SafariServices

private extension TextStyle {
    var cssSelector: String {
        switch self {
        case .largeTitle:
            return "#site-title"
        case .title1:
            return "section.article-content h1"
        case .title2:
            return "section.article-content h2"
        case .title3:
            return "section.article-content h3"
        case .headline:
            return "section.article-content h4"
        case .subheadline:
            return "section.article-content h5"
        case .body:
            return ".article-content, .article-content p"
        case .bodyBold:
            return "section.article-content strong, section.article-content b"
        case .callout:
            return ".callout"
        case .footnote, .count, .segmentedControl:
            return ".footnote"
        case .caption1:
            return "caption"
        case .caption2:
            return ".wp-caption p.wp-caption-text, .caption"
        case .topic:
            return ".topic"
        case .tabIcon:
            return ".app-tab-icon"
        case .tabLabel:
            return ".app-tab-label"
        case .smallIcon:
            return ".small-icon"
        }
    }
    
    var cssRules: String {
        var rules: [String:String] = ["text-align": "left|right|center"]
        
        rules["line-height"] = "\(Int(self.lineSpacing * 133))%"
        rules["font-family"] = "\"\(uiFont.fontName)\""
        rules["font-size"] = "\(self.size)px"
        rules["color"] = self.uiColor.hexValue(true) + (cssSelector.contains(".") ? " !important" : "")
        
        return rules.compactMap { (key, val) in
            return "\(key): \(val);"
        }.joined(separator: " ")
    }
    
    var css: String {
        return "\(cssSelector) { \(cssRules) }"
    }
    
    static var cssDeclaration: String {
        var styles: [String] = Self.allCases.compactMap { $0.css }
    
        let bundleFontNames = ["Poppins-Bold", "Poppins-Medium", "Poppins-SemiBold", "Poppins-Regular"]
        bundleFontNames.forEach { fontName in
            styles.insert("@font-face { font-family: '\(fontName)'; src: local('\(fontName)'), url('\(fontName).otf') format('opentype'); }", at: 0)
        }
        styles.append("body {-webkit-text-size-adjust: none;}")
        styles.append("div.wp-caption { width: auto !important; }")
        styles.append("img { max-height: 100%; min-height: 100%; height:auto; max-width: 100%; width: auto !important; margin-bottom:5px; }")
        styles.append("iframe { width:100%; height:250px; }")
        styles.append("ol, ul, li { \(TextStyle.body.cssRules) }")
        styles.append("a:link { color: \(ColorName.tint.uiColor.hexValue(true)) }")
        styles.append("a { text-decoration: none; }")
        styles.append(".article-content > p { max-width: 800px; margin-left: auto; margin-right: auto; }")
        styles.append("hr { border: 0; border-style: none; height: 1px; background: \(ColorName.separator.uiColor.hexValue(true)); }")
        
        return styles.joined(separator: " \n ")
    }
}

struct HTMLView: UIViewRepresentable {
    @Environment(\.openURL) private var openURL
    
    @Binding var dynamicHeight: CGFloat
    var html: String
    var assets: [WebAsset]
    var ignoreAppCSS: Bool = false
    var handleDeeplinks: ((Deeplink) -> Void)?

    public class Coordinator: NSObject, WKNavigationDelegate {
        var parent: HTMLView
        let resizeMessageHandlerName: String = "resizeMessageHandler"
        
        init(_ parent: HTMLView) {
            self.parent = parent
        }

        public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            webView.evaluateJavaScript("document.documentElement.scrollHeight", completionHandler: { (height, _) in
                DispatchQueue.main.async {
                    self.parent.dynamicHeight = height as! CGFloat
                }
            })
        }

        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            if navigationAction.navigationType == WKNavigationType.linkActivated {
                if let url = navigationAction.request.url,
                    let rootVC = UIApplication.shared.windows.first?.rootViewController {
                    
                    if let deeplink = Deeplink(url: url), let deeplinkHandler = parent.handleDeeplinks {
                        deeplinkHandler(deeplink)
                    } else {
                        rootVC.present(SFSafariViewController(url: url), animated: true, completion: nil)
                    }
                }

                decisionHandler(WKNavigationActionPolicy.cancel)
                return
            }
            
            decisionHandler(WKNavigationActionPolicy.allow)
        }
    }

    public func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    public func makeUIView(context: Context) -> WKWebView {
        let configuration = WKWebViewConfiguration()
        configuration.userContentController.add(context.coordinator, name: "resizeMessageHandler")
        let webview = WKWebView(frame: .zero, configuration: configuration)
        webview.scrollView.bounces = false
        webview.navigationDelegate = context.coordinator
        webview.scrollView.isScrollEnabled = false
        webview.loadHTMLString(htmlStructure(coordinator: context.coordinator), baseURL: Bundle.main.bundleURL)
        webview.isOpaque = false
        webview.backgroundColor = UIColor.clear
        webview.scrollView.backgroundColor = UIColor.clear
        
        return webview
    }

    public func updateUIView(_ uiView: WKWebView, context: Context) {
    }
    
    private var appCSS: String {
        if ignoreAppCSS {
            return ""
        }
        
        return """
                        <style type='text/css'>
                            \(TextStyle.cssDeclaration)
                        </style>
        """
    }
    
    private func htmlStructure(coordinator: HTMLView.Coordinator) -> String {
        """
        <html>
            <head>
                <meta name='viewport' content='width=device-width, shrink-to-fit=YES, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no'>
                \(htmlCSSAssets)
                \(appCSS)
            </head>
            <body>
                <script>
                const resizeObserver = new ResizeObserver(entries =>
                    window.webkit.messageHandlers.resizeMessageHandler.postMessage(document.documentElement.scrollHeight)
                )
        
                resizeObserver.observe(document.documentElement)
        
                </script>
                <section id="app-content" class="article-content">
                    \(html)
                </section>
                \(htmlJSAssets)
            </body>
        </html>
        """
    }
    
    private var htmlCSSAssets: String {
        let cssURLs: [URL] = self.assets.compactMap { asset in
            if case .css(let url) = asset {
                return url
            }
            return nil
        }
        
        return cssURLs.reduce("", { $0 + "<link rel='stylesheet' type='text/css' href='\($1)' />\n" })
    }
    
    private var htmlJSAssets: String {
        let jsURLs: [URL] = self.assets.compactMap { asset in
            if case .js(let url) = asset {
                return url
            }
            return nil
        }
        
        return jsURLs.reduce("", { $0 + "<script type='text/javascript' src='\($1)'></script>\n" })
    }
}

extension HTMLView.Coordinator: WKScriptMessageHandler {
    public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        
        if message.name == "resizeMessageHandler",
           let height = message.body as? CGFloat {
            let newHeight = height.rounded()
            if parent.dynamicHeight != newHeight {
                DispatchQueue.main.async {
                    self.parent.dynamicHeight = newHeight
                }
            }
        }
    }
    
    
}
