import SwiftUI
import WebKit

struct HTMLBlockView: UIViewRepresentable {
    private static let markedJS = BundledWebResources.contents(of: "marked.min.js")
    private static let purifyJS = BundledWebResources.contents(of: "purify.min.js")
    private static let darkCSS = BundledWebResources.contents(of: "github-markdown-dark.min.css")
    private static let lightCSS = BundledWebResources.contents(of: "github-markdown-light.min.css")

    let markdown: String
    @Binding var height: CGFloat
    var baseURL: URL?
    var onNavigateToFile: ((String) -> Void)?
    @Environment(\.colorScheme) private var colorScheme

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    func makeUIView(context: Context) -> WKWebView {
        let config = WKWebViewConfiguration()
        let controller = WKUserContentController()
        controller.add(context.coordinator, name: "heightReporter")
        config.userContentController = controller

        let webView = WKWebView(frame: .zero, configuration: config)
        webView.isOpaque = false
        webView.backgroundColor = .clear
        webView.scrollView.backgroundColor = .clear
        webView.scrollView.isScrollEnabled = true
        webView.scrollView.bounces = false
        webView.scrollView.showsVerticalScrollIndicator = false
        webView.navigationDelegate = context.coordinator
        return webView
    }

    // swiftlint:disable:next function_body_length
    func updateUIView(_ webView: WKWebView, context: Context) {
        let theme = colorScheme == .dark ? "dark" : "light"
        let key = "\(markdown)\n---\n\(theme)"
        guard key != context.coordinator.lastRenderedKey else { return }
        context.coordinator.lastRenderedKey = key

        let jsonData = try? JSONSerialization.data(
            withJSONObject: markdown, options: .fragmentsAllowed,
        )
        let escapedMarkdown = jsonData.flatMap { String(data: $0, encoding: .utf8) } ?? "\"\""

        // Build raw base URL for resolving relative image paths
        let rawBase: String = {
            guard let url = baseURL else { return "" }
            let str = url.absoluteString
            if let range = str.range(of: "/src/branch/") {
                return str.replacingCharacters(in: range, with: "/raw/branch/")
            }
            return str
        }()
        let escapedRawBase = rawBase
            .replacingOccurrences(of: "\\", with: "\\\\")
            .replacingOccurrences(of: "'", with: "\\'")
            .replacingOccurrences(of: "\n", with: "")

        let markdownCSS = theme == "dark" ? Self.darkCSS : Self.lightCSS
        let markedJS = Self.markedJS
        let purifyJS = Self.purifyJS
        // swiftlint:disable:next line_length
        let csp = "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src * data: blob:; connect-src *;"
        let html = """
        <!DOCTYPE html>
        <html data-color-mode="\(theme)" data-\(theme)-theme="\(theme)">
        <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
        <meta http-equiv="Content-Security-Policy" content="\(csp)">
        <style>\(markdownCSS)</style>
        <style>
            body {
                margin: 0;
                padding: 0;
                background: transparent;
            }
            .markdown-body {
                background: transparent;
                font-size: 15px;
                line-height: 1.5;
            }
            .table-wrapper {
                overflow-x: auto;
                -webkit-overflow-scrolling: touch;
            }
            .markdown-body table {
                display: table;
                min-width: 100%;
            }
            .markdown-body img {
                max-width: 100%;
            }
        </style>
        </head>
        <body>
        <div class="markdown-body" id="content"></div>
        <script>\(markedJS)</script>
        <script>\(purifyJS)</script>
        <script>
            const raw = \(escapedMarkdown);
            const rendered = DOMPurify.sanitize(marked.parse(raw), {
                ADD_TAGS: ['details', 'summary', 'picture', 'source', 'figure',
                           'figcaption', 'dl', 'dt', 'dd', 'center', 'section'],
                ADD_ATTR: ['align', 'width', 'height', 'srcset', 'media', 'loading']
            });
            const el = document.getElementById('content');
            el.innerHTML = rendered;

            // Rewrite relative image URLs to absolute raw URLs
            const rawBase = '\(escapedRawBase)';
            if (rawBase) {
                const base = rawBase.endsWith('/') ? rawBase : rawBase + '/';
                function resolveUrl(src) {
                    if (!src || src.startsWith('http') || src.startsWith('data:')
                        || src.startsWith('blob:') || src.startsWith('/')) return src;
                    return base + src;
                }
                el.querySelectorAll('img').forEach(img => {
                    img.src = resolveUrl(img.getAttribute('src'));
                });
                el.querySelectorAll('source').forEach(source => {
                    const srcset = source.getAttribute('srcset');
                    if (srcset) {
                        source.srcset = srcset.split(',').map(entry => {
                            const parts = entry.trim().split(/\\s+/);
                            parts[0] = resolveUrl(parts[0]);
                            return parts.join(' ');
                        }).join(', ');
                    }
                });
            }

            // Wrap tables for horizontal scrolling
            el.querySelectorAll('table').forEach(table => {
                const wrapper = document.createElement('div');
                wrapper.className = 'table-wrapper';
                table.parentNode.insertBefore(wrapper, table);
                wrapper.appendChild(table);
            });

            function reportHeight() {
                const h = document.body.scrollHeight;
                if (h > 0) {
                    window.webkit.messageHandlers.heightReporter.postMessage(h);
                }
            }

            const observer = new ResizeObserver(() => reportHeight());
            observer.observe(document.getElementById('content'));
            setTimeout(reportHeight, 500);
        </script>
        </body>
        </html>
        """

        // Use raw/ endpoint so relative image paths resolve to actual files
        let rawBaseURL = baseURL.flatMap { url -> URL? in
            let str = url.absoluteString
            guard let range = str.range(of: "/src/branch/") else { return url }
            return URL(string: str.replacingCharacters(in: range, with: "/raw/branch/"))
        }
        webView.loadHTMLString(html, baseURL: rawBaseURL)
    }

    static func dismantleUIView(_ webView: WKWebView, coordinator _: Coordinator) {
        webView.configuration.userContentController.removeScriptMessageHandler(
            forName: "heightReporter",
        )
    }

    class Coordinator: NSObject, WKScriptMessageHandler, WKNavigationDelegate {
        var parent: HTMLBlockView
        var lastRenderedKey: String?

        init(parent: HTMLBlockView) {
            self.parent = parent
        }

        func userContentController(
            _: WKUserContentController, didReceive message: WKScriptMessage,
        ) {
            if let height = message.body as? CGFloat, height > 0 {
                DispatchQueue.main.async { [weak self] in
                    self?.parent.height = height
                }
            }
        }

        // swiftlint:disable:next line_length
        func webView(_: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            guard navigationAction.navigationType == .linkActivated,
                  let url = navigationAction.request.url
            else {
                decisionHandler(.allow)
                return
            }

            decisionHandler(.cancel)

            if let onNavigateToFile = parent.onNavigateToFile,
               let path = repoRelativePath(from: url)
            {
                onNavigateToFile(path)
            } else {
                UIApplication.shared.open(url)
            }
        }
    }
}
