import QtQuick
import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services
import "../../../Helpers/FuzzySort.js" as Fuzzysort

Item {
  property var launcher: null
  property string name: "Applications"
  property bool handleSearch: true
  property var entries: []

  // Persistent usage tracking stored in cacheDir
  property string usageFilePath: Settings.cacheDir + "launcher_app_usage.json"

  // Debounced saver to avoid excessive IO
  Timer {
    id: saveTimer
    interval: 750
    repeat: false
    onTriggered: usageFile.writeAdapter()
  }

  FileView {
    id: usageFile
    path: usageFilePath
    printErrors: false
    watchChanges: false

    onLoadFailed: function (error) {
      if (error.toString().includes("No such file") || error === 2) {
        writeAdapter()
      }
    }

    onAdapterUpdated: saveTimer.start()

    JsonAdapter {
      id: usageAdapter
      // key: app id/command, value: integer count
      property var counts: ({})
    }
  }

  function init() {
    loadApplications()
  }

  function onOpened() {
    // Refresh apps when launcher opens
    loadApplications()
  }

  function loadApplications() {
    if (typeof DesktopEntries === 'undefined') {
      Logger.warn("ApplicationsPlugin", "DesktopEntries service not available")
      return
    }

    const allApps = DesktopEntries.applications.values || []
    entries = allApps.filter(app => app && !app.noDisplay)
    Logger.log("ApplicationsPlugin", `Loaded ${entries.length} applications`)
  }

  function getResults(query) {
    if (!entries || entries.length === 0)
      return []

    if (!query || query.trim() === "") {
      // Return all apps, optionally sorted by usage
      let sorted
      if (Settings.data.appLauncher.sortByMostUsed) {
        sorted = entries.slice().sort((a, b) => {
                                        const ua = getUsageCount(a)
                                        const ub = getUsageCount(b)
                                        if (ub !== ua)
                                        return ub - ua
                                        return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase())
                                      })
      } else {
        sorted = entries.slice().sort((a, b) => (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase()))
      }
      return sorted.map(app => createResultEntry(app))
    }

    // Use fuzzy search if available, fallback to simple search
    if (typeof Fuzzysort !== 'undefined') {
      const fuzzyResults = Fuzzysort.go(query, entries, {
                                          "keys": ["name", "comment", "genericName"],
                                          "threshold": -1000,
                                          "limit": 20
                                        })

      return fuzzyResults.map(result => createResultEntry(result.obj))
    } else {
      // Fallback to simple search
      const searchTerm = query.toLowerCase()
      return entries.filter(app => {
                              const name = (app.name || "").toLowerCase()
                              const comment = (app.comment || "").toLowerCase()
                              const generic = (app.genericName || "").toLowerCase()
                              return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm)
                            }).sort((a, b) => {
                                      // Prioritize name matches
                                      const aName = a.name.toLowerCase()
                                      const bName = b.name.toLowerCase()
                                      const aStarts = aName.startsWith(searchTerm)
                                      const bStarts = bName.startsWith(searchTerm)
                                      if (aStarts && !bStarts)
                                      return -1
                                      if (!aStarts && bStarts)
                                      return 1
                                      return aName.localeCompare(bName)
                                    }).slice(0, 20).map(app => createResultEntry(app))
    }
  }

  function createResultEntry(app) {
    return {
      "name": app.name || "Unknown",
      "description": app.genericName || app.comment || "",
      "icon": app.icon || "application-x-executable",
      "isImage": false,
      "onActivate": function () {
        // Close the launcher/NPanel immediately without any animations.
        // Ensures we are not preventing the future focusing of the app
        launcher.closeCompleted()

        Logger.log("ApplicationsPlugin", `Launching: ${app.name}`)
        // Record usage and persist asynchronously
        if (Settings.data.appLauncher.sortByMostUsed)
          recordUsage(app)
        if (Settings.data.appLauncher.useApp2Unit && app.id) {
          Logger.log("ApplicationsPlugin", `Using app2unit for: ${app.id}`)
          if (app.runInTerminal)
            Quickshell.execDetached(["app2unit", "--", app.id + ".desktop"])
          else
            Quickshell.execDetached(["app2unit", "--"].concat(app.command))
        } else if (app.execute) {
          app.execute()
        } else {
          Logger.log("ApplicationsPlugin", `Could not launch: ${app.name}`)
        }
      }
    }
  }

  // -------------------------
  // Usage tracking helpers
  function getAppKey(app) {
    if (app && app.id)
      return String(app.id)
    if (app && app.command && app.command.join)
      return app.command.join(" ")
    return String(app && app.name ? app.name : "unknown")
  }

  function getUsageCount(app) {
    const key = getAppKey(app)
    const m = usageAdapter && usageAdapter.counts ? usageAdapter.counts : null
    if (!m)
      return 0
    const v = m[key]
    return typeof v === 'number' && isFinite(v) ? v : 0
  }

  function recordUsage(app) {
    const key = getAppKey(app)
    if (!usageAdapter.counts)
      usageAdapter.counts = ({})
    const current = getUsageCount(app)
    usageAdapter.counts[key] = current + 1
    // Trigger save via debounced timer
    saveTimer.restart()
  }
}
