//
//  WKWebViewController.swift
//  Sample
//
//  Created by Meniny on 2018-01-20.
//  Copyright © 2018年 Meniny. All rights reserved.
//

import UIKit
import QuickLook
import WebKit

private let estimatedProgressKeyPath = "estimatedProgress"
private let titleKeyPath = "title"
private let cookieKey = "Cookie"

private struct UrlsHandledByApp {
    static var hosts = ["itunes.apple.com"]
    static var schemes = ["tel", "mailto", "sms"]
}

private struct WKDownloadState {
    let destinationURL: URL?
    let mimeType: String?
    let sourceURL: String?
}

private enum DownloadReservationStore {
    static var paths: Set<String> = []
    static let lock = NSLock()
}

public struct WKWebViewCredentials {
    var username: String
    var password: String
}

@objc public protocol WKWebViewControllerDelegate {
    @objc optional func webViewController(_ controller: WKWebViewController, canDismiss url: URL) -> Bool

    @objc optional func webViewController(_ controller: WKWebViewController, didStart url: URL)
    @objc optional func webViewController(_ controller: WKWebViewController, didFinish url: URL)
    @objc optional func webViewController(_ controller: WKWebViewController, didFail url: URL, withError error: Error)
    @objc optional func webViewController(_ controller: WKWebViewController, decidePolicy url: URL, navigationType: NavigationType) -> Bool
}

extension Dictionary {
    func mapKeys<T>(_ transform: (Key) throws -> T) rethrows -> [T: Value] {
        var dictionary = [T: Value]()
        for (key, value) in self {
            dictionary[try transform(key)] = value
        }
        return dictionary
    }
}

enum ConsoleMessageSupport {
    static func normalizePayload(from data: [String: Any]) -> [String: Any] {
        var payload: [String: Any] = [
            "level": normalizedLevel(data["level"] as? String),
            "message": normalizedMessage(data["message"])
        ]

        if let source = data["source"] as? String, !source.isEmpty {
            payload["source"] = source
        }

        if let line = integerValue(from: data["line"]) {
            payload["line"] = line
        }

        if let column = integerValue(from: data["column"]) {
            payload["column"] = column
        }

        if let kind = data["kind"] as? String, !kind.isEmpty {
            payload["kind"] = kind
        }

        return payload
    }

    static func captureScriptSource() -> String {
        return """
        (function() {
          if (window.__capgoInAppBrowserConsoleCaptureInstalled) {
            return;
          }
          window.__capgoInAppBrowserConsoleCaptureInstalled = true;

          var nativeHandler =
            window.webkit &&
            window.webkit.messageHandlers &&
            window.webkit.messageHandlers.consoleMessageHandler;

          if (!nativeHandler) {
            return;
          }

          var serialize = function(value) {
            if (value instanceof Error) {
              return value.stack || value.message || String(value);
            }
            if (typeof value === 'string') {
              return value;
            }
            try {
              return JSON.stringify(value);
            } catch (_error) {
              try {
                return String(value);
              } catch (_error2) {
                return '[unserializable]';
              }
            }
          };

          var post = function(level, values, metadata) {
            try {
              var parts = Array.prototype.slice.call(values || []).map(serialize);
              nativeHandler.postMessage({
                level: level,
                message: parts.join(' '),
                source: metadata && metadata.source ? metadata.source : window.location.href,
                line: metadata && typeof metadata.line === 'number' ? metadata.line : null,
                column: metadata && typeof metadata.column === 'number' ? metadata.column : null,
                kind: metadata && metadata.kind ? metadata.kind : 'console',
              });
            } catch (_error) {}
          };

          ['log', 'info', 'warn', 'error', 'debug'].forEach(function(level) {
            var original = console[level] ? console[level].bind(console) : null;
            console[level] = function() {
              post(level, arguments, null);
              if (original) {
                return original.apply(console, arguments);
              }
            };
          });

          var originalAssert = console.assert ? console.assert.bind(console) : null;
          console.assert = function(condition) {
            if (!condition) {
              var args = Array.prototype.slice.call(arguments, 1);
              post('assert', args.length ? args : ['console.assert'], null);
            }
            if (originalAssert) {
              return originalAssert.apply(console, arguments);
            }
          };

          window.addEventListener('error', function(event) {
            post(
              'error',
              [event && event.message ? event.message : 'Uncaught error'],
              {
                source: event && event.filename ? event.filename : window.location.href,
                line: event && typeof event.lineno === 'number' ? event.lineno : null,
                column: event && typeof event.colno === 'number' ? event.colno : null,
                kind: 'error',
              }
            );
          });

          window.addEventListener('unhandledrejection', function(event) {
            var reason = event ? event.reason : null;
            var message =
              reason && reason.message
                ? reason.message
                : reason
                  ? serialize(reason)
                  : 'Unhandled promise rejection';
            post('error', [message], { source: window.location.href, kind: 'unhandledrejection' });
          });
        })();
        """
    }

    private static func normalizedLevel(_ value: String?) -> String {
        let normalized = value?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
        switch normalized {
        case "log", "info", "warn", "error", "debug", "assert":
            return normalized ?? "log"
        default:
            return "log"
        }
    }

    private static func normalizedMessage(_ value: Any?) -> String {
        switch value {
        case let text as String:
            return text
        case let number as NSNumber:
            return number.stringValue
        case nil:
            return ""
        default:
            return String(describing: value!)
        }
    }

    private static func integerValue(from value: Any?) -> Int? {
        switch value {
        case let int as Int:
            return int
        case let number as NSNumber:
            return number.intValue
        case let text as String:
            return Int(text)
        default:
            return nil
        }
    }
}

open class WKWebViewController: UIViewController, WKScriptMessageHandler {

    public init() {
        super.init(nibName: nil, bundle: nil)
    }

    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    public init(source: WKWebSource?, credentials: WKWebViewCredentials? = nil, allowWebViewJsVisibilityControl: Bool = false, allowScreenshotsFromWebPage: Bool = false, captureConsoleLogs: Bool = false) {
        super.init(nibName: nil, bundle: nil)
        self.source = source
        self.credentials = credentials
        self.allowWebViewJsVisibilityControl = allowWebViewJsVisibilityControl
        self.allowScreenshotsFromWebPage = allowScreenshotsFromWebPage
        self.captureConsoleLogs = captureConsoleLogs
        self.initWebview()
    }

    public init(url: URL, credentials: WKWebViewCredentials? = nil, allowWebViewJsVisibilityControl: Bool = false, allowScreenshotsFromWebPage: Bool = false, captureConsoleLogs: Bool = false) {
        super.init(nibName: nil, bundle: nil)
        self.source = .remote(url)
        self.credentials = credentials
        self.allowWebViewJsVisibilityControl = allowWebViewJsVisibilityControl
        self.allowScreenshotsFromWebPage = allowScreenshotsFromWebPage
        self.captureConsoleLogs = captureConsoleLogs
        self.initWebview()
    }

    public init(url: URL, headers: [String: String], isInspectable: Bool, credentials: WKWebViewCredentials? = nil, preventDeeplink: Bool, allowWebViewJsVisibilityControl: Bool = false, allowScreenshotsFromWebPage: Bool = false, captureConsoleLogs: Bool = false) {
        super.init(nibName: nil, bundle: nil)
        self.source = .remote(url)
        self.credentials = credentials
        self.allowWebViewJsVisibilityControl = allowWebViewJsVisibilityControl
        self.allowScreenshotsFromWebPage = allowScreenshotsFromWebPage
        self.captureConsoleLogs = captureConsoleLogs
        self.setHeaders(headers: headers)
        self.setPreventDeeplink(preventDeeplink: preventDeeplink)
        self.initWebview(isInspectable: isInspectable)
    }

    public init(url: URL, headers: [String: String], isInspectable: Bool, credentials: WKWebViewCredentials? = nil, preventDeeplink: Bool, blankNavigationTab: Bool, enabledSafeBottomMargin: Bool, enabledSafeTopMargin: Bool = true, allowWebViewJsVisibilityControl: Bool = false, allowScreenshotsFromWebPage: Bool = false, captureConsoleLogs: Bool = false, openBlankTargetInWebView: Bool = false) {
        super.init(nibName: nil, bundle: nil)
        self.blankNavigationTab = blankNavigationTab
        self.enabledSafeBottomMargin = enabledSafeBottomMargin
        self.enabledSafeTopMargin = enabledSafeTopMargin
        self.source = .remote(url)
        self.credentials = credentials
        self.allowWebViewJsVisibilityControl = allowWebViewJsVisibilityControl
        self.allowScreenshotsFromWebPage = allowScreenshotsFromWebPage
        self.captureConsoleLogs = captureConsoleLogs
        self.openBlankTargetInWebView = openBlankTargetInWebView
        self.setHeaders(headers: headers)
        self.setPreventDeeplink(preventDeeplink: preventDeeplink)
        self.initWebview(isInspectable: isInspectable)
    }

    public init(url: URL, headers: [String: String], isInspectable: Bool, credentials: WKWebViewCredentials? = nil, preventDeeplink: Bool, blankNavigationTab: Bool, enabledSafeBottomMargin: Bool, enabledSafeTopMargin: Bool = true, blockedHosts: [String], allowWebViewJsVisibilityControl: Bool = false, allowScreenshotsFromWebPage: Bool = false, captureConsoleLogs: Bool = false, openBlankTargetInWebView: Bool = false) {
        super.init(nibName: nil, bundle: nil)
        self.blankNavigationTab = blankNavigationTab
        self.enabledSafeBottomMargin = enabledSafeBottomMargin
        self.enabledSafeTopMargin = enabledSafeTopMargin
        self.source = .remote(url)
        self.credentials = credentials
        self.allowWebViewJsVisibilityControl = allowWebViewJsVisibilityControl
        self.allowScreenshotsFromWebPage = allowScreenshotsFromWebPage
        self.captureConsoleLogs = captureConsoleLogs
        self.openBlankTargetInWebView = openBlankTargetInWebView
        self.setHeaders(headers: headers)
        self.setPreventDeeplink(preventDeeplink: preventDeeplink)
        self.setBlockedHosts(blockedHosts: blockedHosts)
        self.initWebview(isInspectable: isInspectable)
    }

    public init(url: URL, headers: [String: String], isInspectable: Bool, credentials: WKWebViewCredentials? = nil, preventDeeplink: Bool, blankNavigationTab: Bool, enabledSafeBottomMargin: Bool, enabledSafeTopMargin: Bool = true, blockedHosts: [String], authorizedAppLinks: [String], allowWebViewJsVisibilityControl: Bool = false, allowScreenshotsFromWebPage: Bool = false, captureConsoleLogs: Bool = false, proxyRequests: Bool = false, proxySchemeHandler: ProxySchemeHandler? = nil, documentStartUserScripts: [String] = [], openBlankTargetInWebView: Bool = false) {
        super.init(nibName: nil, bundle: nil)
        self.blankNavigationTab = blankNavigationTab
        self.enabledSafeBottomMargin = enabledSafeBottomMargin
        self.enabledSafeTopMargin = enabledSafeTopMargin
        self.source = .remote(url)
        self.credentials = credentials
        self.allowWebViewJsVisibilityControl = allowWebViewJsVisibilityControl
        self.allowScreenshotsFromWebPage = allowScreenshotsFromWebPage
        self.captureConsoleLogs = captureConsoleLogs
        self.proxyRequests = proxyRequests
        self.proxySchemeHandler = proxySchemeHandler
        self.openBlankTargetInWebView = openBlankTargetInWebView
        self.setHeaders(headers: headers)
        self.setPreventDeeplink(preventDeeplink: preventDeeplink)
        self.setBlockedHosts(blockedHosts: blockedHosts)
        self.setAuthorizedAppLinks(authorizedAppLinks: authorizedAppLinks)
        self.documentStartUserScripts = documentStartUserScripts
        self.initWebview(isInspectable: isInspectable)
    }

    open var hasDynamicTitle = false
    open var source: WKWebSource?
    /// use `source` instead
    open internal(set) var url: URL?
    open var tintColor: UIColor?
    open var allowsFileURL = true
    open var allowWebViewJsVisibilityControl = false
    open var allowScreenshotsFromWebPage = false
    open var captureConsoleLogs = false
    open var handleDownloads = false
    open var delegate: WKWebViewControllerDelegate?
    open var bypassedSSLHosts: [String]?
    open var cookies: [HTTPCookie]?
    open var headers: [String: String]?
    open var httpMethod: String?
    open var httpBody: String?
    open var capBrowserPlugin: InAppBrowserPlugin?
    var instanceId: String = ""
    var shareDisclaimer: [String: Any]?
    var shareSubject: String?
    var didpageInit = false
    var viewHeightLandscape: CGFloat?
    var viewHeightPortrait: CGFloat?
    var currentViewHeight: CGFloat?
    open var closeModal = false
    open var closeModalTitle = ""
    open var closeModalDescription = ""
    open var closeModalOk = ""
    open var closeModalCancel = ""
    open var closeModalURLPattern = "" {
        didSet {
            closeModalURLRegex = closeModalURLPattern.isEmpty ? nil : (try? NSRegularExpression(pattern: closeModalURLPattern))
        }
    }
    private var closeModalURLRegex: NSRegularExpression?
    open var ignoreUntrustedSSLError = false
    open var enableGooglePaySupport = false
    var viewWasPresented = false
    var preventDeeplink: Bool = false
    var openBlankTargetInWebView: Bool = false
    var blankNavigationTab: Bool = false
    var capacitorStatusBar: UIView?
    var enabledSafeBottomMargin: Bool = false
    var enabledSafeTopMargin: Bool = true
    var blockedHosts: [String] = []
    var authorizedAppLinks: [String] = []
    var activeNativeNavigationForWebview: Bool = true
    var disableOverscroll: Bool = false
    var proxyRequests: Bool = false
    var proxySchemeHandler: ProxySchemeHandler?
    var initialWebConfiguration: WKWebViewConfiguration?
    var waitsForPopupNavigation = false
    var hiddenPopupWindow = false
    var opensHidden = false

    // Dimension properties
    var customWidth: CGFloat?
    var customHeight: CGFloat?
    var customX: CGFloat?
    var customY: CGFloat?

    internal var preShowSemaphore: DispatchSemaphore?
    internal var preShowError: String?
    private var isWebViewInitialized = false
    private var downloadStates: [ObjectIdentifier: WKDownloadState] = [:]
    private var previewItemURL: URL?

    func setHeaders(headers: [String: String]) {
        self.headers = headers
        let lowercasedHeaders = headers.mapKeys { $0.lowercased() }
        let userAgent = lowercasedHeaders["user-agent"]
        self.headers?.removeValue(forKey: "User-Agent")
        self.headers?.removeValue(forKey: "user-agent")

        if let userAgent = userAgent {
            self.customUserAgent = userAgent
        }
    }

    func setPreventDeeplink(preventDeeplink: Bool) {
        self.preventDeeplink = preventDeeplink
    }

    func setBlockedHosts(blockedHosts: [String]) {
        self.blockedHosts = blockedHosts
    }

    func setAuthorizedAppLinks(authorizedAppLinks: [String]) {
        self.authorizedAppLinks = authorizedAppLinks
    }

    private func register(download: WKDownload, response: URLResponse?, sourceURL: String? = nil) {
        download.delegate = self
        downloadStates[ObjectIdentifier(download)] = WKDownloadState(
            destinationURL: nil,
            mimeType: response?.mimeType,
            sourceURL: sourceURL ?? response?.url?.absoluteString
        )
    }

    private func attachmentDisposition(_ response: URLResponse) -> String? {
        guard let httpResponse = response as? HTTPURLResponse else {
            return nil
        }

        return httpResponse.value(forHTTPHeaderField: "Content-Disposition")
    }

    private func attachmentDispositionType(_ response: URLResponse) -> String? {
        guard let disposition = attachmentDisposition(response)?
            .split(separator: ";", maxSplits: 1)
            .first?
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .lowercased(),
              !disposition.isEmpty else {
            return nil
        }

        return disposition
    }

    private func shouldInterceptDownload(for response: WKNavigationResponse) -> Bool {
        guard handleDownloads else {
            return false
        }

        if !response.canShowMIMEType {
            return true
        }

        guard let disposition = attachmentDispositionType(response.response) else {
            return false
        }

        return disposition == "attachment"
    }

    private func sanitizeDownloadFilename(_ suggestedFilename: String) -> String {
        let invalidCharacters = CharacterSet(charactersIn: "/:\\?%*|\"<>")
        let sanitized = suggestedFilename
            .components(separatedBy: invalidCharacters)
            .joined(separator: "_")
            .trimmingCharacters(in: .whitespacesAndNewlines)

        return sanitized.isEmpty ? "download" : sanitized
    }

    private func uniqueDownloadDestination(for suggestedFilename: String) throws -> URL {
        let downloadsDirectory = FileManager.default.temporaryDirectory.appendingPathComponent("InAppBrowserDownloads", isDirectory: true)
        try FileManager.default.createDirectory(at: downloadsDirectory, withIntermediateDirectories: true)

        let sanitizedFilename = sanitizeDownloadFilename(suggestedFilename)
        let baseName = (sanitizedFilename as NSString).deletingPathExtension
        let fileExtension = (sanitizedFilename as NSString).pathExtension

        var candidateURL = downloadsDirectory.appendingPathComponent(sanitizedFilename)
        var duplicateIndex = 1

        while FileManager.default.fileExists(atPath: candidateURL.path) || !reserveDownloadDestination(candidateURL) {
            let duplicateSuffix = "-\(duplicateIndex)"
            let duplicateFilename = fileExtension.isEmpty
                ? baseName + duplicateSuffix
                : baseName + duplicateSuffix + "." + fileExtension
            candidateURL = downloadsDirectory.appendingPathComponent(duplicateFilename)
            duplicateIndex += 1
        }

        return candidateURL
    }

    private func reserveDownloadDestination(_ candidateURL: URL) -> Bool {
        DownloadReservationStore.lock.lock()
        defer { DownloadReservationStore.lock.unlock() }

        let candidatePath = candidateURL.path
        guard !DownloadReservationStore.paths.contains(candidatePath) else {
            return false
        }
        DownloadReservationStore.paths.insert(candidatePath)
        return true
    }

    private func releaseDownloadDestination(_ destinationURL: URL?) {
        guard let destinationURL else {
            return
        }

        DownloadReservationStore.lock.lock()
        DownloadReservationStore.paths.remove(destinationURL.path)
        DownloadReservationStore.lock.unlock()
    }

    private func normalizedMimeType(_ mimeType: String?, fileURL: URL) -> String? {
        let candidate = mimeType?
            .components(separatedBy: ";")
            .first?
            .trimmingCharacters(in: .whitespacesAndNewlines)
            .lowercased()

        let genericMimeTypes: Set<String> = [
            "application/octet-stream",
            "binary/octet-stream",
            "application/download",
            "application/x-download",
            "application/binary",
            "application/x-binary",
        ]

        if let candidate, !candidate.isEmpty, !genericMimeTypes.contains(candidate) {
            return candidate
        }

        switch fileURL.pathExtension.lowercased() {
        case "pdf":
            return "application/pdf"
        case "json":
            return "application/json"
        case "txt":
            return "text/plain"
        case "csv":
            return "text/csv"
        case "html", "htm":
            return "text/html"
        case "xhtml":
            return "application/xhtml+xml"
        case "png":
            return "image/png"
        case "jpg", "jpeg":
            return "image/jpeg"
        case "gif":
            return "image/gif"
        case "webp":
            return "image/webp"
        case "svg":
            return "image/svg+xml"
        default:
            return nil
        }
    }

    private func shouldPreviewDownloadedFile(_ fileURL: URL, mimeType: String?) -> Bool {
        let activeExtensions: Set<String> = ["html", "htm", "xhtml", "svg"]
        if activeExtensions.contains(fileURL.pathExtension.lowercased()) {
            return false
        }

        guard let normalizedMimeType = normalizedMimeType(mimeType, fileURL: fileURL) else {
            return false
        }

        switch normalizedMimeType {
        case "text/html", "application/xhtml+xml", "image/svg+xml":
            return false
        default:
            break
        }

        return normalizedMimeType == "application/pdf" ||
            normalizedMimeType == "application/json" ||
            normalizedMimeType.starts(with: "text/") ||
            normalizedMimeType.starts(with: "image/")
    }

    private func emitDownloadCompleted(_ fileURL: URL, mimeType: String?, sourceURL: String?, handledBy: String) {
        var data: [String: Any] = [
            "fileName": fileURL.lastPathComponent,
            "path": fileURL.path,
            "localUrl": fileURL.absoluteString,
            "handledBy": handledBy,
        ]
        if let sourceURL {
            data["sourceUrl"] = sourceURL
        }
        if let mimeType {
            data["mimeType"] = mimeType
        }
        emit("downloadCompleted", data: data)
    }

    private func emitDownloadFailed(sourceURL: String?, fileName: String? = nil, mimeType: String? = nil, error: String) {
        var data: [String: Any] = ["error": error]
        if let sourceURL {
            data["sourceUrl"] = sourceURL
        }
        if let fileName {
            data["fileName"] = fileName
        }
        if let mimeType {
            data["mimeType"] = mimeType
        }
        emit("downloadFailed", data: data)
    }

    private func previewDownloadedFile(_ fileURL: URL, mimeType: String?, sourceURL: String?) {
        DispatchQueue.main.async {
            if self.allowsFileURL && self.shouldPreviewDownloadedFile(fileURL, mimeType: mimeType) {
                let accessURL = fileURL.deletingLastPathComponent()
                self.source = .file(fileURL, access: accessURL)
                self.load(file: fileURL, access: accessURL)
                self.emitDownloadCompleted(fileURL, mimeType: mimeType, sourceURL: sourceURL, handledBy: "inAppBrowser")
                return
            }

            self.previewItemURL = fileURL
            let previewController = QLPreviewController()
            previewController.dataSource = self
            previewController.delegate = self
            self.present(previewController, animated: true)
            self.emitDownloadCompleted(fileURL, mimeType: mimeType, sourceURL: sourceURL, handledBy: "systemPreview")
        }
    }

    internal var customUserAgent: String? {
        didSet {
            guard let agent = userAgent else {
                return
            }
            webView?.customUserAgent = agent
        }
    }

    open var userAgent: String? {
        didSet {
            guard let originalUserAgent = originalUserAgent, let userAgent = userAgent else {
                return
            }
            webView?.customUserAgent = [originalUserAgent, userAgent].joined(separator: " ")
        }
    }

    open var pureUserAgent: String? {
        didSet {
            guard let agent = pureUserAgent else {
                return
            }
            webView?.customUserAgent = agent
        }
    }

    open var websiteTitleInNavigationBar = true
    open var doneBarButtonItemPosition: NavigationBarPosition = .right
    open var showArrowAsClose = false
    open var documentStartUserScripts: [String] = []
    open var preShowScript: String?
    open var preShowScriptInjectionTime: String = "pageLoad" // "documentStart" or "pageLoad"
    open var leftNavigationBarItemTypes: [BarButtonItemType] = []
    open var rightNavigaionBarItemTypes: [BarButtonItemType] = []

    // Status bar style to be applied
    open var statusBarStyle: UIStatusBarStyle = .default

    // Status bar background view
    private var statusBarBackgroundView: UIView?

    // Status bar height
    private var statusBarHeight: CGFloat {
        return UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
    }

    // Make status bar background with colored view underneath
    open func setupStatusBarBackground(color: UIColor) {
        // Remove any existing status bar view
        statusBarBackgroundView?.removeFromSuperview()

        // Create a new view to cover both status bar and navigation bar
        statusBarBackgroundView = UIView()

        if let navView = navigationController?.view {
            // Add to back of view hierarchy
            navView.insertSubview(statusBarBackgroundView!, at: 0)
            statusBarBackgroundView?.translatesAutoresizingMaskIntoConstraints = false

            // Calculate total height - status bar + navigation bar
            let navBarHeight = navigationController?.navigationBar.frame.height ?? 44
            let totalHeight = (navigationController?.view.safeAreaInsets.top ?? CGFloat(0)) + navBarHeight

            // Position from top of screen to bottom of navigation bar
            NSLayoutConstraint.activate([
                statusBarBackgroundView!.topAnchor.constraint(equalTo: navView.topAnchor),
                statusBarBackgroundView!.leadingAnchor.constraint(equalTo: navView.leadingAnchor),
                statusBarBackgroundView!.trailingAnchor.constraint(equalTo: navView.trailingAnchor),
                statusBarBackgroundView!.heightAnchor.constraint(equalToConstant: totalHeight)
            ])

            // Set background color
            statusBarBackgroundView?.backgroundColor = color

            // Make navigation bar transparent to show our view underneath
            navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default)
            navigationController?.navigationBar.shadowImage = UIImage()
            navigationController?.navigationBar.isTranslucent = true
            navigationController?.navigationBar.isTranslucent = true
        }
    }

    // Override to use our custom status bar style
    override open var preferredStatusBarStyle: UIStatusBarStyle {
        return statusBarStyle
    }

    // Force status bar style update when needed
    open func updateStatusBarStyle() {
        setNeedsStatusBarAppearanceUpdate()
    }

    open var backBarButtonItemImage: UIImage?
    open var forwardBarButtonItemImage: UIImage?
    open var reloadBarButtonItemImage: UIImage?
    open var stopBarButtonItemImage: UIImage?
    open var activityBarButtonItemImage: UIImage?

    open var buttonNearDoneIcon: UIImage?
    open var showScreenshotButton = false

    fileprivate var webView: WKWebView?
    fileprivate var progressView: UIProgressView?

    fileprivate var previousNavigationBarState: (tintColor: UIColor, hidden: Bool) = (.black, false)
    fileprivate var previousToolbarState: (tintColor: UIColor, hidden: Bool) = (.black, false)

    fileprivate var originalUserAgent: String?

    fileprivate lazy var backBarButtonItem: UIBarButtonItem = {
        let navBackImage = UIImage(systemName: "chevron.backward")?.withRenderingMode(.alwaysTemplate)
        let barButtonItem = UIBarButtonItem(image: navBackImage, style: .plain, target: self, action: #selector(backDidClick(sender:)))
        if let tintColor = self.tintColor ?? self.navigationController?.navigationBar.tintColor {
            barButtonItem.tintColor = tintColor
        }
        return barButtonItem
    }()

    fileprivate lazy var forwardBarButtonItem: UIBarButtonItem = {
        let forwardImage = UIImage(systemName: "chevron.forward")?.withRenderingMode(.alwaysTemplate)
        let barButtonItem = UIBarButtonItem(image: forwardImage, style: .plain, target: self, action: #selector(forwardDidClick(sender:)))
        if let tintColor = self.tintColor ?? self.navigationController?.navigationBar.tintColor {
            barButtonItem.tintColor = tintColor
        }
        return barButtonItem
    }()

    fileprivate lazy var reloadBarButtonItem: UIBarButtonItem = {
        if let image = reloadBarButtonItemImage {
            return UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(reloadDidClick(sender:)))
        } else {
            return UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(reloadDidClick(sender:)))
        }
    }()

    fileprivate lazy var stopBarButtonItem: UIBarButtonItem = {
        if let image = stopBarButtonItemImage {
            return UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(stopDidClick(sender:)))
        } else {
            return UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(stopDidClick(sender:)))
        }
    }()

    fileprivate lazy var activityBarButtonItem: UIBarButtonItem = {
        // Check if custom image is provided
        if let image = activityBarButtonItemImage {
            let button = UIBarButtonItem(image: image.withRenderingMode(.alwaysTemplate),
                                         style: .plain,
                                         target: self,
                                         action: #selector(activityDidClick(sender:)))

            // Apply tint from navigation bar or from tintColor property
            if let tintColor = self.tintColor ?? self.navigationController?.navigationBar.tintColor {
                button.tintColor = tintColor
            }

            print("[DEBUG] Created activity button with custom image")
            return button
        } else {
            // Use system share icon
            let button = UIBarButtonItem(barButtonSystemItem: .action,
                                         target: self,
                                         action: #selector(activityDidClick(sender:)))

            // Apply tint from navigation bar or from tintColor property
            if let tintColor = self.tintColor ?? self.navigationController?.navigationBar.tintColor {
                button.tintColor = tintColor
            }

            print("[DEBUG] Created activity button with system action icon")
            return button
        }
    }()

    fileprivate lazy var doneBarButtonItem: UIBarButtonItem = {
        if showArrowAsClose {
            // Show chevron icon when showArrowAsClose is true (originally was arrow.left)
            let chevronImage = UIImage(systemName: "chevron.left")?.withRenderingMode(.alwaysTemplate)
            let barButtonItem = UIBarButtonItem(image: chevronImage, style: .plain, target: self, action: #selector(doneDidClick(sender:)))
            if let tintColor = self.tintColor ?? self.navigationController?.navigationBar.tintColor {
                barButtonItem.tintColor = tintColor
            }
            return barButtonItem
        } else {
            // Show X icon by default
            let xImage = UIImage(systemName: "xmark")?.withRenderingMode(.alwaysTemplate)
            let barButtonItem = UIBarButtonItem(image: xImage, style: .plain, target: self, action: #selector(doneDidClick(sender:)))
            if let tintColor = self.tintColor ?? self.navigationController?.navigationBar.tintColor {
                barButtonItem.tintColor = tintColor
            }
            return barButtonItem
        }
    }()

    fileprivate lazy var flexibleSpaceBarButtonItem: UIBarButtonItem = {
        return UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
    }()

    fileprivate var credentials: WKWebViewCredentials?

    var textZoom: Int?

    var capableWebView: WKWebView? {
        return webView
    }

    deinit {
        webView?.removeObserver(self, forKeyPath: estimatedProgressKeyPath)
        if websiteTitleInNavigationBar {
            webView?.removeObserver(self, forKeyPath: titleKeyPath)
        }
        webView?.removeObserver(self, forKeyPath: #keyPath(WKWebView.url))
    }

    override open func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)

        if self.isBeingDismissed || self.isMovingFromParent {
            self.cleanupWebView()
        }

        if let capacitorStatusBar = capacitorStatusBar {
            self.capBrowserPlugin?.bridge?.webView?.superview?.addSubview(capacitorStatusBar)
            self.capBrowserPlugin?.bridge?.webView?.frame.origin.y = capacitorStatusBar.frame.height
        }
    }

    override open func viewDidLoad() {
        super.viewDidLoad()
        if self.webView == nil {
            self.initWebview()
        }

        // Apply navigation gestures setting
        updateNavigationGestures()

        // Force all buttons to use tint color
        updateButtonTintColors()

        // Extra call to ensure buttonNearDone is visible
        if buttonNearDoneIcon != nil {
            // Delay slightly to ensure navigation items are set up
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
                self?.updateButtonTintColors()

                // Force update UI if needed
                self?.navigationController?.navigationBar.setNeedsLayout()
            }
        }
    }

    func updateButtonTintColors() {
        // Ensure all button items use the navigation bar's tint color
        if let tintColor = navigationController?.navigationBar.tintColor {
            backBarButtonItem.tintColor = tintColor
            forwardBarButtonItem.tintColor = tintColor
            reloadBarButtonItem.tintColor = tintColor
            stopBarButtonItem.tintColor = tintColor
            activityBarButtonItem.tintColor = tintColor
            doneBarButtonItem.tintColor = tintColor

            // Update navigation items
            if let leftItems = navigationItem.leftBarButtonItems {
                for item in leftItems {
                    item.tintColor = tintColor
                }
            }

            if let rightItems = navigationItem.rightBarButtonItems {
                for item in rightItems {
                    item.tintColor = tintColor
                }
            }

            // Create buttonNearDone button with the correct tint color if it doesn't already exist
            if buttonNearDoneIcon != nil &&
                navigationItem.rightBarButtonItems?.count == 1 &&
                navigationItem.rightBarButtonItems?.first == doneBarButtonItem {

                // Create a properly tinted button
                let buttonItem = UIBarButtonItem(image: buttonNearDoneIcon?.withRenderingMode(.alwaysTemplate),
                                                 style: .plain,
                                                 target: self,
                                                 action: #selector(buttonNearDoneDidClick))
                buttonItem.tintColor = tintColor

                // Add it to right items
                navigationItem.rightBarButtonItems?.append(buttonItem)
            }
        }
    }

    override open func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)

        // Update colors when appearance changes
        if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
            // Update tint colors
            let isDarkMode = traitCollection.userInterfaceStyle == .dark
            let textColor = isDarkMode ? UIColor.white : UIColor.black

            if let navBar = navigationController?.navigationBar {
                if navBar.backgroundColor == UIColor.black || navBar.backgroundColor == UIColor.white {
                    navBar.backgroundColor = isDarkMode ? UIColor.black : UIColor.white
                    navBar.tintColor = textColor
                    navBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: textColor]

                    // Update all buttons
                    updateButtonTintColors()
                }
            }
        }
    }

    open func setCredentials(credentials: WKWebViewCredentials?) {
        self.credentials = credentials
    }

    // Method to send a message from Swift to JavaScript
    open func postMessageToJS(message: [String: Any]) {
        guard let jsonData = try? JSONSerialization.data(withJSONObject: message, options: []),
              let jsonString = String(data: jsonData, encoding: .utf8) else {
            print("[InAppBrowser] Failed to serialize message to JSON")
            return
        }

        // Safely build the script to avoid any potential issues
        let script = "window.dispatchEvent(new CustomEvent('messageFromNative', { detail: \(jsonString) }));"

        DispatchQueue.main.async {
            self.webView?.evaluateJavaScript(script) { _, error in
                if let error = error {
                    print("[InAppBrowser] JavaScript evaluation error in postMessageToJS: \(error)")
                }
            }
        }
    }

    private func payload(with data: [String: Any] = [:]) -> [String: Any] {
        guard !instanceId.isEmpty else {
            return data
        }
        var payload = data
        payload["id"] = instanceId
        return payload
    }

    private func emit(_ eventName: String, data: [String: Any] = [:]) {
        capBrowserPlugin?.notifyListeners(eventName, data: payload(with: data))
    }

    private func jsonString(from object: Any) -> String? {
        guard JSONSerialization.isValidJSONObject(object),
              let data = try? JSONSerialization.data(withJSONObject: object),
              let string = String(data: data, encoding: .utf8) else {
            return nil
        }
        return string
    }

    private func resolveJavaScriptScreenshot(requestId: String, result: [String: Any]) {
        guard let payload = jsonString(from: ["requestId": requestId, "result": result]) else {
            return
        }
        let script = "window.__capgoInAppBrowserResolveScreenshot(\(payload));"
        DispatchQueue.main.async {
            self.webView?.evaluateJavaScript(script, completionHandler: nil)
        }
    }

    private func rejectJavaScriptScreenshot(requestId: String, message: String) {
        guard let payload = jsonString(from: ["requestId": requestId, "message": message]) else {
            return
        }
        let script = "window.__capgoInAppBrowserRejectScreenshot(\(payload));"
        DispatchQueue.main.async {
            self.webView?.evaluateJavaScript(script, completionHandler: nil)
        }
    }

    func takeScreenshot(completion: @escaping (Result<[String: Any], Error>) -> Void) {
        DispatchQueue.main.async {
            guard let webView = self.webView else {
                completion(.failure(NSError(domain: "InAppBrowser", code: 1, userInfo: [NSLocalizedDescriptionKey: "WebView is not initialized"])))
                return
            }

            let targetSize = webView.bounds.size
            guard targetSize.width > 0, targetSize.height > 0 else {
                completion(.failure(NSError(domain: "InAppBrowser", code: 2, userInfo: [NSLocalizedDescriptionKey: "WebView is not ready to capture a screenshot"])))
                return
            }

            let configuration = WKSnapshotConfiguration()
            configuration.rect = webView.bounds
            configuration.afterScreenUpdates = false

            webView.takeSnapshot(with: configuration) { image, error in
                if let error {
                    completion(.failure(error))
                    return
                }

                guard let image,
                      let imageData = image.pngData() else {
                    completion(.failure(NSError(domain: "InAppBrowser", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to encode screenshot"])))
                    return
                }

                let base64 = imageData.base64EncodedString()
                let result: [String: Any] = [
                    "format": "png",
                    "mimeType": "image/png",
                    "base64": base64,
                    "dataUrl": "data:image/png;base64,\(base64)",
                    "width": Int(image.size.width * image.scale),
                    "height": Int(image.size.height * image.scale)
                ]
                self.emit("screenshotTaken", data: result)
                completion(.success(result))
            }
        }
    }

    // Method to receive messages from JavaScript
    public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "messageHandler" {
            if let messageBody = message.body as? [String: Any] {
                print("Received message from JavaScript:", messageBody)
                emit("messageFromWebview", data: messageBody)
            } else {
                print("Received non-dictionary message from JavaScript:", message.body)
                emit("messageFromWebview", data: ["rawMessage": String(describing: message.body)])
            }
        } else if message.name == "consoleMessageHandler" {
            if let messageBody = message.body as? [String: Any] {
                emit("consoleMessage", data: ConsoleMessageSupport.normalizePayload(from: messageBody))
            } else {
                emit(
                    "consoleMessage",
                    data: ConsoleMessageSupport.normalizePayload(
                        from: [
                            "level": "log",
                            "message": String(describing: message.body)
                        ]
                    )
                )
            }
        } else if message.name == "preShowScriptSuccess" {
            guard let semaphore = preShowSemaphore else {
                print("[InAppBrowser - preShowScriptSuccess]: Semaphore not found")
                return
            }

            semaphore.signal()
        } else if message.name == "preShowScriptError" {
            guard let semaphore = preShowSemaphore else {
                print("[InAppBrowser - preShowScriptError]: Semaphore not found")
                return
            }
            print("[InAppBrowser - preShowScriptError]: Error!!!!")
            semaphore.signal()
        } else if message.name == "close" {
            closeView()
        } else if message.name == "hide" {
            guard allowWebViewJsVisibilityControl else {
                return
            }
            capBrowserPlugin?.setHiddenFromJavaScript(true, sourceId: instanceId)
        } else if message.name == "show" {
            guard allowWebViewJsVisibilityControl else {
                return
            }
            capBrowserPlugin?.setHiddenFromJavaScript(false, sourceId: instanceId)
        } else if message.name == "takeScreenshot" {
            guard let body = message.body as? [String: Any],
                  let requestId = body["requestId"] as? String,
                  !requestId.isEmpty else {
                return
            }

            guard allowScreenshotsFromWebPage else {
                self.rejectJavaScriptScreenshot(requestId: requestId, message: "Screenshot bridge is not enabled for this page")
                return
            }

            takeScreenshot { result in
                switch result {
                case .success(let screenshot):
                    self.resolveJavaScriptScreenshot(requestId: requestId, result: screenshot)
                case .failure(let error):
                    self.rejectJavaScriptScreenshot(requestId: requestId, message: error.localizedDescription)
                }
            }
        } else if message.name == "magicPrint" {
            if let webView = self.webView {
                let printController = UIPrintInteractionController.shared

                let printInfo = UIPrintInfo(dictionary: nil)
                printInfo.outputType = .general
                printInfo.jobName = "Print Job"

                printController.printInfo = printInfo
                printController.printFormatter = webView.viewPrintFormatter()

                printController.present(animated: true, completionHandler: nil)
            }
        }
    }

    private func mobileAppScriptSource() -> String {
        let screenshotControls = allowScreenshotsFromWebPage ? """
                                ,
                                takeScreenshot: function() {
                                        return new Promise(function(resolve, reject) {
                                                if (!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.takeScreenshot)) {
                                                        reject(new Error('Screenshot bridge is not available'));
                                                        return;
                                                }
                                                var requestId = 'screenshot_' + Date.now() + '_' + Math.random().toString(36).slice(2);
                                                window.__capgoInAppBrowserPendingScreenshots[requestId] = { resolve: resolve, reject: reject };
                                                window.webkit.messageHandlers.takeScreenshot.postMessage({ requestId: requestId });
                                        });
                                }
                """ : ""
        let extraControls = allowWebViewJsVisibilityControl ? """
                                ,
                                hide: function() {
                                        window.webkit.messageHandlers.hide.postMessage(null);
                                },
                                show: function() {
                                        window.webkit.messageHandlers.show.postMessage(null);
                                }
                """ : ""
        return """
                window.__capgoInAppBrowserPendingScreenshots = window.__capgoInAppBrowserPendingScreenshots || {};
                window.__capgoInAppBrowserResolveScreenshot = window.__capgoInAppBrowserResolveScreenshot || function(payload) {
                        var pending = window.__capgoInAppBrowserPendingScreenshots[payload.requestId];
                        if (!pending) {
                                return;
                        }
                        delete window.__capgoInAppBrowserPendingScreenshots[payload.requestId];
                        pending.resolve(payload.result);
                };
                window.__capgoInAppBrowserRejectScreenshot = window.__capgoInAppBrowserRejectScreenshot || function(payload) {
                        var pending = window.__capgoInAppBrowserPendingScreenshots[payload.requestId];
                        if (!pending) {
                                return;
                        }
                        delete window.__capgoInAppBrowserPendingScreenshots[payload.requestId];
                        pending.reject(new Error(payload.message));
                };
                window.mobileApp = Object.assign({}, window.mobileApp || {}, {
                        postMessage: function(message) {
                                if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.messageHandler) {
                                        window.webkit.messageHandlers.messageHandler.postMessage(message);
                                }
                        },
                        close: function() {
                                window.webkit.messageHandlers.close.postMessage(null);
                        }\(extraControls)\(screenshotControls)
                });
                """
    }

    private func addMobileAppUserScript(to userContentController: WKUserContentController) {
        let script = WKUserScript(
            source: mobileAppScriptSource(),
            injectionTime: .atDocumentStart,
            forMainFrameOnly: true
        )
        userContentController.addUserScript(script)
    }

    private func addConsoleCaptureUserScript(to userContentController: WKUserContentController) {
        let script = WKUserScript(
            source: ConsoleMessageSupport.captureScriptSource(),
            injectionTime: .atDocumentStart,
            forMainFrameOnly: false
        )
        userContentController.addUserScript(script)
    }

    func injectJavaScriptInterface() {
        let script = mobileAppScriptSource()
        DispatchQueue.main.async {
            self.webView?.evaluateJavaScript(script) { result, error in
                if let error = error {
                    print("JavaScript evaluation error: \(error)")
                } else if let result = result {
                    print("JavaScript result: \(result)")
                } else {
                    print("JavaScript executed with no result")
                }
            }
        }
    }

    open func initWebview(isInspectable: Bool = true) {
        if self.isWebViewInitialized {
            return
        }
        self.isWebViewInitialized = true
        self.view.backgroundColor = UIColor.white

        self.extendedLayoutIncludesOpaqueBars = true
        self.edgesForExtendedLayout = [.bottom]

        let webConfiguration = initialWebConfiguration ?? WKWebViewConfiguration()
        let userContentController = webConfiguration.userContentController
        userContentController.removeAllUserScripts()
        userContentController.removeScriptMessageHandler(forName: "messageHandler")
        userContentController.removeScriptMessageHandler(forName: "preShowScriptError")
        userContentController.removeScriptMessageHandler(forName: "preShowScriptSuccess")
        userContentController.removeScriptMessageHandler(forName: "close")
        userContentController.removeScriptMessageHandler(forName: "hide")
        userContentController.removeScriptMessageHandler(forName: "show")
        userContentController.removeScriptMessageHandler(forName: "takeScreenshot")
        userContentController.removeScriptMessageHandler(forName: "consoleMessageHandler")
        userContentController.removeScriptMessageHandler(forName: "magicPrint")

        if proxyRequests || proxySchemeHandler != nil, let handler = proxySchemeHandler {
            WKWebView.enableCustomSchemeHandling(for: ["https", "http"])
            if !WKWebView.handlesURLScheme("https") && !WKWebView.handlesURLScheme("http") {
                webConfiguration.setURLSchemeHandler(handler, forURLScheme: "https")
                webConfiguration.setURLSchemeHandler(handler, forURLScheme: "http")
            } else {
                print("[InAppBrowser][Proxy] WARNING: handlesURLScheme swizzle failed; proxy scheme handler not registered")
            }
        }

        let weakHandler = WeakScriptMessageHandler(self)
        userContentController.add(weakHandler, name: "messageHandler")
        userContentController.add(weakHandler, name: "preShowScriptError")
        userContentController.add(weakHandler, name: "preShowScriptSuccess")
        userContentController.add(weakHandler, name: "close")
        userContentController.add(weakHandler, name: "hide")
        userContentController.add(weakHandler, name: "show")
        if allowScreenshotsFromWebPage {
            userContentController.add(weakHandler, name: "takeScreenshot")
        }
        if captureConsoleLogs {
            userContentController.add(weakHandler, name: "consoleMessageHandler")
            addConsoleCaptureUserScript(to: userContentController)
        }
        if let preShowScript = self.preShowScript,
           !preShowScript.isEmpty,
           preShowScriptInjectionTime == "documentStart" {
            userContentController.addUserScript(
                WKUserScript(
                    source: preShowScript,
                    injectionTime: .atDocumentStart,
                    forMainFrameOnly: false
                )
            )
            print("[InAppBrowser] Injected preShowScript at document start")
        }
        for scriptSource in documentStartUserScripts where !scriptSource.isEmpty {
            userContentController.addUserScript(
                WKUserScript(
                    source: scriptSource,
                    injectionTime: .atDocumentStart,
                    forMainFrameOnly: false
                )
            )
        }
        userContentController.add(weakHandler, name: "magicPrint")

        // Inject JavaScript to override window.print
        let script = WKUserScript(
            source: """
            window.print = function() {
                window.webkit.messageHandlers.magicPrint.postMessage('magicPrint');
            };
            """,
            injectionTime: .atDocumentStart,
            forMainFrameOnly: false
        )
        userContentController.addUserScript(script)
        addMobileAppUserScript(to: userContentController)

        webConfiguration.allowsInlineMediaPlayback = true
        webConfiguration.userContentController = userContentController
        // Enable background task processing
        if initialWebConfiguration == nil {
            webConfiguration.processPool = WKProcessPool()
        }

        // Enable JavaScript to run automatically (needed for preShowScript and Firebase polyfill)
        webConfiguration.preferences.javaScriptCanOpenWindowsAutomatically = true

        // Enhanced configuration for Google Pay support (only when enabled)
        if enableGooglePaySupport {
            print("[InAppBrowser] Enabling Google Pay support features for iOS")

            // Inject Google Pay support script
            let googlePayScript = WKUserScript(
                source: """
                console.log('[InAppBrowser] Injecting Google Pay support for iOS');

                // Enhanced window.open for Google Pay
                (function() {
                    const originalWindowOpen = window.open;
                    window.open = function(url, target, features) {
                        console.log('[InAppBrowser iOS] Enhanced window.open called:', url, target, features);

                        // For Google Pay URLs, handle popup properly
                        if (url && (url.includes('google.com/pay') || url.includes('accounts.google.com'))) {
                            console.log('[InAppBrowser iOS] Google Pay popup detected');
                            return originalWindowOpen.call(window, url, target || '_blank', features);
                        }

                        return originalWindowOpen.call(window, url, target, features);
                    };

                    // Add Cross-Origin-Opener-Policy meta tag if not present
                    if (!document.querySelector('meta[http-equiv="Cross-Origin-Opener-Policy"]')) {
                        const meta = document.createElement('meta');
                        meta.setAttribute('http-equiv', 'Cross-Origin-Opener-Policy');
                        meta.setAttribute('content', 'same-origin-allow-popups');
                        if (document.head) {
                            document.head.appendChild(meta);
                            console.log('[InAppBrowser iOS] Added Cross-Origin-Opener-Policy meta tag');
                        }
                    }

                    console.log('[InAppBrowser iOS] Google Pay support enhancements complete');
                })();
                """,
                injectionTime: .atDocumentStart,
                forMainFrameOnly: false
            )
            userContentController.addUserScript(googlePayScript)
        }

        let webView = WKWebView(frame: .zero, configuration: webConfiguration)

        //        if webView.responds(to: Selector(("setInspectable:"))) {
        //            // Fix: https://stackoverflow.com/questions/76216183/how-to-debug-wkwebview-in-ios-16-4-1-using-xcode-14-2/76603043#76603043
        //            webView.perform(Selector(("setInspectable:")), with: isInspectable)
        //        }

        if #available(iOS 16.4, *) {
            webView.isInspectable = true
        } else {
            // Fallback on earlier versions
        }

        // First add the webView to view hierarchy
        self.view.addSubview(webView)

        // Then set up constraints
        webView.translatesAutoresizingMaskIntoConstraints = false
        var bottomAnchor = self.view.bottomAnchor
        var topAnchor = self.view.safeAreaLayoutGuide.topAnchor

        if self.enabledSafeBottomMargin {
            bottomAnchor = self.view.safeAreaLayoutGuide.bottomAnchor
        }

        if !self.enabledSafeTopMargin {
            topAnchor = self.view.topAnchor
        }

        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: topAnchor),
            webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            webView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])

        webView.uiDelegate = self
        webView.navigationDelegate = self

        webView.allowsBackForwardNavigationGestures = self.activeNativeNavigationForWebview
        webView.isMultipleTouchEnabled = true

        // Disable bounce effect by setting scrollView.bounces to false when disableOverscroll is true
        webView.scrollView.bounces = !self.disableOverscroll

        webView.addObserver(self, forKeyPath: estimatedProgressKeyPath, options: .new, context: nil)
        if websiteTitleInNavigationBar {
            webView.addObserver(self, forKeyPath: titleKeyPath, options: .new, context: nil)
        }
        webView.addObserver(self, forKeyPath: #keyPath(WKWebView.url), options: .new, context: nil)

        if !self.blankNavigationTab {
            self.view.addSubview(webView)
            // Then set up constraints
            webView.translatesAutoresizingMaskIntoConstraints = false
        }
        self.webView = webView

        self.webView?.customUserAgent = self.customUserAgent ?? self.userAgent ?? self.originalUserAgent

        self.navigationItem.title = self.navigationItem.title ?? self.source?.absoluteString

        if let navigation = self.navigationController {
            self.previousNavigationBarState = (navigation.navigationBar.tintColor, navigation.navigationBar.isHidden)
            self.previousToolbarState = (navigation.toolbar.tintColor, navigation.toolbar.isHidden)
        }

        if let sourceValue = self.source, !waitsForPopupNavigation {
            self.load(source: sourceValue)
        } else if self.source == nil {
            print("[\(type(of: self))][Error] Invalid url")
        }
    }

    open func setupViewElements() {
        self.setUpProgressView()
        self.setUpConstraints()
        self.setUpNavigationBarAppearance()
        self.addBarButtonItems()
        self.updateBarButtonItems()
    }

    @discardableResult
    func inheritPopupPresentation(
        from parent: WKWebViewController,
        request: URLRequest,
        configuration: WKWebViewConfiguration,
        instanceId: String,
        proxySchemeHandler: ProxySchemeHandler?
    ) -> WKWebView? {
        self.instanceId = instanceId
        self.source = request.url.map { .remote($0) }
        self.url = request.url
        self.credentials = parent.credentials
        self.headers = parent.headers
        self.customUserAgent = parent.customUserAgent
        self.userAgent = parent.userAgent
        self.pureUserAgent = parent.pureUserAgent
        self.capBrowserPlugin = parent.capBrowserPlugin
        self.shareDisclaimer = parent.shareDisclaimer
        self.shareSubject = parent.shareSubject
        self.closeModal = parent.closeModal
        self.closeModalTitle = parent.closeModalTitle
        self.closeModalDescription = parent.closeModalDescription
        self.closeModalOk = parent.closeModalOk
        self.closeModalCancel = parent.closeModalCancel
        self.closeModalURLPattern = parent.closeModalURLPattern
        self.ignoreUntrustedSSLError = parent.ignoreUntrustedSSLError
        self.enableGooglePaySupport = parent.enableGooglePaySupport
        self.preventDeeplink = parent.preventDeeplink
        self.openBlankTargetInWebView = parent.openBlankTargetInWebView
        self.blankNavigationTab = false
        self.enabledSafeBottomMargin = parent.enabledSafeBottomMargin
        self.enabledSafeTopMargin = parent.enabledSafeTopMargin
        self.blockedHosts = parent.blockedHosts
        self.authorizedAppLinks = parent.authorizedAppLinks
        self.activeNativeNavigationForWebview = parent.activeNativeNavigationForWebview
        self.disableOverscroll = parent.disableOverscroll
        self.proxyRequests = parent.proxyRequests
        self.proxySchemeHandler = proxySchemeHandler
        self.initialWebConfiguration = configuration
        self.waitsForPopupNavigation = true
        self.hiddenPopupWindow = parent.hiddenPopupWindow
        self.opensHidden = parent.hiddenPopupWindow
        self.captureConsoleLogs = parent.captureConsoleLogs
        self.allowWebViewJsVisibilityControl = parent.allowWebViewJsVisibilityControl
        self.allowScreenshotsFromWebPage = parent.allowScreenshotsFromWebPage
        self.handleDownloads = parent.handleDownloads
        self.websiteTitleInNavigationBar = parent.websiteTitleInNavigationBar
        self.doneBarButtonItemPosition = parent.doneBarButtonItemPosition
        self.showArrowAsClose = parent.showArrowAsClose
        self.documentStartUserScripts = parent.documentStartUserScripts
        self.preShowScript = parent.preShowScript
        self.preShowScriptInjectionTime = parent.preShowScriptInjectionTime
        self.leftNavigationBarItemTypes = parent.leftNavigationBarItemTypes
        self.rightNavigaionBarItemTypes = parent.rightNavigaionBarItemTypes
        self.statusBarStyle = parent.statusBarStyle
        self.tintColor = parent.tintColor
        self.backBarButtonItemImage = parent.backBarButtonItemImage
        self.forwardBarButtonItemImage = parent.forwardBarButtonItemImage
        self.reloadBarButtonItemImage = parent.reloadBarButtonItemImage
        self.stopBarButtonItemImage = parent.stopBarButtonItemImage
        self.activityBarButtonItemImage = parent.activityBarButtonItemImage
        self.buttonNearDoneIcon = parent.buttonNearDoneIcon
        self.showScreenshotButton = parent.showScreenshotButton
        self.textZoom = parent.textZoom
        self.customWidth = parent.customWidth
        self.customHeight = parent.customHeight
        self.customX = parent.customX
        self.customY = parent.customY
        self.view.backgroundColor = parent.view.backgroundColor
        self.title = parent.title ?? request.url?.host ?? "Popup Window"
        self.navigationItem.title = self.title
        self.initWebview()
        return self.capableWebView
    }

    @objc func restateViewHeight() {
        var bottomPadding = CGFloat(0.0)
        var topPadding = CGFloat(0.0)
        let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow })
        bottomPadding = window?.safeAreaInsets.bottom ?? 0.0
        topPadding = window?.safeAreaInsets.top ?? 0.0
        if UIDevice.current.orientation.isPortrait {
            // Don't force toolbar visibility
            if self.viewHeightPortrait == nil {
                self.viewHeightPortrait = self.view.safeAreaLayoutGuide.layoutFrame.size.height
                self.viewHeightPortrait! += bottomPadding
                if self.navigationController?.navigationBar.isHidden == true {
                    self.viewHeightPortrait! += topPadding
                }
            }
            self.currentViewHeight = self.viewHeightPortrait
        } else if UIDevice.current.orientation.isLandscape {
            // Don't force toolbar visibility
            if self.viewHeightLandscape == nil {
                self.viewHeightLandscape = self.view.safeAreaLayoutGuide.layoutFrame.size.height
                self.viewHeightLandscape! += bottomPadding
                if self.navigationController?.navigationBar.isHidden == true {
                    self.viewHeightLandscape! += topPadding
                }
            }
            self.currentViewHeight = self.viewHeightLandscape
        }
    }

    override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        //        self.view.frame.size.height = self.currentViewHeight!
    }

    override open func viewWillLayoutSubviews() {
        restateViewHeight()
        // Don't override frame height when enabledSafeBottomMargin is true, as it would override our constraints
        if self.currentViewHeight != nil && !self.enabledSafeBottomMargin {
            self.view.frame.size.height = self.currentViewHeight!
        }
    }

    override open func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if !self.viewWasPresented {
            self.setupViewElements()
            setUpState()
            self.viewWasPresented = true

            // Apply custom dimensions if specified
            applyCustomDimensions()
        }

        // Force update button appearances
        updateButtonTintColors()

        // Ensure status bar appearance is correct when view appears
        // Make sure we have the latest tint color
        if let tintColor = self.tintColor {
            // Update the status bar background if needed
            if let navController = navigationController, let backgroundColor = navController.navigationBar.backgroundColor ?? statusBarBackgroundView?.backgroundColor {
                setupStatusBarBackground(color: backgroundColor)
            } else {
                setupStatusBarBackground(color: UIColor.white)
            }
        }

        // Update status bar style
        updateStatusBarStyle()

        // Special handling for blank toolbar mode
        if blankNavigationTab && statusBarBackgroundView != nil {
            if let color = statusBarBackgroundView?.backgroundColor {
                // Set view color to match status bar
                view.backgroundColor = color
            }
        }
    }

    override open func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // Force add buttonNearDone if it's not visible yet
        if buttonNearDoneIcon != nil {
            // Check if button already exists in the navigation bar
            let buttonExists = navigationItem.rightBarButtonItems?.contains { item in
                return item.action == #selector(buttonNearDoneDidClick)
            } ?? false

            if !buttonExists {
                // Create and add the button directly
                let buttonItem = UIBarButtonItem(
                    image: buttonNearDoneIcon?.withRenderingMode(.alwaysTemplate),
                    style: .plain,
                    target: self,
                    action: #selector(buttonNearDoneDidClick)
                )

                // Apply tint color
                if let tintColor = self.tintColor ?? self.navigationController?.navigationBar.tintColor {
                    buttonItem.tintColor = tintColor
                }

                // Add to right items
                if navigationItem.rightBarButtonItems == nil {
                    navigationItem.rightBarButtonItems = [buttonItem]
                } else {
                    var items = navigationItem.rightBarButtonItems ?? []
                    items.append(buttonItem)
                    navigationItem.rightBarButtonItems = items
                }

                print("[DEBUG] Force added buttonNearDone in viewDidAppear")
            }
        }
    }

    override open func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        rollbackState()
    }

    override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        switch keyPath {
        case estimatedProgressKeyPath?:
            DispatchQueue.main.async {
                guard let estimatedProgress = self.webView?.estimatedProgress else {
                    return
                }
                self.progressView?.alpha = 1
                self.progressView?.setProgress(Float(estimatedProgress), animated: true)

                if estimatedProgress >= 1.0 {
                    UIView.animate(withDuration: 0.3, delay: 0.3, options: .curveEaseOut, animations: {
                        self.progressView?.alpha = 0
                    }, completion: {
                        _ in
                        self.progressView?.setProgress(0, animated: false)
                    })
                }
            }
        case titleKeyPath?:
            if self.hasDynamicTitle {
                self.navigationItem.title = webView?.url?.host
            }
        case "URL":
            // Guard against notifications during cleanup when webView is being torn down
            guard self.webView != nil else { return }
            emit("urlChangeEvent", data: ["url": webView?.url?.absoluteString ?? ""])
            DispatchQueue.main.async { [weak self] in
                self?.updateBarButtonItems()
            }
            self.injectJavaScriptInterface()
        default:
            super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        }
    }
}

// MARK: - Public Methods
public extension WKWebViewController {

    func load(source sourceValue: WKWebSource) {
        switch sourceValue {
        case .remote(let url):
            self.load(remote: url)
        case .file(let url, access: let access):
            self.load(file: url, access: access)
        case .string(let str, base: let base):
            self.load(string: str, base: base)
        }
    }

    func load(remote: URL) {
        DispatchQueue.main.async {
            self.webView?.load(self.createRequest(url: remote))
        }
    }

    func load(file: URL, access: URL) {
        webView?.loadFileURL(file, allowingReadAccessTo: access)
    }

    func load(string: String, base: URL? = nil) {
        webView?.loadHTMLString(string, baseURL: base)
    }

    func goBackToFirstPage() {
        if let firstPageItem = webView?.backForwardList.backList.first {
            webView?.go(to: firstPageItem)
        }
    }
    func reload() {
        webView?.reload()
    }

    func websiteDataStore() -> WKWebsiteDataStore? {
        return webView?.configuration.websiteDataStore
    }

    func executeScript(script: String, completion: ((Any?, Error?) -> Void)? = nil) {
        DispatchQueue.main.async { [weak self] in
            self?.webView?.evaluateJavaScript(script, completionHandler: completion)
        }
    }

    func applyTextZoom(_ zoomPercent: Int) {
        let script = """
        document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust = '\(zoomPercent)%';
        document.getElementsByTagName('body')[0].style.textSizeAdjust = '\(zoomPercent)%';
        """

        executeScript(script: script)
    }

    func injectPreShowScriptAtDocumentStart() {
        guard let preShowScript = self.preShowScript,
              !preShowScript.isEmpty,
              self.preShowScriptInjectionTime == "documentStart",
              let webView = self.webView else {
            return
        }

        let userScript = WKUserScript(
            source: preShowScript,
            injectionTime: .atDocumentStart,
            forMainFrameOnly: false
        )
        webView.configuration.userContentController.addUserScript(userScript)
        print("[InAppBrowser] Injected preShowScript at document start")

        // Reload the webview so the script executes at document start
        if let currentURL = webView.url {
            load(remote: currentURL)
        } else if let source = self.source {
            load(source: source)
        }
    }

    func updateNavigationGestures() {
        self.webView?.allowsBackForwardNavigationGestures = self.activeNativeNavigationForWebview
    }

    open func cleanupWebView() {
        guard let webView = self.webView else { return }
        webView.stopLoading()
        previewItemURL = nil

        // Remove KVO observers FIRST, before any operation that could trigger them
        webView.removeObserver(self, forKeyPath: estimatedProgressKeyPath)
        if websiteTitleInNavigationBar {
            webView.removeObserver(self, forKeyPath: titleKeyPath)
        }
        webView.removeObserver(self, forKeyPath: #keyPath(WKWebView.url))

        // Now safe to tear down
        webView.navigationDelegate = nil
        webView.uiDelegate = nil
        webView.loadHTMLString("", baseURL: nil)

        webView.configuration.userContentController.removeAllUserScripts()
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "messageHandler")
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "close")
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "hide")
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "show")
        if allowScreenshotsFromWebPage {
            webView.configuration.userContentController.removeScriptMessageHandler(forName: "takeScreenshot")
        }
        if captureConsoleLogs {
            webView.configuration.userContentController.removeScriptMessageHandler(forName: "consoleMessageHandler")
        }
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "preShowScriptSuccess")
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "preShowScriptError")
        webView.configuration.userContentController.removeScriptMessageHandler(forName: "magicPrint")

        webView.removeFromSuperview()
        // Also clean progress bar view if present
        progressView?.removeFromSuperview()
        progressView = nil
        self.webView = nil
    }
}

// MARK: - Fileprivate Methods
fileprivate extension WKWebViewController {
    var availableCookies: [HTTPCookie]? {
        return cookies?.filter {
            cookie in
            var result = true
            let url = self.source?.remoteURL
            if let host = url?.host, !cookie.domain.hasSuffix(host) {
                result = false
            }
            if cookie.isSecure && url?.scheme != "https" {
                result = false
            }

            return result
        }
    }
    func createRequest(url: URL) -> URLRequest {
        var request = URLRequest(url: url)

        // Set up HTTP method if specified
        if let method = httpMethod {
            request.httpMethod = method.uppercased()
        }

        // Set up HTTP body if specified
        if let body = httpBody, let data = body.data(using: .utf8) {
            request.httpBody = data
        }

        // Set up headers
        if let headers = headers {
            for (field, value) in headers {
                request.addValue(value, forHTTPHeaderField: field)
            }
        }

        // Set up Cookies
        if let cookies = availableCookies, let value = HTTPCookie.requestHeaderFields(with: cookies)[cookieKey] {
            request.addValue(value, forHTTPHeaderField: cookieKey)
        }

        return request
    }

    func setUpProgressView() {
        let progressView = UIProgressView(progressViewStyle: .default)
        progressView.trackTintColor = UIColor(white: 1, alpha: 0)
        self.progressView = progressView
        //        updateProgressViewFrame()
    }

    func setUpConstraints() {
        if !(self.navigationController?.navigationBar.isHidden)! {
            self.progressView?.frame.origin.y = CGFloat((self.navigationController?.navigationBar.frame.height)!)
            self.navigationController?.navigationBar.addSubview(self.progressView!)
        }
    }

    func addBarButtonItems() {
        func barButtonItem(_ type: BarButtonItemType) -> UIBarButtonItem? {
            switch type {
            case .back:
                return backBarButtonItem
            case .forward:
                return forwardBarButtonItem
            case .reload:
                return reloadBarButtonItem
            case .stop:
                return stopBarButtonItem
            case .activity:
                return activityBarButtonItem
            case .done:
                return doneBarButtonItem
            case .flexibleSpace:
                return flexibleSpaceBarButtonItem
            case .custom(let icon, let title, let action):
                let item: BlockBarButtonItem
                if let icon = icon {
                    item = BlockBarButtonItem(image: icon, style: .plain, target: self, action: #selector(customDidClick(sender:)))
                } else {
                    item = BlockBarButtonItem(title: title, style: .plain, target: self, action: #selector(customDidClick(sender:)))
                }
                item.block = action
                return item
            }
        }

        switch doneBarButtonItemPosition {
        case .left:
            if !leftNavigationBarItemTypes.contains(where: { type in
                switch type {
                case .done:
                    return true
                default:
                    return false
                }
            }) {
                leftNavigationBarItemTypes.insert(.done, at: 0)
            }
        case .right:
            if !rightNavigaionBarItemTypes.contains(where: { type in
                switch type {
                case .done:
                    return true
                default:
                    return false
                }
            }) {
                rightNavigaionBarItemTypes.insert(.done, at: 0)
            }
        case .none:
            break
        }

        navigationItem.leftBarButtonItems = leftNavigationBarItemTypes.map {
            barButtonItemType in
            if let barButtonItem = barButtonItem(barButtonItemType) {
                return barButtonItem
            }
            return UIBarButtonItem()
        }

        var rightBarButtons = rightNavigaionBarItemTypes.map {
            barButtonItemType in
            if let barButtonItem = barButtonItem(barButtonItemType) {
                return barButtonItem
            }
            return UIBarButtonItem()
        }

        // If we have buttonNearDoneIcon and the first (or only) right button is the done button
        if buttonNearDoneIcon != nil &&
            ((rightBarButtons.count == 1 && rightBarButtons[0] == doneBarButtonItem) ||
                (rightBarButtons.isEmpty && doneBarButtonItemPosition == .right) ||
                rightBarButtons.contains(doneBarButtonItem)) {

            // Check if button already exists to avoid duplicates
            let buttonExists = rightBarButtons.contains { item in
                let selector = #selector(buttonNearDoneDidClick)
                return item.action == selector
            }

            if !buttonExists {
                // Create button with proper tint and template rendering mode
                let buttonItem = UIBarButtonItem(
                    image: buttonNearDoneIcon?.withRenderingMode(.alwaysTemplate),
                    style: .plain,
                    target: self,
                    action: #selector(buttonNearDoneDidClick)
                )

                // Apply tint from navigation bar or from tintColor property
                if let tintColor = self.tintColor ?? self.navigationController?.navigationBar.tintColor {
                    buttonItem.tintColor = tintColor
                }

                // Make sure the done button is there before adding this one
                if rightBarButtons.isEmpty && doneBarButtonItemPosition == .right {
                    rightBarButtons.append(doneBarButtonItem)
                }

                // Add the button
                rightBarButtons.append(buttonItem)

                print("[DEBUG] Added buttonNearDone to right bar buttons, icon: \(String(describing: buttonNearDoneIcon))")
            } else {
                print("[DEBUG] buttonNearDone already exists in right bar buttons")
            }
        }

        navigationItem.rightBarButtonItems = rightBarButtons

        // After all buttons are set up, apply tint color
        updateButtonTintColors()
    }

    func updateBarButtonItems() {
        // Update navigation buttons (completely separate from close button)
        backBarButtonItem.isEnabled = webView?.canGoBack ?? false
        forwardBarButtonItem.isEnabled = webView?.canGoForward ?? false

        let updateReloadBarButtonItem: (UIBarButtonItem, Bool) -> UIBarButtonItem = {
            [weak self] barButtonItem, isLoading in
            guard let self = self else { return barButtonItem }
            switch barButtonItem {
            case self.reloadBarButtonItem, self.stopBarButtonItem:
                return isLoading ? self.stopBarButtonItem : self.reloadBarButtonItem
            default:
                return barButtonItem
            }
        }

        let isLoading = webView?.isLoading ?? false
        navigationItem.leftBarButtonItems = navigationItem.leftBarButtonItems?.map {
            barButtonItem -> UIBarButtonItem in
            return updateReloadBarButtonItem(barButtonItem, isLoading)
        }

        navigationItem.rightBarButtonItems = navigationItem.rightBarButtonItems?.map {
            barButtonItem -> UIBarButtonItem in
            return updateReloadBarButtonItem(barButtonItem, isLoading)
        }
    }

    func setUpState() {
        navigationController?.setNavigationBarHidden(false, animated: true)

        // Always hide toolbar since we never want it
        navigationController?.setToolbarHidden(true, animated: true)

        // Set tint colors but don't override specific colors
        if tintColor == nil {
            // Use system appearance if no specific tint color is set
            let isDarkMode = traitCollection.userInterfaceStyle == .dark
            let textColor = isDarkMode ? UIColor.white : UIColor.black

            navigationController?.navigationBar.tintColor = textColor
            progressView?.progressTintColor = textColor
        } else {
            progressView?.progressTintColor = tintColor
            navigationController?.navigationBar.tintColor = tintColor
        }
    }

    func rollbackState() {
        progressView?.progress = 0

        navigationController?.navigationBar.tintColor = previousNavigationBarState.tintColor

        navigationController?.setNavigationBarHidden(previousNavigationBarState.hidden, animated: true)
    }

    func checkRequestCookies(_ request: URLRequest, cookies: [HTTPCookie]) -> Bool {
        if cookies.count <= 0 {
            return true
        }
        guard let headerFields = request.allHTTPHeaderFields, let cookieString = headerFields[cookieKey] else {
            return false
        }

        let requestCookies = cookieString.components(separatedBy: ";").map {
            $0.trimmingCharacters(in: .whitespacesAndNewlines).split(separator: "=", maxSplits: 1).map(String.init)
        }

        var valid = false
        for cookie in cookies {
            valid = requestCookies.filter {
                $0[0] == cookie.name && $0[1] == cookie.value
            }.count > 0
            if !valid {
                break
            }
        }
        return valid
    }

    private func tryOpenCustomScheme(_ url: URL) -> Bool {
        let app = UIApplication.shared

        if app.canOpenURL(url) {
            app.open(url, options: [:], completionHandler: nil)
            return true // external app opened -> cancel WebView load
        }

        // Cannot open scheme: notify and still block WebView (avoid rendering garbage / errors)
        emit("pageLoadError")
        return true
    }

    private func tryOpenUniversalLink(_ url: URL, completion: @escaping (Bool) -> Void) {
        // Only for http(s):// and authorized hosts
        UIApplication.shared.open(url, options: [.universalLinksOnly: true]) { opened in
            completion(opened) // true => app opened, false => no associated app
        }
    }

    func openURLWithApp(_ url: URL) -> Bool {
        let application = UIApplication.shared
        if application.canOpenURL(url) {
            application.open(url, options: [:], completionHandler: nil)
            return true
        }

        return false
    }

    private func normalizeHost(_ host: String?) -> String? {
        guard var hostValue = host?.lowercased() else { return nil }
        if hostValue.hasPrefix("www.") { hostValue.removeFirst(4) }
        return hostValue
    }

    func isUrlAuthorized(_ url: URL, authorizedLinks: [String]) -> Bool {
        guard !authorizedLinks.isEmpty else { return false }

        let urlHostNorm = normalizeHost(url.host)
        for auth in authorizedLinks {
            guard let comp = URLComponents(string: auth) else { continue }
            let authHostNorm = normalizeHost(comp.host)
            if urlHostNorm == authHostNorm {
                return true
            }
        }

        return false
    }

    private func isHttpOrHttps(_ url: URL) -> Bool {
        guard let scheme = url.scheme?.lowercased() else {
            return false
        }
        return scheme == "http" || scheme == "https"
    }

    private func shouldLoadBlankTargetInCurrentWebView(_ url: URL, targetFrame: WKFrameInfo?) -> Bool {
        guard targetFrame == nil else {
            return false
        }
        guard isHttpOrHttps(url) else {
            return false
        }

        // Preserve native handling for authorized App/Universal Links unless deeplinks are blocked
        if isUrlAuthorized(url, authorizedLinks: authorizedAppLinks) && !preventDeeplink {
            return false
        }

        return openBlankTargetInWebView || preventDeeplink
    }

    /// Attempts to open URL in an external app if it's a custom scheme OR an authorized universal link.
    /// Returns via completion whether an external app was opened (true) or not (false).
    private func handleURLWithApp(_ url: URL, targetFrame _: WKFrameInfo?, completion: @escaping (Bool) -> Void) {

        // If preventDeeplink is true, don't try to open URLs in external apps
        if preventDeeplink {
            print("[InAppBrowser] preventDeeplink is true, won't try to open URLs in external apps")
            completion(false)
            return
        }

        let scheme = url.scheme?.lowercased() ?? ""
        let host = url.host?.lowercased() ?? ""

        print("[InAppBrowser] scheme \(scheme), host \(host)")

        // Don't try to open internal WebKit URLs externally (about:, data:, blob:, etc.)
        let internalSchemes = ["about", "data", "blob", "javascript"]
        if internalSchemes.contains(scheme) {
            print("[InAppBrowser] internal WebKit scheme detected, allowing navigation")
            completion(false)
            return
        }

        // Handle all non-http(s) schemes by default
        if scheme != "http" && scheme != "https" && scheme != "file" {
            print("[InAppBrowser] not http(s) scheme, try to open URLs in external apps")
            completion(tryOpenCustomScheme(url))
            return
        }

        // Also handle specific hosts and schemes from UrlsHandledByApp
        let hosts = UrlsHandledByApp.hosts
        let schemes = UrlsHandledByApp.schemes

        if hosts.contains(host) {
            print("[InAppBrowser] host \(host) matches one in UrlsHandledByApp, try to open URLs in external apps")
            completion(tryOpenCustomScheme(url))
            return
        }
        if schemes.contains(scheme) {
            print("[InAppBrowser] scheme \(scheme) matches one in UrlsHandledByApp, try to open URLs in external apps")
            completion(tryOpenCustomScheme(url))
            return
        }

        // Authorized Universal Link hosts: prefer app via universalLinksOnly
        print("[InAppBrowser] Authorized App Links: \(self.authorizedAppLinks)")
        if isUrlAuthorized(url, authorizedLinks: self.authorizedAppLinks) {
            print("[InAppBrowser] Authorized Universal Link detected \(scheme + host), try to open URLs in external apps")
            tryOpenUniversalLink(url) { opened in
                print("[InAppBrowser] Handle as Universal Link: \(opened)")
                completion(opened) // opened => cancel navigation; not opened => allow WebView
            }
            return
        }

        // Default: let WebView load
        print("[InAppBrowser] default completion handler: false")
        completion(false)
    }

    @objc func backDidClick(sender: AnyObject) {
        // Only handle back navigation, not closing
        if webView?.canGoBack ?? false {
            webView?.goBack()
        }
    }

    // Public method for safe back navigation
    public func goBack() -> Bool {
        if webView?.canGoBack ?? false {
            webView?.goBack()
            return true
        }
        return false
    }

    @objc func forwardDidClick(sender: AnyObject) {
        webView?.goForward()
    }

    @objc func buttonNearDoneDidClick(sender: AnyObject) {
        if showScreenshotButton {
            takeScreenshot { result in
                if case .failure(let error) = result {
                    print("[InAppBrowser] Failed to capture screenshot from toolbar button: \(error.localizedDescription)")
                }
            }
            return
        }
        emit("buttonNearDoneClick")
    }

    @objc func reloadDidClick(sender: AnyObject) {
        webView?.stopLoading()
        if webView?.url != nil {
            webView?.reload()
        } else if let sourceValue = self.source {
            self.load(source: sourceValue)
        }
    }

    @objc func stopDidClick(sender: AnyObject) {
        webView?.stopLoading()
    }

    @objc func activityDidClick(sender: AnyObject) {
        print("[DEBUG] Activity button clicked, shareSubject: \(self.shareSubject ?? "nil")")

        guard let sourceValue = self.source else {
            print("[DEBUG] Activity button: No source available")
            return
        }

        let items: [Any]
        switch sourceValue {
        case .remote(let urlValue):
            items = [urlValue]
        case .file(let urlValue, access: _):
            items = [urlValue]
        case .string(let str, base: _):
            items = [str]
        }
        showDisclaimer(items: items, sender: sender)
    }

    func showDisclaimer(items: [Any], sender: AnyObject) {
        // Show disclaimer dialog before sharing if shareDisclaimer is set
        if let disclaimer = self.shareDisclaimer, !disclaimer.isEmpty {
            // Create and show the alert
            let alert = UIAlertController(
                title: disclaimer["title"] as? String ?? "Title",
                message: disclaimer["message"] as? String ?? "Message",
                preferredStyle: UIAlertController.Style.alert)
            let currentUrl = self.webView?.url?.absoluteString ?? ""

            // Add confirm button that continues with sharing
            alert.addAction(UIAlertAction(
                title: disclaimer["confirmBtn"] as? String ?? "Confirm",
                style: UIAlertAction.Style.default,
                handler: { _ in
                    // Notify that confirm was clicked
                    self.emit("confirmBtnClicked", data: ["url": currentUrl])

                    // Show the share dialog
                    self.showShareSheet(items: items, sender: sender)
                }
            ))

            // Add cancel button
            alert.addAction(UIAlertAction(
                title: disclaimer["cancelBtn"] as? String ?? "Cancel",
                style: UIAlertAction.Style.cancel,
                handler: nil
            ))

            // Present the alert
            self.present(alert, animated: true, completion: nil)
        } else {
            // No disclaimer, directly show share sheet
            showShareSheet(items: items, sender: sender)
        }
    }

    // Separated the actual sharing functionality
    private func showShareSheet(items: [Any], sender: AnyObject) {
        let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
        activityViewController.setValue(self.shareSubject ?? self.title, forKey: "subject")
        if let barButtonItem = sender as? UIBarButtonItem {
            activityViewController.popoverPresentationController?.barButtonItem = barButtonItem
        }
        self.present(activityViewController, animated: true, completion: nil)
    }

    func closeView() {
        var canDismiss = true
        if let url = self.source?.url {
            canDismiss = delegate?.webViewController?(self, canDismiss: url) ?? true
        }
        if canDismiss {
            let currentUrl = webView?.url?.absoluteString ?? ""
            cleanupWebView()
            self.capBrowserPlugin?.handleWebViewDidClose(id: instanceId, url: currentUrl)
            dismiss(animated: true, completion: nil)
        }
    }

    @objc func doneDidClick(sender: AnyObject) {
        // check if closeModal is true, if true display alert before close
        if self.closeModal {
            let currentUrl = webView?.url?.absoluteString ?? ""
            var shouldShowModal = true
            if let regex = self.closeModalURLRegex {
                let matched = regex.firstMatch(in: currentUrl, range: NSRange(currentUrl.startIndex..., in: currentUrl)) != nil
                shouldShowModal = matched
            }
            if shouldShowModal {
                let alert = UIAlertController(title: self.closeModalTitle, message: self.closeModalDescription, preferredStyle: UIAlertController.Style.alert)
                alert.addAction(UIAlertAction(title: self.closeModalOk, style: UIAlertAction.Style.default, handler: { _ in
                    // Notify that confirm was clicked
                    self.emit("confirmBtnClicked", data: ["url": currentUrl])
                    self.closeView()
                }))
                alert.addAction(UIAlertAction(title: self.closeModalCancel, style: UIAlertAction.Style.default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            } else {
                self.closeView()
            }
        } else {
            self.closeView()
        }

    }

    @objc func customDidClick(sender: BlockBarButtonItem) {
        sender.block?(self)
    }

    func canRotate() {}

    func close() {
        let currentUrl = webView?.url?.absoluteString ?? ""
        cleanupWebView()
        capBrowserPlugin?.handleWebViewDidClose(id: instanceId, url: currentUrl)
        dismiss(animated: true, completion: nil)
    }

    open func setUpNavigationBarAppearance() {
        // Set up basic bar appearance
        if let navBar = navigationController?.navigationBar {
            // Make navigation bar transparent
            navBar.setBackgroundImage(UIImage(), for: .default)
            navBar.shadowImage = UIImage()
            navBar.isTranslucent = true

            // Ensure tint colors are applied properly
            if navBar.tintColor == nil {
                navBar.tintColor = tintColor ?? .black
            }

            // Ensure text colors are set
            if navBar.titleTextAttributes == nil {
                navBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: tintColor ?? .black]
            }

            // Ensure the navigation bar buttons are properly visible
            for item in navBar.items ?? [] {
                for barButton in (item.leftBarButtonItems ?? []) + (item.rightBarButtonItems ?? []) {
                    barButton.tintColor = tintColor ?? navBar.tintColor ?? .black
                }
            }
        }

        // Force button colors to update
        updateButtonTintColors()
    }
}

// MARK: - WKUIDelegate
extension WKWebViewController: WKUIDelegate {
    public func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        // Create a strong reference to the completion handler to ensure it's called
        let strongCompletionHandler = completionHandler

        // Ensure UI updates are on the main thread
        DispatchQueue.main.async { [weak self] in
            guard let self = self else {
                // View controller was deallocated
                strongCompletionHandler()
                return
            }

            // Check if view is available and ready for presentation
            guard self.view.window != nil, !self.isBeingDismissed, !self.isMovingFromParent else {
                print("[InAppBrowser] Cannot present alert - view not in window hierarchy or being dismissed")
                strongCompletionHandler()
                return
            }

            let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in
                strongCompletionHandler()
            }))

            // Try to present the alert
            do {
                self.present(alertController, animated: true, completion: nil)
            } catch {
                // This won't typically be triggered as present doesn't throw,
                // but adding as a safeguard
                print("[InAppBrowser] Error presenting alert: \(error)")
                strongCompletionHandler()
            }
        }
    }

    public func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        guard let url = navigationAction.request.url else {
            return nil
        }

        print("[InAppBrowser] Handling popup/new window request for URL: \(url.absoluteString)")
        return capBrowserPlugin?.createManagedPopupWebView(
            from: self,
            configuration: configuration,
            navigationAction: navigationAction
        )
    }

    public func webViewDidClose(_ webView: WKWebView) {
        if webView == self.webView {
            closeView()
        }
    }

    @available(iOS 15.0, *)
    public func webView(_ webView: WKWebView, requestGeolocationPermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
        print("[InAppBrowser] Geolocation permission requested for origin: \(origin.host)")

        // Grant geolocation permission automatically for openWebView
        // This allows websites to access location when opened with openWebView
        decisionHandler(.grant)
    }
}

// MARK: - Host Blocking Utilities
extension WKWebViewController {

    /// Checks if a host should be blocked based on the configured blocked hosts patterns
    /// - Parameter host: The host to check
    /// - Returns: true if the host should be blocked, false otherwise
    private func shouldBlockHost(_ host: String) -> Bool {
        guard !host.isEmpty else { return false }

        let normalizedHost = host.lowercased()

        return blockedHosts.contains { blockPattern in
            return matchesBlockPattern(host: normalizedHost, pattern: blockPattern.lowercased())
        }
    }

    /// Matches a host against a blocking pattern (supports wildcards)
    /// - Parameters:
    ///   - host: The normalized host to check
    ///   - pattern: The normalized blocking pattern
    /// - Returns: true if the host matches the pattern
    private func matchesBlockPattern(host: String, pattern: String) -> Bool {
        guard !pattern.isEmpty else { return false }

        // Exact match - fastest check first
        if host == pattern {
            return true
        }

        // No wildcards - already checked exact match above
        guard pattern.contains("*") else {
            return false
        }

        // Handle wildcard patterns
        if pattern.hasPrefix("*.") {
            return matchesWildcardDomain(host: host, pattern: pattern)
        } else if pattern.contains("*") {
            return matchesRegexPattern(host: host, pattern: pattern)
        }

        return false
    }

    /// Handles simple subdomain wildcard patterns like "*.example.com"
    /// - Parameters:
    ///   - host: The host to check
    ///   - pattern: The wildcard pattern starting with "*."
    /// - Returns: true if the host matches the wildcard domain
    private func matchesWildcardDomain(host: String, pattern: String) -> Bool {
        let domain = String(pattern.dropFirst(2))  // Remove "*."

        guard !domain.isEmpty else { return false }

        // Match exact domain or any subdomain
        return host == domain || host.hasSuffix("." + domain)
    }

    /// Handles complex regex patterns with multiple wildcards
    /// - Parameters:
    ///   - host: The host to check
    ///   - pattern: The pattern with wildcards to convert to regex
    /// - Returns: true if the host matches the regex pattern
    private func matchesRegexPattern(host: String, pattern: String) -> Bool {
        // Escape everything, then re-enable '*' as a wildcard
        let escaped = NSRegularExpression.escapedPattern(for: pattern)
        let wildcardEnabled = escaped.replacingOccurrences(of: "\\*", with: ".*")
        let regexPattern = "^\(wildcardEnabled)$"

        do {
            let regex = try NSRegularExpression(pattern: regexPattern, options: [])
            let range = NSRange(location: 0, length: host.utf16.count)
            return regex.firstMatch(in: host, options: [], range: range) != nil
        } catch {
            print("[InAppBrowser] Invalid regex pattern '\(regexPattern)': \(error)")
            return false
        }
    }
}

// MARK: - WKNavigationDelegate
extension WKWebViewController: WKNavigationDelegate {
    internal func injectPreShowScript() {
        if preShowSemaphore != nil {
            return
        }

        // Safely construct script template with proper escaping
        let userScript = self.preShowScript ?? ""

        // Build script using safe concatenation to avoid multi-line string issues
        let scriptTemplate = [
            "async function preShowFunction() {",
            userScript,
            "}",
            "preShowFunction().then(",
            "    () => window.webkit.messageHandlers.preShowScriptSuccess.postMessage({})",
            ").catch(",
            "    err => {",
            "        console.error('Preshow error', err);",
            "        window.webkit.messageHandlers.preShowScriptError.postMessage(JSON.stringify(err, Object.getOwnPropertyNames(err)));",
            "    }",
            ")"
        ]

        let script = scriptTemplate.joined(separator: "\n")
        print("[InAppBrowser - InjectPreShowScript] PreShowScript script: \(script)")

        self.preShowSemaphore = DispatchSemaphore(value: 0)
        self.executeScript(script: script) // this will run on the main thread

        defer {
            self.preShowSemaphore = nil
            self.preShowError = nil
        }

        if self.preShowSemaphore?.wait(timeout: .now() + 10) == .timedOut {
            print("[InAppBrowser - InjectPreShowScript] PreShowScript running for over 10 seconds. The plugin will not wait any longer!")
            return
        }

        //            "async function preShowFunction() {\n" +
        //            self.preShowScript + "\n" +
        //            "};\n" +
        //            "preShowFunction().then(() => window.PreShowScriptInterface.success()).catch(err => { console.error('Preshow error', err); window.PreShowScriptInterface.error(JSON.stringify(err, Object.getOwnPropertyNames(err))) })";

    }

    public func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
        updateBarButtonItems()
        self.progressView?.progress = 0
        if let urlValue = webView.url {
            self.url = urlValue
            delegate?.webViewController?(self, didStart: urlValue)
        }
    }
    public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        if !didpageInit && self.capBrowserPlugin?.isPresentAfterPageLoad == true {
            // Only inject preShowScript if it wasn't already injected at document start
            let shouldInjectScript = self.preShowScript.map { !$0.isEmpty } ?? false &&
                self.preShowScriptInjectionTime != "documentStart"

            if shouldInjectScript {
                if self.opensHidden {
                    DispatchQueue.global(qos: .userInitiated).async {
                        self.injectPreShowScript()
                    }
                } else {
                    // injectPreShowScript will block, don't execute on the main thread
                    DispatchQueue.global(qos: .userInitiated).async {
                        self.injectPreShowScript()
                        DispatchQueue.main.async { [weak self] in
                            self?.capBrowserPlugin?.presentView(webViewId: self?.instanceId)
                        }
                    }
                }
            } else if !self.opensHidden {
                self.capBrowserPlugin?.presentView(webViewId: instanceId)
            }
        } else if self.preShowScript != nil &&
                    !self.preShowScript!.isEmpty &&
                    self.capBrowserPlugin?.isPresentAfterPageLoad == true &&
                    self.preShowScriptInjectionTime != "documentStart" {
            // Only inject if not already injected at document start
            DispatchQueue.global(qos: .userInitiated).async {
                self.injectPreShowScript()
            }
        }

        // Apply text zoom if set
        if let zoom = self.textZoom {
            applyTextZoom(zoom)
        }

        didpageInit = true
        updateBarButtonItems()
        self.progressView?.progress = 0
        if let url = webView.url {
            self.url = url
            delegate?.webViewController?(self, didFinish: url)
        }
        self.injectJavaScriptInterface()
        emit("browserPageLoaded")
    }

    public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        updateBarButtonItems()
        self.progressView?.progress = 0
        if let url = webView.url {
            self.url = url
            delegate?.webViewController?(self, didFail: url, withError: error)
        }
        emit("pageLoadError")
    }

    public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        updateBarButtonItems()
        self.progressView?.progress = 0
        if let url = webView.url {
            self.url = url
            delegate?.webViewController?(self, didFail: url, withError: error)
        }
        emit("pageLoadError")
    }

    public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        if let credentials = credentials,
           challenge.protectionSpace.receivesCredentialSecurely,
           let url = webView.url, challenge.protectionSpace.host == url.host, challenge.protectionSpace.protocol == url.scheme, challenge.protectionSpace.port == url.port ?? (url.scheme == "https" ? 443 : url.scheme == "http" ? 80 : nil) {
            let urlCredential = URLCredential(user: credentials.username, password: credentials.password, persistence: .none)
            completionHandler(.useCredential, urlCredential)
        } else if let bypassedSSLHosts = bypassedSSLHosts, bypassedSSLHosts.contains(challenge.protectionSpace.host) {
            let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
            completionHandler(.useCredential, credential)
        } else {
            guard self.ignoreUntrustedSSLError else {
                completionHandler(.performDefaultHandling, nil)
                return
            }
            /* allows to open links with self-signed certificates
             Follow Apple's guidelines https://developer.apple.com/documentation/foundation/url_loading_system/handling_an_authentication_challenge/performing_manual_server_trust_authentication
             */
            guard let serverTrust = challenge.protectionSpace.serverTrust  else {
                completionHandler(.useCredential, nil)
                return
            }
            let credential = URLCredential(trust: serverTrust)
            completionHandler(.useCredential, credential)
        }
        self.injectJavaScriptInterface()
    }

    public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        let shouldForceDownload = handleDownloads && navigationAction.shouldPerformDownload

        var actionPolicy: WKNavigationActionPolicy = self.preventDeeplink ? .preventDeeplinkActionPolicy : .allow

        guard let url = navigationAction.request.url else {
            print("[InAppBrowser] Cannot determine URL from navigationAction")
            decisionHandler(actionPolicy)
            return
        }

        if url.absoluteString.contains("apps.apple.com") {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
            decisionHandler(.cancel)
            return
        }

        if !self.allowsFileURL, url.isFileURL {
            print("[InAppBrowser] Cannot handle file URLs")
            decisionHandler(.cancel)
            return
        }

        // Defer the rest of the logic until the async external-app handling checks completes.
        handleURLWithApp(url, targetFrame: navigationAction.targetFrame) { [weak self] openedExternally in
            guard let self else {
                decisionHandler(.cancel)
                return
            }

            if openedExternally {
                decisionHandler(.cancel)
                return
            }

            if self.shouldLoadBlankTargetInCurrentWebView(url, targetFrame: navigationAction.targetFrame) {
                if let webView = self.webView {
                    webView.load(navigationAction.request)
                } else {
                    self.load(remote: url)
                }
                decisionHandler(.cancel)
                return
            }

            let host = url.host ?? ""

            if host == self.source?.url?.host,
               let cookies = self.availableCookies,
               !self.checkRequestCookies(navigationAction.request, cookies: cookies) {
                self.load(remote: url)
                decisionHandler(.cancel)
                return
            }

            if self.shouldBlockHost(host) {
                print("[InAppBrowser] Blocked host detected: \(host)")
                emit("urlChangeEvent", data: ["url": url.absoluteString])
                decisionHandler(.cancel)
                return
            }

            if let navigationType = NavigationType(rawValue: navigationAction.navigationType.rawValue),
               let result = self.delegate?.webViewController?(self, decidePolicy: url, navigationType: navigationType) {
                actionPolicy = result ? .allow : .cancel
            }

            if shouldForceDownload, actionPolicy != .cancel {
                decisionHandler(.download)
                return
            }

            self.injectJavaScriptInterface()
            decisionHandler(actionPolicy)
        }
    }

    public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        if shouldInterceptDownload(for: navigationResponse) {
            decisionHandler(.download)
            return
        }

        decisionHandler(.allow)
    }

    public func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) {
        register(download: download, response: nil, sourceURL: navigationAction.request.url?.absoluteString)
    }

    public func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) {
        register(download: download, response: navigationResponse.response)
    }

    // MARK: - Dimension Management

    /// Apply custom dimensions to the view if specified
    open func applyCustomDimensions() {
        guard let navigationController = navigationController else { return }

        // Apply custom dimensions if both width and height are specified
        if let width = customWidth, let height = customHeight {
            let xPos = customX ?? 0
            let yPos = customY ?? 0

            // Set the frame for the navigation controller's view
            navigationController.view.frame = CGRect(x: xPos, y: yPos, width: width, height: height)
        }
        // If only height is specified, use fullscreen width
        else if let height = customHeight, customWidth == nil {
            let xPos = customX ?? 0
            let yPos = customY ?? 0
            let screenWidth = UIScreen.main.bounds.width

            // Set the frame with fullscreen width and custom height
            navigationController.view.frame = CGRect(x: xPos, y: yPos, width: screenWidth, height: height)
        }
        // Otherwise, use default fullscreen behavior (no action needed)
    }

    /// Update dimensions at runtime
    open func updateDimensions(width: CGFloat?, height: CGFloat?, xPos: CGFloat?, yPos: CGFloat?) {
        // Update stored dimensions
        if let width = width {
            customWidth = width
        }
        if let height = height {
            customHeight = height
        }
        if let xPos = xPos {
            customX = xPos
        }
        if let yPos = yPos {
            customY = yPos
        }

        // Apply the new dimensions
        applyCustomDimensions()
    }

    open func updateSafeTopMargin(_ enabled: Bool) {
        guard enabled != self.enabledSafeTopMargin else { return }
        self.enabledSafeTopMargin = enabled
        guard let webView = self.webView else { return }
        guard webView.superview === self.view else { return }

        // Find and deactivate the existing top constraint
        let existingTopConstraints = self.view.constraints.filter {
            ($0.firstItem as? WKWebView) == webView && $0.firstAttribute == .top
        }
        NSLayoutConstraint.deactivate(existingTopConstraints)

        // Create new top constraint based on enabled value
        let topAnchor = enabled ? self.view.safeAreaLayoutGuide.topAnchor : self.view.topAnchor
        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: topAnchor)
        ])
        self.view.layoutIfNeeded()
    }

    open func updateSafeBottomMargin(_ enabled: Bool) {
        guard enabled != self.enabledSafeBottomMargin else { return }
        self.enabledSafeBottomMargin = enabled
        guard let webView = self.webView else { return }
        guard webView.superview === self.view else { return }

        // Find and deactivate the existing bottom constraint
        let existingBottomConstraints = self.view.constraints.filter {
            ($0.firstItem as? WKWebView) == webView && $0.firstAttribute == .bottom
        }
        NSLayoutConstraint.deactivate(existingBottomConstraints)

        // Create new bottom constraint based on enabled value
        let bottomAnchor = enabled ? self.view.safeAreaLayoutGuide.bottomAnchor : self.view.bottomAnchor
        NSLayoutConstraint.activate([
            webView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
        self.view.layoutIfNeeded()
    }
}

extension WKWebViewController: WKDownloadDelegate {
    public func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) {
        let identifier = ObjectIdentifier(download)
        let sourceURL = downloadStates[identifier]?.sourceURL ?? response.url?.absoluteString
        do {
            let destinationURL = try uniqueDownloadDestination(for: suggestedFilename)
            downloadStates[identifier] = WKDownloadState(destinationURL: destinationURL, mimeType: response.mimeType, sourceURL: sourceURL)
            completionHandler(destinationURL)
        } catch {
            print("[InAppBrowser] Failed to prepare download destination: \(error)")
            downloadStates.removeValue(forKey: identifier)
            emitDownloadFailed(
                sourceURL: sourceURL,
                fileName: suggestedFilename,
                mimeType: response.mimeType,
                error: "Failed to prepare download destination: \(error.localizedDescription)"
            )
            completionHandler(nil)
        }
    }

    public func downloadDidFinish(_ download: WKDownload) {
        let identifier = ObjectIdentifier(download)
        guard let state = downloadStates.removeValue(forKey: identifier) else {
            return
        }
        releaseDownloadDestination(state.destinationURL)
        guard let destinationURL = state.destinationURL else {
            emitDownloadFailed(sourceURL: state.sourceURL, mimeType: state.mimeType, error: "Download finished without a destination URL")
            return
        }

        previewDownloadedFile(destinationURL, mimeType: state.mimeType, sourceURL: state.sourceURL)
    }

    public func download(_ download: WKDownload, didFailWithError error: Error, resumeData: Data?) {
        let state = downloadStates.removeValue(forKey: ObjectIdentifier(download))
        releaseDownloadDestination(state?.destinationURL)
        emitDownloadFailed(
            sourceURL: state?.sourceURL,
            fileName: state?.destinationURL?.lastPathComponent,
            mimeType: state?.mimeType,
            error: "Download failed: \(error.localizedDescription)"
        )
        print("[InAppBrowser] Download failed: \(error.localizedDescription)")
    }
}

extension WKWebViewController: QLPreviewControllerDataSource, QLPreviewControllerDelegate {
    public func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
        previewItemURL == nil ? 0 : 1
    }

    public func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
        return previewItemURL! as NSURL
    }

    public func previewControllerDidDismiss(_ controller: QLPreviewController) {
        previewItemURL = nil
    }
}

class BlockBarButtonItem: UIBarButtonItem {

    var block: ((WKWebViewController) -> Void)?
}

/// Custom view that passes touches outside a target frame to the underlying view
class PassThroughView: UIView {
    var targetFrame: CGRect?

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // If we have a target frame and the touch is outside it, pass through
        if let frame = targetFrame {
            if !frame.contains(point) {
                return nil  // Pass through to underlying views
            }
        }

        // Otherwise, handle normally
        return super.hitTest(point, with: event)
    }
}

extension WKNavigationActionPolicy {
    static let preventDeeplinkActionPolicy = WKNavigationActionPolicy(rawValue: WKNavigationActionPolicy.allow.rawValue + 2)!
}

class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler {
    weak var delegate: WKScriptMessageHandler?

    init(_ delegate: WKScriptMessageHandler) {
        self.delegate = delegate
        super.init()
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        self.delegate?.userContentController(userContentController, didReceive: message)
    }
}
