Skip to content
import QtQuick
import Quickshell
import Quickshell.Io
import "Model.js" as Model

Item {
  id: root

  property var shell: null

  readonly property string home: Quickshell.env("HOME")
  readonly property string configDir: home + "/.config/notify"
  readonly property string mainConfigPath: configDir + "/notify.json"
  readonly property string stateDir: home + "/.local/state/omarchy/notify-desktop"

  readonly property int pollSeconds: {
    var n = parseInt(Quickshell.env("NOTIFY_POLL_SECONDS"))
    return (isFinite(n) && n > 0) ? n : 5
  }

  readonly property var envServer: ({
    url: Quickshell.env("NOTIFY_URL") || "http://127.0.0.1:8080",
    pin: Quickshell.env("NOTIFY_PIN") || "123456",
    name: ""
  })

  property var servers: [envServer]
  property string activeProfile: ""
  property var scanQueue: []
  property var currentServer: null

  readonly property var icons: ({ heart: "\uF004", emergency: "\uF06A" })

  function scanCommand() {
    var dir = Model.shellQuote(root.configDir)
    var main = Model.shellQuote(root.mainConfigPath)
    return "mkdir -p " + dir + "; printf '@@CONFIG\\n'; cat " + main + " 2>/dev/null || true; printf '\\n'; "
      + "for f in " + dir + "/*.json; do "
      + "[ \"$f\" = " + main + " ] && continue; "
      + "[ -f \"$f\" ] || continue; printf '@@FILE %s\\n' \"${f##*/}\"; cat \"$f\"; printf '\\n'; "
      + "done"
  }

  function applyConfig(content) {
    var dump = Model.parseDump(content)
    var profile = Model.byName(dump.profiles, dump.active)
    if (!profile && dump.profiles.length > 0) profile = dump.profiles[0]
    root.activeProfile = profile ? profile.name : ""
    root.servers = profile && profile.servers.length > 0 ? profile.servers : [root.envServer]
    root.scanQueue = root.servers.slice()
  }

  function nextScan() {
    if (root.scanQueue.length === 0) return
    root.currentServer = root.scanQueue.shift()
    var server = root.currentServer
    var pin = (server && (server.pin || server.api_key)) || ""
    pollProc.command = [
      "curl", "-sS", "-m", "10",
      "-H", "X-PIN: " + pin,
      server.url + "/api/notifications/scan"
    ]
    pollProc.running = true
  }

  Process {
    id: configScanProc
    command: ["sh", "-c", root.scanCommand()]
    stdout: StdioCollector {
      waitForEnd: true
      onStreamFinished: {
        root.applyConfig(text)
        root.nextScan()
      }
    }
  }

  Process {
    id: ensureDirsProc
    command: ["mkdir", "-p", root.stateDir, root.configDir]
    onExited: configScanProc.running = true
  }

  Component.onCompleted: ensureDirsProc.running = true

  FileView {
    id: latestFile
    path: root.stateDir + "/latest.json"
    atomicWrites: true
    printErrors: false
  }

  Timer {
    id: pollTimer
    interval: root.pollSeconds * 1000
    running: true
    repeat: true
    triggeredOnStart: true
    onTriggered: if (!configScanProc.running && !pollProc.running) configScanProc.running = true
  }

  function labelFor(server) { return server && server.name ? server.name : "Notify" }

  function showNotification(server, n) {
    var argv = ["omarchy-notification-send", "-u", n.kind === "emergency" ? "critical" : "normal"]
    if (n.kind === "emergency") argv.push("-g", root.icons.emergency)
    argv.push("--app-name", n.kind === "emergency" ? "notify-send" : root.labelFor(server), n.message || "Notification")
    try { Quickshell.execDetached(argv); return true } catch (e) { return false }
  }

  function handleScan(server, body) {
    if (!server || !body) return
    var items
    try { items = JSON.parse(body) } catch (e) { return }
    if (!Array.isArray(items) || items.length === 0) return
    var shownIds = []
    var lastShown = null
    for (var i = 0; i < items.length; i++) {
      var n = items[i]
      if (n && typeof n.id === "number" && root.showNotification(server, n)) {
        shownIds.push(n.id)
        lastShown = n
      }
    }
    if (shownIds.length > 0) {
      latestFile.setText(JSON.stringify({ kind: String(lastShown.kind || ""), message: String(lastShown.message || ""), server: root.labelFor(server), ts: Date.now() }) + "\n")
      var pin = (server && (server.pin || server.api_key)) || ""
      ackProc.command = ["curl", "-sS", "-m", "10", "-X", "POST", "-H", "X-PIN: " + pin, "-H", "Content-Type: application/json", "-d", JSON.stringify({ ids: shownIds }), server.url + "/api/notifications/sent"]
      ackProc.running = true
    }
  }

  Process {
    id: pollProc
    property string buffer: ""
    stdout: SplitParser { onRead: function(data) { pollProc.buffer += data } }
    onRunningChanged: if (running) pollProc.buffer = ""
    onExited: function(exitCode) {
      if (exitCode === 0) root.handleScan(root.currentServer, pollProc.buffer)
      pollProc.buffer = ""
      if (!ackProc.running) root.nextScan()
    }
  }

  Process {
    id: ackProc
    command: ["true"]
    onExited: root.nextScan()
  }
}