import SwiftUI
import WebKit
import Foundation
import Observation

struct ContentView: View {
    @State private var host = TraderHost()
    @State private var didAutostart = false
    @AppStorage("mt_setup_completed") private var setupCompleted = false
    @AppStorage("mt_symbol") private var storedSymbol = "AAPL"
    @AppStorage("mt_interval") private var storedInterval = "1d"
    @AppStorage("mt_period") private var storedPeriod = "6mo"
    @AppStorage("mt_max_bars") private var storedMaxBars = 500
    @AppStorage("mt_drop_live") private var storedDropLive = true
    @AppStorage("mt_use_body_range") private var storedUseBodyRange = false
    @AppStorage("mt_volume_filter_enabled") private var storedVolumeFilterEnabled = false
    @AppStorage("mt_volume_sma_window") private var storedVolumeSMAWindow = 20
    @AppStorage("mt_volume_multiplier") private var storedVolumeMultiplier = 1.0
    @AppStorage("mt_gray_fake") private var storedGrayFake = true
    @AppStorage("mt_hide_market_closed_gaps") private var storedHideMarketClosedGaps = true
    @AppStorage("mt_enable_auto_refresh") private var storedEnableAutoRefresh = false
    @AppStorage("mt_refresh_sec") private var storedRefreshSeconds = 60
    @State private var showSetupSheet = false
    @State private var showHelpSheet = false
    @State private var setupDraft = UserSetupPreferences.default
#if DEBUG
    @State private var showDebugPanel = false
#endif

    var body: some View {
        VStack(spacing: 0) {
            if host.isRunning {
                LocalWebView(url: host.serverURL, reloadToken: host.reloadToken)
            } else {
                launchView
            }

#if DEBUG
            debugPanel
#endif
        }
        .frame(minWidth: 1100, minHeight: 760)
        .onAppear {
            guard !didAutostart else { return }
            didAutostart = true
            let normalized = syncHostPreferencesFromSharedSettings()
            persistSharedSettingsFile(normalized)
            host.start()

            if !setupCompleted {
                setupDraft = normalized.setupDefaults
                showSetupSheet = true
            }
        }
        .onDisappear { host.stop() }
        .toolbar {
            ToolbarItemGroup(placement: .primaryAction) {
                Button("Help") {
                    showHelpSheet = true
                }
                Button("Setup") {
                    syncStateFromSharedSettingsFileIfAvailable()
                    setupDraft = storedWebPreferences.normalized().setupDefaults
                    showSetupSheet = true
                }
                Button("Reload") {
                    _ = syncHostPreferencesFromSharedSettings()
                    host.reloadWebView()
                }
                    .disabled(!host.isRunning)
                Button("Restart") {
                    _ = syncHostPreferencesFromSharedSettings()
                    host.restart()
                }
                    .disabled(host.isStarting)
            }
        }
        .sheet(isPresented: $showSetupSheet) {
            setupSheet
        }
        .sheet(isPresented: $showHelpSheet) {
            HelpSheetView()
        }
    }

    private var sharedSettingsURL: URL {
        FileManager.default.homeDirectoryForCurrentUser
            .appendingPathComponent(".manesh_trader", isDirectory: true)
            .appendingPathComponent("settings.json", isDirectory: false)
    }

    private var storedWebPreferences: WebPreferences {
        WebPreferences(
            symbol: storedSymbol,
            interval: storedInterval,
            period: storedPeriod,
            maxBars: storedMaxBars,
            dropLive: storedDropLive,
            useBodyRange: storedUseBodyRange,
            volumeFilterEnabled: storedVolumeFilterEnabled,
            volumeSMAWindow: storedVolumeSMAWindow,
            volumeMultiplier: storedVolumeMultiplier,
            grayFake: storedGrayFake,
            hideMarketClosedGaps: storedHideMarketClosedGaps,
            enableAutoRefresh: storedEnableAutoRefresh,
            refreshSeconds: storedRefreshSeconds
        )
    }

    private var setupSheet: some View {
        NavigationStack {
            Form {
                Section {
                    Text("Choose your default market settings so you see useful data immediately on launch.")
                        .foregroundStyle(.secondary)
                }

                Section("Data Defaults") {
                    TextField("Symbol", text: $setupDraft.symbol)
                    Text("Ticker or pair, e.g. AAPL, MSFT, BTC-USD.")
                        .font(.caption)
                        .foregroundStyle(.secondary)

                    Picker("Timeframe", selection: $setupDraft.timeframe) {
                        ForEach(UserSetupPreferences.timeframeOptions, id: \.self) { option in
                            Text(option).tag(option)
                        }
                    }
                    Text("Bar size for each candle. `1d` is a good starting point.")
                        .font(.caption)
                        .foregroundStyle(.secondary)

                    Picker("Period", selection: $setupDraft.period) {
                        ForEach(UserSetupPreferences.periodOptions, id: \.self) { option in
                            Text(option).tag(option)
                        }
                    }
                    Text("How much history to load for analysis.")
                        .font(.caption)
                        .foregroundStyle(.secondary)

                    Stepper(value: $setupDraft.maxBars, in: 20...5000, step: 10) {
                        Text("Max bars: \(setupDraft.maxBars)")
                    }
                    Text("Limits candles loaded for speed and readability.")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
            }
            .formStyle(.grouped)
            .navigationTitle("Welcome to ManeshTrader")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Use Defaults") {
                        let normalized = setupDraft.normalized()
                        applySetup(normalized, markCompleted: true)
                    }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Save and Continue") {
                        let normalized = setupDraft.normalized()
                        applySetup(normalized, markCompleted: true)
                    }
                }
            }
        }
        .frame(minWidth: 560, minHeight: 460)
    }

    private func applySetup(_ preferences: UserSetupPreferences, markCompleted: Bool) {
        let normalizedSetup = preferences.normalized()
        let mergedPreferences = storedWebPreferences
            .applyingSetup(normalizedSetup)
            .normalized()
        writeWebPreferencesToStorage(mergedPreferences)
        persistSharedSettingsFile(mergedPreferences)
        if markCompleted {
            setupCompleted = true
        }
        host.applyPreferences(mergedPreferences)
        host.reloadWebView()
        showSetupSheet = false
    }

    private func syncStateFromSharedSettingsFileIfAvailable() {
        guard
            let data = try? Data(contentsOf: sharedSettingsURL),
            let decoded = try? JSONDecoder().decode(WebPreferences.self, from: data)
        else {
            return
        }

        writeWebPreferencesToStorage(decoded.normalized())
    }

    @discardableResult
    private func syncHostPreferencesFromSharedSettings() -> WebPreferences {
        syncStateFromSharedSettingsFileIfAvailable()
        let normalized = storedWebPreferences.normalized()
        writeWebPreferencesToStorage(normalized)
        host.applyPreferences(normalized)
        return normalized
    }

    private func writeWebPreferencesToStorage(_ preferences: WebPreferences) {
        storedSymbol = preferences.symbol
        storedInterval = preferences.interval
        storedPeriod = preferences.period
        storedMaxBars = preferences.maxBars
        storedDropLive = preferences.dropLive
        storedUseBodyRange = preferences.useBodyRange
        storedVolumeFilterEnabled = preferences.volumeFilterEnabled
        storedVolumeSMAWindow = preferences.volumeSMAWindow
        storedVolumeMultiplier = preferences.volumeMultiplier
        storedGrayFake = preferences.grayFake
        storedHideMarketClosedGaps = preferences.hideMarketClosedGaps
        storedEnableAutoRefresh = preferences.enableAutoRefresh
        storedRefreshSeconds = preferences.refreshSeconds
    }

    private func persistSharedSettingsFile(_ preferences: WebPreferences) {
        let encoder = JSONEncoder()
        encoder.outputFormatting = [.prettyPrinted, .sortedKeys]

        do {
            let normalized = preferences.normalized()
            let directory = sharedSettingsURL.deletingLastPathComponent()
            try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
            let data = try encoder.encode(normalized)
            try data.write(to: sharedSettingsURL, options: .atomic)
        } catch {
            #if DEBUG
            print("Failed to persist shared settings: \(error.localizedDescription)")
            #endif
        }
    }

    private var launchView: some View {
        VStack(spacing: 14) {
            launchCard
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(
            LinearGradient(
                colors: [
                    Color(nsColor: .windowBackgroundColor),
                    Color(nsColor: .underPageBackgroundColor),
                ],
                startPoint: .top,
                endPoint: .bottom
            )
        )
    }

    @ViewBuilder
    private var launchCard: some View {
        let card = VStack(spacing: 14) {
            Image(systemName: host.launchError == nil ? "chart.line.uptrend.xyaxis.circle.fill" : "exclamationmark.triangle.fill")
                .font(.system(size: 42))
                .foregroundStyle(host.launchError == nil ? .green : .orange)

            Text(host.launchError == nil ? "Starting ManeshTrader" : "Couldn’t Start ManeshTrader")
                .font(.title3.weight(.semibold))

            if let launchError = host.launchError {
                Text(launchError)
                    .foregroundStyle(.secondary)
                    .multilineTextAlignment(.center)
                    .frame(maxWidth: 700)

                if #available(macOS 26.0, *) {
                    Button("Try Again") { host.start() }
                        .buttonStyle(.glassProminent)
                } else {
                    Button("Try Again") { host.start() }
                        .buttonStyle(.borderedProminent)
                }
            } else {
                ProgressView()
                    .controlSize(.large)

                Text("Loading local engine...")
                    .foregroundStyle(.secondary)
            }
        }
        .padding(.horizontal, 26)
        .padding(.vertical, 22)

        if #available(macOS 26.0, *) {
            card.glassEffect(.regular.interactive(), in: .rect(cornerRadius: 24))
        } else {
            card.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 24, style: .continuous))
        }
    }

#if DEBUG
    private var debugPanel: some View {
        VStack(spacing: 0) {
            Divider()
            DisclosureGroup("Developer Tools", isExpanded: $showDebugPanel) {
                VStack(alignment: .leading, spacing: 8) {
                    HStack(spacing: 8) {
                        Button("Start") {
                            _ = syncHostPreferencesFromSharedSettings()
                            host.start()
                        }
                            .disabled(host.isRunning || host.isStarting)
                        Button("Stop") { host.stop() }
                            .disabled(!host.isRunning && !host.isStarting)
                        Button("Reload") {
                            _ = syncHostPreferencesFromSharedSettings()
                            host.reloadWebView()
                        }
                            .disabled(!host.isRunning)
                    }

                    Text(host.status)
                        .font(.callout)
                        .foregroundStyle(.secondary)

                    Text("Local URL: \(host.serverURL.absoluteString)")
                        .font(.caption)
                        .foregroundStyle(.secondary)

                    Text("Embedded backend: \(host.backendExecutablePath)")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                        .lineLimit(1)
                        .truncationMode(.middle)
                }
                .padding(.top, 6)
            }
            .padding(.horizontal, 12)
            .padding(.vertical, 10)
        }
    }
#endif
}

private struct HelpSheetView: View {
    @Environment(\.dismiss) private var dismiss

    private var helpFileURL: URL? {
        let fm = FileManager.default
        let candidates = [
            Bundle.main.url(forResource: "help", withExtension: "html", subdirectory: "Help"),
            Bundle.main.url(forResource: "help", withExtension: "html"),
            Bundle.main.resourceURL?.appendingPathComponent("Help/help.html"),
            Bundle.main.resourceURL?.appendingPathComponent("help.html"),
        ].compactMap { $0 }
        return candidates.first(where: { fm.fileExists(atPath: $0.path) })
    }

    var body: some View {
        NavigationStack {
            Group {
                if let helpFileURL {
                    HelpDocumentWebView(fileURL: helpFileURL)
                } else {
                    VStack(alignment: .leading, spacing: 8) {
                        Text("Help file not found.")
                            .font(.title3.weight(.semibold))
                        Text("Expected bundled file: help.html")
                            .foregroundStyle(.secondary)
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                    .padding(24)
                }
            }
            .navigationTitle("Help & Quick Start")
            .toolbar {
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done") { dismiss() }
                }
            }
        }
        .frame(minWidth: 860, minHeight: 640)
    }
}

private struct HelpDocumentWebView: NSViewRepresentable {
    let fileURL: URL

    func makeNSView(context: Context) -> WKWebView {
        let webView = WKWebView(frame: .zero)
        webView.allowsBackForwardNavigationGestures = true
        webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL.deletingLastPathComponent())
        return webView
    }

    func updateNSView(_ webView: WKWebView, context: Context) {
        if webView.url != fileURL {
            webView.loadFileURL(fileURL, allowingReadAccessTo: fileURL.deletingLastPathComponent())
        }
    }
}

#Preview {
    ContentView()
}

private struct UserSetupPreferences {
    static let timeframeOptions = ["1m", "2m", "5m", "15m", "30m", "60m", "90m", "1h", "1d", "5d", "1wk", "1mo"]
    static let periodOptions = ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "max"]

    static let `default` = UserSetupPreferences(symbol: "AAPL", timeframe: "1d", period: "6mo", maxBars: 500)

    var symbol: String
    var timeframe: String
    var period: String
    var maxBars: Int

    func normalized() -> UserSetupPreferences {
        let normalizedSymbol = symbol.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
        let safeSymbol = normalizedSymbol.isEmpty ? "AAPL" : normalizedSymbol

        let safeTimeframe = Self.timeframeOptions.contains(timeframe) ? timeframe : "1d"
        let safePeriod = Self.periodOptions.contains(period) ? period : "6mo"
        let safeMaxBars = min(5000, max(20, maxBars))

        return UserSetupPreferences(
            symbol: safeSymbol,
            timeframe: safeTimeframe,
            period: safePeriod,
            maxBars: safeMaxBars
        )
    }
}

private struct WebPreferences: Codable {
    var symbol: String
    var interval: String
    var period: String
    var maxBars: Int
    var dropLive: Bool
    var useBodyRange: Bool
    var volumeFilterEnabled: Bool
    var volumeSMAWindow: Int
    var volumeMultiplier: Double
    var grayFake: Bool
    var hideMarketClosedGaps: Bool
    var enableAutoRefresh: Bool
    var refreshSeconds: Int

    init(
        symbol: String,
        interval: String,
        period: String,
        maxBars: Int,
        dropLive: Bool,
        useBodyRange: Bool,
        volumeFilterEnabled: Bool,
        volumeSMAWindow: Int,
        volumeMultiplier: Double,
        grayFake: Bool,
        hideMarketClosedGaps: Bool,
        enableAutoRefresh: Bool,
        refreshSeconds: Int
    ) {
        self.symbol = symbol
        self.interval = interval
        self.period = period
        self.maxBars = maxBars
        self.dropLive = dropLive
        self.useBodyRange = useBodyRange
        self.volumeFilterEnabled = volumeFilterEnabled
        self.volumeSMAWindow = volumeSMAWindow
        self.volumeMultiplier = volumeMultiplier
        self.grayFake = grayFake
        self.hideMarketClosedGaps = hideMarketClosedGaps
        self.enableAutoRefresh = enableAutoRefresh
        self.refreshSeconds = refreshSeconds
    }

    static let `default` = WebPreferences(
        symbol: "AAPL",
        interval: "1d",
        period: "6mo",
        maxBars: 500,
        dropLive: true,
        useBodyRange: false,
        volumeFilterEnabled: false,
        volumeSMAWindow: 20,
        volumeMultiplier: 1.0,
        grayFake: true,
        hideMarketClosedGaps: true,
        enableAutoRefresh: false,
        refreshSeconds: 60
    )

    private enum CodingKeys: String, CodingKey {
        case symbol
        case interval
        case period
        case maxBars = "max_bars"
        case dropLive = "drop_live"
        case useBodyRange = "use_body_range"
        case volumeFilterEnabled = "volume_filter_enabled"
        case volumeSMAWindow = "volume_sma_window"
        case volumeMultiplier = "volume_multiplier"
        case grayFake = "gray_fake"
        case hideMarketClosedGaps = "hide_market_closed_gaps"
        case enableAutoRefresh = "enable_auto_refresh"
        case refreshSeconds = "refresh_sec"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let defaults = Self.default

        symbol = try container.decodeIfPresent(String.self, forKey: .symbol) ?? defaults.symbol
        interval = try container.decodeIfPresent(String.self, forKey: .interval) ?? defaults.interval
        period = try container.decodeIfPresent(String.self, forKey: .period) ?? defaults.period
        maxBars = try container.decodeIfPresent(Int.self, forKey: .maxBars) ?? defaults.maxBars
        dropLive = try container.decodeIfPresent(Bool.self, forKey: .dropLive) ?? defaults.dropLive
        useBodyRange = try container.decodeIfPresent(Bool.self, forKey: .useBodyRange) ?? defaults.useBodyRange
        volumeFilterEnabled = try container.decodeIfPresent(Bool.self, forKey: .volumeFilterEnabled) ?? defaults.volumeFilterEnabled
        volumeSMAWindow = try container.decodeIfPresent(Int.self, forKey: .volumeSMAWindow) ?? defaults.volumeSMAWindow
        volumeMultiplier = try container.decodeIfPresent(Double.self, forKey: .volumeMultiplier) ?? defaults.volumeMultiplier
        grayFake = try container.decodeIfPresent(Bool.self, forKey: .grayFake) ?? defaults.grayFake
        hideMarketClosedGaps = try container.decodeIfPresent(Bool.self, forKey: .hideMarketClosedGaps) ?? defaults.hideMarketClosedGaps
        enableAutoRefresh = try container.decodeIfPresent(Bool.self, forKey: .enableAutoRefresh) ?? defaults.enableAutoRefresh
        refreshSeconds = try container.decodeIfPresent(Int.self, forKey: .refreshSeconds) ?? defaults.refreshSeconds
    }

    func normalized() -> WebPreferences {
        let safeSymbol = {
            let candidate = symbol.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
            return candidate.isEmpty ? "AAPL" : candidate
        }()

        let safeInterval = UserSetupPreferences.timeframeOptions.contains(interval) ? interval : "1d"
        let safePeriod = UserSetupPreferences.periodOptions.contains(period) ? period : "6mo"
        let safeMaxBars = min(5000, max(20, maxBars))
        let safeVolumeSMAWindow = min(100, max(2, volumeSMAWindow))
        let clampedMultiplier = min(3.0, max(0.1, volumeMultiplier))
        let safeVolumeMultiplier = (clampedMultiplier * 10).rounded() / 10
        let safeRefreshSeconds = min(600, max(10, refreshSeconds))

        return WebPreferences(
            symbol: safeSymbol,
            interval: safeInterval,
            period: safePeriod,
            maxBars: safeMaxBars,
            dropLive: dropLive,
            useBodyRange: useBodyRange,
            volumeFilterEnabled: volumeFilterEnabled,
            volumeSMAWindow: safeVolumeSMAWindow,
            volumeMultiplier: safeVolumeMultiplier,
            grayFake: grayFake,
            hideMarketClosedGaps: hideMarketClosedGaps,
            enableAutoRefresh: enableAutoRefresh,
            refreshSeconds: safeRefreshSeconds
        )
    }

    var setupDefaults: UserSetupPreferences {
        UserSetupPreferences(
            symbol: symbol,
            timeframe: interval,
            period: period,
            maxBars: maxBars
        ).normalized()
    }

    func applyingSetup(_ setup: UserSetupPreferences) -> WebPreferences {
        let normalizedSetup = setup.normalized()
        return WebPreferences(
            symbol: normalizedSetup.symbol,
            interval: normalizedSetup.timeframe,
            period: normalizedSetup.period,
            maxBars: normalizedSetup.maxBars,
            dropLive: dropLive,
            useBodyRange: useBodyRange,
            volumeFilterEnabled: volumeFilterEnabled,
            volumeSMAWindow: volumeSMAWindow,
            volumeMultiplier: volumeMultiplier,
            grayFake: grayFake,
            hideMarketClosedGaps: hideMarketClosedGaps,
            enableAutoRefresh: enableAutoRefresh,
            refreshSeconds: refreshSeconds
        )
    }

    var queryItems: [URLQueryItem] {
        [
            URLQueryItem(name: "symbol", value: symbol),
            URLQueryItem(name: "interval", value: interval),
            URLQueryItem(name: "period", value: period),
            URLQueryItem(name: "max_bars", value: String(maxBars)),
            URLQueryItem(name: "drop_live", value: String(dropLive)),
            URLQueryItem(name: "use_body_range", value: String(useBodyRange)),
            URLQueryItem(name: "volume_filter_enabled", value: String(volumeFilterEnabled)),
            URLQueryItem(name: "volume_sma_window", value: String(volumeSMAWindow)),
            URLQueryItem(name: "volume_multiplier", value: String(volumeMultiplier)),
            URLQueryItem(name: "gray_fake", value: String(grayFake)),
            URLQueryItem(name: "hide_market_closed_gaps", value: String(hideMarketClosedGaps)),
            URLQueryItem(name: "enable_auto_refresh", value: String(enableAutoRefresh)),
            URLQueryItem(name: "refresh_sec", value: String(refreshSeconds)),
        ]
    }
}

private struct LocalWebView: NSViewRepresentable {
    let url: URL
    let reloadToken: Int

    func makeNSView(context: Context) -> WKWebView {
        let webView = WKWebView(frame: .zero)
        webView.customUserAgent = "ManeshTraderMac"
        webView.allowsBackForwardNavigationGestures = false
        webView.navigationDelegate = context.coordinator
        context.coordinator.attach(webView)
        webView.load(URLRequest(url: url))
        return webView
    }

    func updateNSView(_ webView: WKWebView, context: Context) {
        if context.coordinator.lastReloadToken != reloadToken || webView.url != url {
            context.coordinator.lastReloadToken = reloadToken
            webView.load(URLRequest(url: url))
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(url: url, reloadToken: reloadToken)
    }

    final class Coordinator: NSObject, WKNavigationDelegate {
        private let url: URL
        private weak var webView: WKWebView?
        private var pendingRetry: DispatchWorkItem?
        var lastReloadToken: Int

        init(url: URL, reloadToken: Int) {
            self.url = url
            self.lastReloadToken = reloadToken
        }

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

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            pendingRetry?.cancel()
            pendingRetry = nil
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            scheduleRetry()
        }

        func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
            scheduleRetry()
        }

        private func scheduleRetry() {
            pendingRetry?.cancel()
            let retry = DispatchWorkItem { [weak self] in
                guard let self, let webView = self.webView else { return }
                webView.load(URLRequest(url: self.url))
            }
            pendingRetry = retry
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: retry)
        }
    }
}

@Observable
@MainActor
private final class TraderHost {
    var isRunning = false
    var isStarting = false
    var launchError: String?
    var status = "Preparing backend..."
    var backendExecutablePath = ""
    var reloadToken = 0
    var serverPort = 8501
    var webQueryItems: [URLQueryItem] = []
    var serverURL: URL {
        var components = URLComponents()
        components.scheme = "http"
        components.host = "127.0.0.1"
        components.port = serverPort
        components.queryItems = webQueryItems.isEmpty ? nil : webQueryItems
        return components.url!
    }

    private var process: Process?
    private var outputPipe: Pipe?
    private var latestBackendLogLine = ""
    private var lastSignificantBackendLogLine = ""
    private var didRequestStop = false

    func start() {
        guard process == nil else { return }

        isStarting = true
        launchError = nil
        status = "Starting local engine..."
        didRequestStop = false

        guard let executableURL = bundledBackendExecutableURL() else {
            isStarting = false
            launchError = "Required backend files were not found in the app bundle."
            status = "Bundled backend executable not found."
            return
        }
        backendExecutablePath = executableURL.path

        do {
            try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: executableURL.path)
        } catch {
            status = "Could not set executable permissions: \(error.localizedDescription)"
        }

        let p = Process()
        latestBackendLogLine = ""
        lastSignificantBackendLogLine = ""
        serverPort = nextAvailablePort(preferred: 8501)
        p.currentDirectoryURL = executableURL.deletingLastPathComponent()
        p.executableURL = executableURL
        var environment = ProcessInfo.processInfo.environment
        environment["MANESH_TRADER_PORT"] = "\(serverPort)"
        p.environment = environment

        let pipe = Pipe()
        outputPipe = pipe
        p.standardOutput = pipe
        p.standardError = pipe
        pipe.fileHandleForReading.readabilityHandler = { [weak self] handle in
            let data = handle.availableData
            guard !data.isEmpty, let text = String(data: data, encoding: .utf8) else { return }
            let lastLine = text
                .split(whereSeparator: \.isNewline)
                .last
                .map(String.init)?
                .trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
            guard !lastLine.isEmpty else { return }
            DispatchQueue.main.async {
                self?.latestBackendLogLine = lastLine
                if !lastLine.contains("LOADER: failed to destroy sync semaphore") {
                    self?.lastSignificantBackendLogLine = lastLine
                }
            }
        }

        p.terminationHandler = { [weak self] proc in
            DispatchQueue.main.async {
                guard let self else { return }
                self.isRunning = false
                self.isStarting = false
                self.process = nil
                self.outputPipe?.fileHandleForReading.readabilityHandler = nil
                self.outputPipe = nil

                if self.didRequestStop {
                    self.status = "Stopped."
                    return
                }

                if let line = self.lastSignificantBackendLogLine.isEmpty ? nil : self.lastSignificantBackendLogLine {
                    self.status = "Stopped (exit \(proc.terminationStatus)): \(line)"
                } else if let line = self.latestBackendLogLine.isEmpty ? nil : self.latestBackendLogLine {
                    self.status = "Stopped (exit \(proc.terminationStatus)): \(line)"
                } else {
                    self.status = "Stopped (exit \(proc.terminationStatus))."
                }

                if proc.terminationStatus != 0 {
                    self.launchError = "The local engine stopped unexpectedly. Please try again."
                }
            }
        }

        do {
            try p.run()
            process = p
            isRunning = true
            isStarting = false
            reloadToken += 1
            status = "Running locally in-app at \(serverURL.absoluteString)"
        } catch {
            status = "Failed to start backend: \(error.localizedDescription)"
            process = nil
            outputPipe?.fileHandleForReading.readabilityHandler = nil
            outputPipe = nil
            isRunning = false
            isStarting = false
            launchError = "Unable to start the local engine. Please try again."
        }
    }

    func stop() {
        guard let process else { return }
        didRequestStop = true
        isStarting = false
        if process.isRunning {
            process.terminate()
        }
        outputPipe?.fileHandleForReading.readabilityHandler = nil
        outputPipe = nil
        self.process = nil
        isRunning = false
        status = "Stopped."
    }

    func reloadWebView() {
        guard isRunning else { return }
        reloadToken += 1
    }

    func applyPreferences(_ preferences: WebPreferences) {
        webQueryItems = preferences.normalized().queryItems
    }

    func restart() {
        stop()
        start()
    }

    private func bundledBackendExecutableURL() -> URL? {
        let fm = FileManager.default
        let candidates = [
            Bundle.main.url(forResource: "WebBackend", withExtension: nil, subdirectory: "EmbeddedBackend"),
            Bundle.main.resourceURL?.appendingPathComponent("EmbeddedBackend/WebBackend"),
            Bundle.main.url(forResource: "WebBackend", withExtension: nil),
            Bundle.main.url(forResource: "ManeshTraderBackend", withExtension: nil, subdirectory: "EmbeddedBackend"),
            Bundle.main.resourceURL?.appendingPathComponent("EmbeddedBackend/ManeshTraderBackend"),
            Bundle.main.url(forResource: "ManeshTraderBackend", withExtension: nil),
        ].compactMap { $0 }

        return candidates.first(where: { fm.fileExists(atPath: $0.path) })
    }

    private func nextAvailablePort(preferred: Int) -> Int {
        if canBindToLocalPort(preferred) {
            return preferred
        }

        for candidate in 8502...8600 where canBindToLocalPort(candidate) {
            return candidate
        }
        return preferred
    }

    private func canBindToLocalPort(_ port: Int) -> Bool {
        let fd = socket(AF_INET, SOCK_STREAM, 0)
        guard fd >= 0 else { return false }
        defer { close(fd) }

        var value: Int32 = 1
        _ = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &value, socklen_t(MemoryLayout<Int32>.size))

        var addr = sockaddr_in()
        addr.sin_len = UInt8(MemoryLayout<sockaddr_in>.stride)
        addr.sin_family = sa_family_t(AF_INET)
        addr.sin_port = in_port_t(UInt16(port).bigEndian)
        addr.sin_addr = in_addr(s_addr: inet_addr("127.0.0.1"))

        return withUnsafePointer(to: &addr) { pointer in
            pointer.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockPtr in
                bind(fd, sockPtr, socklen_t(MemoryLayout<sockaddr_in>.stride)) == 0
            }
        }
    }
}
