import SwiftUI
import WebKit

@MainActor
final class WebViewStore: ObservableObject {
    @Published var isLoading = true
    @Published var canGoBack = false
    @Published var pageTitle = "WordLoop"
    weak var webView: WKWebView?
    var schemeHandler: BundleWebSchemeHandler?
}

struct WordLoopWebView: UIViewRepresentable {
    let entryURL: URL
    let apiBaseURL: String
    let usesBundledFrontend: Bool
    @ObservedObject var store: WebViewStore

    func makeCoordinator() -> Coordinator {
        Coordinator(store: store, usesBundledFrontend: usesBundledFrontend)
    }

    func makeUIView(context: Context) -> WKWebView {
        let configuration = WKWebViewConfiguration()
        configuration.defaultWebpagePreferences.allowsContentJavaScript = true
        configuration.websiteDataStore = .default()
        configuration.userContentController.addUserScript(
            Self.makeInjectionScript(apiBaseURL: apiBaseURL)
        )

        let webView = WKWebView(frame: .zero, configuration: configuration)
        if #available(iOS 16.4, *) {
            webView.isInspectable = true
        }
        webView.navigationDelegate = context.coordinator
        webView.allowsBackForwardNavigationGestures = true
        webView.scrollView.bounces = true
        webView.isOpaque = false
        webView.backgroundColor = UIColor(red: 0.956, green: 0.965, blue: 0.984, alpha: 1)

        context.coordinator.attach(webView)
        context.coordinator.load(entryURL: entryURL, apiBaseURL: apiBaseURL, in: webView)
        return webView
    }

    func updateUIView(_ webView: WKWebView, context: Context) {
        context.coordinator.attach(webView)
        context.coordinator.load(entryURL: entryURL, apiBaseURL: apiBaseURL, in: webView)
        context.coordinator.updateAPIBase(apiBaseURL, in: webView)
    }

    private static func makeInjectionScript(apiBaseURL: String) -> WKUserScript {
        let escapedAPI = apiBaseURL
            .replacingOccurrences(of: "\\", with: "\\\\")
            .replacingOccurrences(of: "'", with: "\\'")
        let source = """
        window.__WORDLOOP_RUNTIME__ = {
          embedded: true,
          iosApp: true,
          apiBase: '\(escapedAPI)'
        };
        """
        return WKUserScript(source: source, injectionTime: .atDocumentStart, forMainFrameOnly: true)
    }

    final class Coordinator: NSObject, WKNavigationDelegate {
        private let store: WebViewStore
        private let usesBundledFrontend: Bool
        private var loadedEntryURL: URL?
        private var loadedAPIBaseURL: String?
        private var pendingAPIBaseURL: String?

        init(store: WebViewStore, usesBundledFrontend: Bool) {
            self.store = store
            self.usesBundledFrontend = usesBundledFrontend
        }

        func attach(_ webView: WKWebView) {
            store.webView = webView
        }

        func load(entryURL: URL, apiBaseURL: String, in webView: WKWebView) {
            let entryChanged = loadedEntryURL != entryURL
            let apiChanged = loadedAPIBaseURL != apiBaseURL
            guard entryChanged || (apiChanged && loadedEntryURL == nil) else {
                if apiChanged {
                    updateAPIBase(apiBaseURL, in: webView)
                }
                return
            }

            loadedEntryURL = entryURL
            loadedAPIBaseURL = apiBaseURL
            pendingAPIBaseURL = apiBaseURL
            store.isLoading = true

            if usesBundledFrontend, let bundleEntry = Self.bundledHTMLPayload() {
                // loadHTMLString 比 wordloop:// 自定义协议更可靠（WKWebView 对内联/外链脚本执行更稳定）
                webView.loadHTMLString(bundleEntry.html, baseURL: bundleEntry.baseURL)
            } else {
                webView.load(URLRequest(url: entryURL, cachePolicy: .reloadIgnoringLocalCacheData))
            }
        }

        private static func bundledHTMLPayload() -> (html: String, baseURL: URL)? {
            guard let wwwRoot = ServerSettings.bundledWWWDirectory,
                  let indexURL = ServerSettings.bundledHomeURL,
                  let html = try? String(contentsOf: indexURL, encoding: .utf8),
                  !html.isEmpty else {
                return nil
            }
            return (html, wwwRoot)
        }

        func updateAPIBase(_ apiBaseURL: String, in webView: WKWebView) {
            guard loadedAPIBaseURL != apiBaseURL else { return }
            loadedAPIBaseURL = apiBaseURL
            let escapedAPI = apiBaseURL
                .replacingOccurrences(of: "\\", with: "\\\\")
                .replacingOccurrences(of: "'", with: "\\'")
            webView.evaluateJavaScript("""
            window.__WORDLOOP_RUNTIME__ = window.__WORDLOOP_RUNTIME__ || {};
            window.__WORDLOOP_RUNTIME__.apiBase = '\(escapedAPI)';
            if (window.__wordloopSetApiBase) window.__wordloopSetApiBase('\(escapedAPI)');
            """)
        }

        func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            store.isLoading = true
            store.canGoBack = webView.canGoBack
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            store.isLoading = false
            store.canGoBack = webView.canGoBack
        }

        func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
            store.isLoading = false
            store.canGoBack = webView.canGoBack
        }

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            store.isLoading = false
            store.canGoBack = webView.canGoBack
            store.pageTitle = webView.title ?? "WordLoop"
            if let apiBaseURL = pendingAPIBaseURL {
                updateAPIBase(apiBaseURL, in: webView)
            }
        }
    }
}

struct WordLoopWebContainer: View {
    @Binding var environment: ServerEnvironment
    @Binding var localBaseURL: String
    let baseURL: String
    let onBaseURLChange: (String) -> Void

    @StateObject private var store = WebViewStore()
    @State private var entryURL: URL
    @State private var apiBaseURL: String
    @State private var usesBundledFrontend: Bool

    init(
        environment: Binding<ServerEnvironment>,
        localBaseURL: Binding<String>,
        baseURL: String,
        onBaseURLChange: @escaping (String) -> Void
    ) {
        _environment = environment
        _localBaseURL = localBaseURL
        self.baseURL = baseURL
        self.onBaseURLChange = onBaseURLChange

        let hasBundle = ServerSettings.bundledWWWDirectory != nil
        let resolvedEntry = hasBundle
            ? (ServerSettings.bundledHomeURL ?? BundledWebEntry.schemeURL)
            : (ServerSettings.homeURL(for: baseURL)
                ?? URL(string: ServerEnvironment.production.defaultBaseURL + "/")!)
        let resolvedAPI = ServerSettings.apiBaseURL(for: baseURL)

        _entryURL = State(initialValue: resolvedEntry)
        _apiBaseURL = State(initialValue: resolvedAPI)
        _usesBundledFrontend = State(initialValue: hasBundle)
    }

    var body: some View {
        VStack(spacing: 0) {
            NavigationStack {
                ZStack {
                    WordLoopWebView(
                        entryURL: entryURL,
                        apiBaseURL: apiBaseURL,
                        usesBundledFrontend: usesBundledFrontend,
                        store: store
                    )
                    .ignoresSafeArea(edges: .bottom)

                    if store.isLoading {
                        ProgressView("加载中…")
                            .padding(20)
                            .background(.ultraThinMaterial)
                            .clipShape(RoundedRectangle(cornerRadius: 12))
                    }
                }
                .navigationTitle(store.pageTitle)
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItemGroup(placement: .topBarTrailing) {
                        Button {
                            store.webView?.reload()
                        } label: {
                            Image(systemName: "arrow.clockwise")
                        }
                        .disabled(store.webView == nil)

                        Button {
                            if store.webView?.canGoBack == true {
                                store.webView?.goBack()
                            }
                        } label: {
                            Image(systemName: "chevron.backward")
                        }
                        .disabled(!store.canGoBack)
                    }
                }
            }

            ServerBottomBar(
                environment: $environment,
                localBaseURL: $localBaseURL,
                onServerChange: handleServerChange
            )
        }
    }

    private func handleServerChange(_ newBaseURL: String) {
        let newAPI = ServerSettings.apiBaseURL(for: newBaseURL)
        onBaseURLChange(newBaseURL)
        apiBaseURL = newAPI

        if !usesBundledFrontend, let remoteURL = ServerSettings.homeURL(for: newBaseURL) {
            entryURL = remoteURL
        }
    }
}
