// PRE Desktop — Native macOS desktop app for PRE (Personal Reasoning Engine)
// Wraps the web GUI (localhost:7749) in a WKWebView with dock icon and native window.
// Starts the PRE server automatically if not already running.
// Compile: swiftc -O -o "PRE Desktop.app/Contents/MacOS/pre-desktop" pre-desktop.swift -framework Cocoa -framework WebKit

import Cocoa
import WebKit

// ── Configuration ──────────────────────────────────────────────────────────

let WEB_PORT = ProcessInfo.processInfo.environment["PRE_WEB_PORT"] ?? "7749"
let OLLAMA_PORT = ProcessInfo.processInfo.environment["PRE_PORT"] ?? "11434"
let PRE_URL = "http://localhost:\(WEB_PORT)"

// Resolve paths relative to this binary's location
let binaryPath = CommandLine.arguments[0]
let engineDir = (binaryPath as NSString).deletingLastPathComponent
    .replacingOccurrences(of: "/PRE Desktop.app/Contents/MacOS", with: "")
let webDir = (engineDir as NSString).deletingLastPathComponent + "/web"
let preServerScript = webDir + "/pre-server.sh"
let homeDir = FileManager.default.homeDirectoryForCurrentUser.path
let preDir = homeDir + "/.pre"

// ── Server Manager ─────────────────────────────────────────────────────────

class ServerManager {
    var serverStartedByUs = false

    func isServerRunning(completion: @escaping (Bool) -> Void) {
        checkHTTP(url: "\(PRE_URL)/api/health", completion: completion)
    }

    func isOllamaRunning(completion: @escaping (Bool) -> Void) {
        checkHTTP(url: "http://localhost:\(OLLAMA_PORT)/v1/models", completion: completion)
    }

    func ensureServer(onProgress: @escaping (String) -> Void, completion: @escaping (Bool) -> Void) {
        isServerRunning { [self] running in
            if running {
                DispatchQueue.main.async { completion(true) }
                return
            }

            DispatchQueue.global(qos: .userInitiated).async { [self] in
                // Ensure Ollama is running first
                let ollamaSem = DispatchSemaphore(value: 0)
                var ollamaOk = false
                self.isOllamaRunning { ok in
                    ollamaOk = ok
                    ollamaSem.signal()
                }
                ollamaSem.wait()

                if !ollamaOk {
                    DispatchQueue.main.async { onProgress("Starting Ollama...") }
                    let ollamaApp = "/Applications/Ollama.app"
                    if FileManager.default.fileExists(atPath: ollamaApp) {
                        self.runShell("open -g -a Ollama")
                    } else {
                        self.runShell("ollama serve >/dev/null 2>&1 &")
                    }
                    // Wait up to 20s for Ollama
                    for i in 0..<20 {
                        Thread.sleep(forTimeInterval: 1)
                        let sem = DispatchSemaphore(value: 0)
                        var ready = false
                        self.isOllamaRunning { ok in ready = ok; sem.signal() }
                        sem.wait()
                        if ready { break }
                        if i == 9 {
                            DispatchQueue.main.async { onProgress("Waiting for Ollama...") }
                        }
                    }
                }

                // Start the PRE server
                DispatchQueue.main.async { onProgress("Starting PRE server...") }
                self.serverStartedByUs = true

                if FileManager.default.fileExists(atPath: preServerScript) {
                    self.runShell("cd '\(webDir)' && nohup bash '\(preServerScript)' > /tmp/pre-desktop.log 2>&1 &")
                } else {
                    self.runShell("cd '\(webDir)' && nohup node server.js > /tmp/pre-desktop.log 2>&1 &")
                }

                // Wait up to 30s for server to be healthy
                DispatchQueue.main.async { onProgress("Loading model...") }
                var serverOk = false
                for _ in 0..<30 {
                    Thread.sleep(forTimeInterval: 1)
                    let sem = DispatchSemaphore(value: 0)
                    self.isServerRunning { ok in serverOk = ok; sem.signal() }
                    sem.wait()
                    if serverOk { break }
                }

                DispatchQueue.main.async {
                    completion(serverOk)
                }
            }
        }
    }

    func stopServer() {
        runShell("lsof -ti :\(WEB_PORT) -sTCP:LISTEN | xargs kill 2>/dev/null")
    }

    private func checkHTTP(url: String, completion: @escaping (Bool) -> Void) {
        guard let reqURL = URL(string: url) else {
            completion(false)
            return
        }
        var request = URLRequest(url: reqURL)
        request.timeoutInterval = 3
        request.httpMethod = "GET"
        URLSession.shared.dataTask(with: request) { _, response, error in
            if let http = response as? HTTPURLResponse, error == nil {
                completion((200...399).contains(http.statusCode))
            } else {
                completion(false)
            }
        }.resume()
    }

    @discardableResult
    func runShell(_ command: String) -> String {
        let task = Process()
        let pipe = Pipe()
        task.standardOutput = pipe
        task.standardError = pipe
        task.executableURL = URL(fileURLWithPath: "/bin/zsh")
        task.arguments = ["-c", command]
        task.environment = ProcessInfo.processInfo.environment
        do {
            try task.run()
            task.waitUntilExit()
            let data = pipe.fileHandleForReading.readDataToEndOfFile()
            return String(data: data, encoding: .utf8) ?? ""
        } catch {
            return ""
        }
    }
}

// ── Loading View ───────────────────────────────────────────────────────────

class LoadingView: NSView {
    private let statusLabel = NSTextField(labelWithString: "Starting PRE...")
    private let titleLabel = NSTextField(labelWithString: "PRE")

    override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        setup()
    }

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

    private func setup() {
        wantsLayer = true
        layer?.backgroundColor = NSColor(red: 0.039, green: 0.039, blue: 0.039, alpha: 1.0).cgColor

        // Title
        titleLabel.font = NSFont.systemFont(ofSize: 48, weight: .bold)
        titleLabel.textColor = NSColor(red: 0.424, green: 0.706, blue: 0.933, alpha: 1.0) // #6cb4ee
        titleLabel.alignment = .center
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(titleLabel)

        // Status
        statusLabel.font = NSFont.systemFont(ofSize: 14, weight: .regular)
        statusLabel.textColor = NSColor(white: 0.5, alpha: 1.0)
        statusLabel.alignment = .center
        statusLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(statusLabel)

        NSLayoutConstraint.activate([
            titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -20),
            statusLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            statusLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12),
        ])
    }

    func setStatus(_ text: String) {
        statusLabel.stringValue = text
    }
}

// ── App Delegate ───────────────────────────────────────────────────────────

class PreDesktopApp: NSObject, NSApplicationDelegate, WKNavigationDelegate, WKUIDelegate {
    var window: NSWindow!
    var webView: WKWebView!
    var loadingView: LoadingView!
    let serverManager = ServerManager()
    var healthTimer: Timer?

    func applicationDidFinishLaunching(_ notification: Notification) {
        setupMenuBar()
        createWindow()
        startLoading()
    }

    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
        return true
    }

    func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
        healthTimer?.invalidate()

        if !serverManager.serverStartedByUs {
            return .terminateNow
        }

        // If LaunchAgent is loaded, it will manage the server — just quit
        let plistPath = homeDir + "/Library/LaunchAgents/com.pre.server.plist"
        if FileManager.default.fileExists(atPath: plistPath) {
            return .terminateNow
        }

        // We started the server and there's no LaunchAgent — ask the user
        let alert = NSAlert()
        alert.messageText = "Quit PRE"
        alert.informativeText = "The PRE server is still running. Do you want to stop it?"
        alert.addButton(withTitle: "Quit & Stop Server")
        alert.addButton(withTitle: "Quit (Keep Running)")
        alert.addButton(withTitle: "Cancel")

        let response = alert.runModal()
        switch response {
        case .alertFirstButtonReturn:
            serverManager.stopServer()
            return .terminateNow
        case .alertSecondButtonReturn:
            return .terminateNow
        default:
            return .terminateCancel
        }
    }

    // ── Window ─────────────────────────────────────────────────────────────

    func createWindow() {
        window = NSWindow(
            contentRect: NSRect(x: 0, y: 0, width: 1280, height: 820),
            styleMask: [.titled, .closable, .miniaturizable, .resizable],
            backing: .buffered,
            defer: false
        )
        window.title = "PRE"
        window.titlebarAppearsTransparent = true
        window.backgroundColor = NSColor(red: 0.039, green: 0.039, blue: 0.039, alpha: 1.0)
        window.setFrameAutosaveName("PREDesktopMainWindow")
        window.minSize = NSSize(width: 800, height: 500)
        window.center()

        // Loading view (shown until server is ready)
        loadingView = LoadingView(frame: window.contentView!.bounds)
        loadingView.autoresizingMask = [.width, .height]
        window.contentView?.addSubview(loadingView)

        // WebView (hidden until loaded)
        let config = WKWebViewConfiguration()
        config.preferences.setValue(true, forKey: "developerExtrasEnabled")
        config.defaultWebpagePreferences.allowsContentJavaScript = true
        config.websiteDataStore = .default()

        webView = WKWebView(frame: window.contentView!.bounds, configuration: config)
        webView.autoresizingMask = [.width, .height]
        webView.navigationDelegate = self
        webView.uiDelegate = self
        webView.allowsMagnification = true
        webView.allowsBackForwardNavigationGestures = false
        webView.isHidden = true
        webView.customUserAgent = "PRE-Desktop/2.0"
        // Match dark theme background to avoid white flash
        webView.setValue(false, forKey: "drawsBackground")
        window.contentView?.addSubview(webView)

        window.makeKeyAndOrderFront(nil)
        NSApp.activate(ignoringOtherApps: true)
    }

    // ── Server Startup + Load ──────────────────────────────────────────────

    func startLoading() {
        loadingView.setStatus("Connecting to server...")

        serverManager.ensureServer(
            onProgress: { [weak self] status in
                self?.loadingView.setStatus(status)
            },
            completion: { [weak self] ok in
                guard let self = self else { return }
                if ok {
                    self.loadPage()
                } else {
                    self.loadingView.setStatus("Server failed to start. Check /tmp/pre-desktop.log")
                }
            }
        )
    }

    func loadPage() {
        guard let url = URL(string: PRE_URL) else { return }
        webView.load(URLRequest(url: url))

        // Start health monitoring
        healthTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { [weak self] _ in
            self?.serverManager.isServerRunning { ok in
                DispatchQueue.main.async {
                    if !ok {
                        self?.window.title = "PRE (reconnecting...)"
                    } else {
                        self?.window.title = "PRE"
                    }
                }
            }
        }
    }

    // ── WKNavigationDelegate ───────────────────────────────────────────────

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // Page loaded — swap loading view for web view
        webView.isHidden = false
        loadingView.removeFromSuperview()
    }

    func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        loadingView.setStatus("Connection lost. Retrying...")
        // Retry after a delay
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
            self?.loadPage()
        }
    }

    func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        loadingView.setStatus("Waiting for server...")
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
            self?.loadPage()
        }
    }

    // Handle download requests (artifact downloads)
    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse,
                 decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        if let response = navigationResponse.response as? HTTPURLResponse,
           let contentDisp = response.value(forHTTPHeaderField: "Content-Disposition"),
           contentDisp.contains("attachment") {
            // Open download in default browser
            if let url = response.url {
                NSWorkspace.shared.open(url)
            }
            decisionHandler(.cancel)
            return
        }
        decisionHandler(.allow)
    }

    // ── WKUIDelegate ──────────────────────────────────────────────────────

    // Handle window.open() — open in default browser
    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration,
                 for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        if let url = navigationAction.request.url {
            NSWorkspace.shared.open(url)
        }
        return nil
    }

    // Handle JS alert()
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
        let alert = NSAlert()
        alert.messageText = "PRE"
        alert.informativeText = message
        alert.addButton(withTitle: "OK")
        alert.runModal()
        completionHandler()
    }

    // Handle JS confirm()
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String,
                 initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
        let alert = NSAlert()
        alert.messageText = "PRE"
        alert.informativeText = message
        alert.addButton(withTitle: "OK")
        alert.addButton(withTitle: "Cancel")
        completionHandler(alert.runModal() == .alertFirstButtonReturn)
    }

    // Handle JS prompt()
    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String,
                 defaultText: String?, initiatedByFrame frame: WKFrameInfo,
                 completionHandler: @escaping (String?) -> Void) {
        let alert = NSAlert()
        alert.messageText = "PRE"
        alert.informativeText = prompt
        alert.addButton(withTitle: "OK")
        alert.addButton(withTitle: "Cancel")
        let input = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 24))
        input.stringValue = defaultText ?? ""
        alert.accessoryView = input
        if alert.runModal() == .alertFirstButtonReturn {
            completionHandler(input.stringValue)
        } else {
            completionHandler(nil)
        }
    }

    // ── Menu Bar ──────────────────────────────────────────────────────────

    func setupMenuBar() {
        let mainMenu = NSMenu()

        // App menu
        let appMenu = NSMenu()
        appMenu.addItem(NSMenuItem(title: "About PRE", action: #selector(showAbout), keyEquivalent: ""))
        appMenu.addItem(.separator())
        appMenu.addItem(NSMenuItem(title: "Quit PRE", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
        let appMenuItem = NSMenuItem()
        appMenuItem.submenu = appMenu
        mainMenu.addItem(appMenuItem)

        // Edit menu (required for Cmd+C/V/X/A to work in WKWebView)
        let editMenu = NSMenu(title: "Edit")
        editMenu.addItem(NSMenuItem(title: "Undo", action: Selector(("undo:")), keyEquivalent: "z"))
        editMenu.addItem(NSMenuItem(title: "Redo", action: Selector(("redo:")), keyEquivalent: "Z"))
        editMenu.addItem(.separator())
        editMenu.addItem(NSMenuItem(title: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x"))
        editMenu.addItem(NSMenuItem(title: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c"))
        editMenu.addItem(NSMenuItem(title: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v"))
        editMenu.addItem(NSMenuItem(title: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a"))
        let editMenuItem = NSMenuItem()
        editMenuItem.submenu = editMenu
        mainMenu.addItem(editMenuItem)

        // View menu
        let viewMenu = NSMenu(title: "View")
        viewMenu.addItem(NSMenuItem(title: "Reload", action: #selector(reloadPage), keyEquivalent: "r"))
        viewMenu.addItem(.separator())
        let fullScreenItem = NSMenuItem(title: "Toggle Full Screen", action: #selector(NSWindow.toggleFullScreen(_:)), keyEquivalent: "f")
        fullScreenItem.keyEquivalentModifierMask = [.command, .control]
        viewMenu.addItem(fullScreenItem)
        let viewMenuItem = NSMenuItem()
        viewMenuItem.submenu = viewMenu
        mainMenu.addItem(viewMenuItem)

        // Window menu
        let windowMenu = NSMenu(title: "Window")
        windowMenu.addItem(NSMenuItem(title: "Minimize", action: #selector(NSWindow.performMiniaturize(_:)), keyEquivalent: "m"))
        windowMenu.addItem(NSMenuItem(title: "Zoom", action: #selector(NSWindow.performZoom(_:)), keyEquivalent: ""))
        windowMenu.addItem(.separator())
        windowMenu.addItem(NSMenuItem(title: "Close Window", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w"))
        let windowMenuItem = NSMenuItem()
        windowMenuItem.submenu = windowMenu
        mainMenu.addItem(windowMenuItem)

        NSApp.mainMenu = mainMenu
    }

    @objc func reloadPage() {
        webView?.reload()
    }

    @objc func showAbout() {
        let alert = NSAlert()
        alert.messageText = "PRE — Personal Reasoning Engine"
        alert.informativeText = "A fully local agentic AI assistant.\n\nVersion 2.0\nhttps://github.com/sunkencity999/pre"
        alert.alertStyle = .informational
        alert.addButton(withTitle: "OK")
        alert.runModal()
    }
}

// ── Main ────────────────────────────────────────────────────────────────────

let app = NSApplication.shared
app.setActivationPolicy(.regular) // Show in Dock
let delegate = PreDesktopApp()
app.delegate = delegate
app.run()
