import AppKit
import Combine
import Foundation
import WebKit

/// Loads `keep.google.com` in a hidden `WKWebView` (shared `WKWebsiteDataStore.default()` cookies with the large widget)
/// and extracts recent note titles/snippets via JavaScript. No OAuth or Workspace API.
@MainActor
final class KeepLiveNotesStore: NSObject, ObservableObject {
    static let shared = KeepLiveNotesStore()

    struct Note: Identifiable, Equatable {
        let id: String
        let title: String
        let snippet: String
    }

    enum Phase: Equatable {
        case idle
        case loading
        case ready
        case empty
        case needsSignIn
        case failed(String)
    }

    @Published private(set) var phase: Phase = .idle
    @Published private(set) var notes: [Note] = []
    @Published private(set) var lastFetchedAt: Date?

    private var webView: WKWebView?
    private var hiddenWindow: NSWindow?
    private var extractionAttempt = 0
    private var pendingReload = false
    private let keepURL = URL(string: "https://keep.google.com/")!
    private let minRefreshInterval: TimeInterval = 45

    private override init() {
        super.init()
    }

    /// Call when the small/medium Keep widget appears.
    func requestRefreshIfNeeded() {
        let now = Date()
        if case .loading = phase { return }
        if let t = lastFetchedAt, now.timeIntervalSince(t) < minRefreshInterval {
            switch phase {
            case .ready, .empty: return
            default: break
            }
        }
        reloadAndExtract()
    }

    func forceRefresh() {
        lastFetchedAt = nil
        reloadAndExtract()
    }

    private func reloadAndExtract() {
        ensureWebView()
        guard let wv = webView else {
            phase = .failed("Could not start web view")
            return
        }
        extractionAttempt = 0
        phase = .loading
        pendingReload = true
        wv.load(URLRequest(url: keepURL))
    }

    private func ensureWebView() {
        guard webView == nil else { return }
        let config = WKWebViewConfiguration()
        config.defaultWebpagePreferences.allowsContentJavaScript = true
        let wv = WKWebView(frame: CGRect(x: 0, y: 0, width: 900, height: 800), configuration: config)
        wv.navigationDelegate = self
        wv.customUserAgent =
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15"
        let view = NSView(frame: NSRect(x: 0, y: 0, width: 900, height: 800))
        wv.frame = view.bounds
        wv.autoresizingMask = [.width, .height]
        view.addSubview(wv)
        let window = NSWindow(
            contentRect: NSRect(x: -2400, y: -2400, width: 900, height: 800),
            styleMask: [.borderless],
            backing: .buffered,
            defer: false
        )
        window.isOpaque = false
        window.backgroundColor = .clear
        window.contentView = view
        window.alphaValue = 0
        window.ignoresMouseEvents = true
        window.orderOut(nil)
        hiddenWindow = window
        webView = wv
    }

    private func scheduleExtraction(after delay: TimeInterval) {
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
            guard let self else { return }
            Task { @MainActor in
                self.runExtractionPass()
            }
        }
    }

    private func runExtractionPass() {
        guard let wv = webView else { return }
        extractionAttempt += 1
        wv.evaluateJavaScript(Self.extractNotesJavaScript) { [weak self] result, error in
            Task { @MainActor in
                guard let self else { return }
                if let error {
                    self.phase = .failed(error.localizedDescription)
                    return
                }
                guard let jsonString = result as? String,
                      let data = jsonString.data(using: .utf8)
                else {
                    self.phase = .failed("Unexpected response")
                    return
                }
                self.applyExtractedPayload(data)
            }
        }
    }

    private func applyExtractedPayload(_ data: Data) {
        struct Payload: Decodable {
            var notes: [PayloadNote]?
            var needsSignIn: Bool?
            var error: String?
        }
        struct PayloadNote: Decodable {
            var id: String
            var title: String
            var snippet: String
        }

        let decoder = JSONDecoder()
        guard let payload = try? decoder.decode(Payload.self, from: data) else {
            if extractionAttempt < 4 {
                scheduleExtraction(after: 2.0)
            } else {
                phase = .failed("Could not read Keep")
            }
            return
        }

        if payload.needsSignIn == true {
            phase = .needsSignIn
            notes = []
            lastFetchedAt = Date()
            return
        }

        if let err = payload.error, !err.isEmpty {
            if extractionAttempt < 4 {
                scheduleExtraction(after: 2.0)
            } else {
                phase = .failed(err)
            }
            return
        }

        let raw = payload.notes ?? []
        let mapped = raw.map { n in
            Note(
                id: n.id,
                title: String(n.title.prefix(200)),
                snippet: String(n.snippet.prefix(500))
            )
        }
        let deduped = dedupe(mapped)

        if deduped.isEmpty {
            if extractionAttempt < 4 {
                scheduleExtraction(after: 2.0)
            } else {
                phase = .empty
                notes = []
                lastFetchedAt = Date()
            }
            return
        }

        notes = deduped
        phase = .ready
        lastFetchedAt = Date()
    }

    private func dedupe(_ items: [Note]) -> [Note] {
        var seen = Set<String>()
        var out: [Note] = []
        for n in items {
            let key = "\(n.title)|\(n.snippet)"
            if seen.contains(key) { continue }
            seen.insert(key)
            out.append(n)
        }
        return out
    }

    /// JavaScript: prefer `[role="article"]`, then `contenteditable="false"` pairs (classic Keep export pattern).
    private static let extractNotesJavaScript = #"""
(function() {
  try {
    var href = window.location.href || '';
    if (href.indexOf('accounts.google') !== -1) {
      return JSON.stringify({ needsSignIn: true });
    }
    var results = [];
    var articles = document.querySelectorAll('[role="article"]');
    var i = 0;
    if (articles.length > 0) {
      for (i = 0; i < articles.length && results.length < 24; i++) {
        var el = articles[i];
        var text = (el.innerText || '').trim();
        if (!text) continue;
        var lines = text.split(/\r?\n/).map(function(l) { return l.trim(); }).filter(function(l) { return l.length > 0; });
        if (lines.length === 0) continue;
        var title = lines[0].substring(0, 160);
        var snippet = lines.slice(1).join(' ').substring(0, 400);
        results.push({ id: 'a-' + i, title: title, snippet: snippet });
      }
      return JSON.stringify({ notes: results });
    }
    var all = document.getElementsByTagName('*');
    var titles = [];
    var contents = [];
    var x = 0;
    for (i = 0; i < all.length; i++) {
      var node = all[i];
      if (node.getAttribute && node.getAttribute('contenteditable') === 'false') {
        var txt = (node.innerText || '').trim();
        if (x % 2 === 0) titles.push(txt);
        else contents.push(txt);
        x++;
      }
    }
    if (x > 200) {
      return JSON.stringify({ notes: [], error: 'unexpected_dom' });
    }
    var n = Math.min(titles.length, contents.length, 12);
    for (i = 0; i < n; i++) {
      var ti = (titles[i] || 'Note').substring(0, 160);
      var sn = (contents[i] || '').substring(0, 400);
      results.push({ id: 'c-' + i, title: ti, snippet: sn });
    }
    return JSON.stringify({ notes: results });
  } catch (e) {
    return JSON.stringify({ error: String(e) });
  }
})();
"""#
}

extension KeepLiveNotesStore: WKNavigationDelegate {
    nonisolated func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        Task { @MainActor in
            guard self.pendingReload else { return }
            self.pendingReload = false
            self.extractionAttempt = 0
            self.scheduleExtraction(after: 2.0)
        }
    }

    nonisolated func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        Task { @MainActor in
            self.phase = .failed(error.localizedDescription)
        }
    }

    nonisolated func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        Task { @MainActor in
            self.phase = .failed(error.localizedDescription)
        }
    }
}
