import QtQuick
import Quickshell
import qs.Commons
import qs.Services.Compositor
import qs.Services.System

Item {
  id: root

  property var launcher: null
  property string name: I18n.tr("launcher.providers.applications")
  property bool handleSearch: true
  property var entries: []
  property string supportedLayouts: "both"
  property bool isDefaultProvider: true // This provider handles empty search
  property bool ignoreDensity: false // Apps should scale with launcher density
  property bool trackUsage: true // Track usage frequency for "most used" sorting

  // Category support
  property string selectedCategory: "all"
  property bool showsCategories: true // Default to showing categories
  property var categories: ["all", "Pinned", "AudioVideo", "Chat", "Development", "Education", "Game", "Graphics", "Network", "Office", "System", "Misc", "WebBrowser"]
  property var availableCategories: ["all"] // Reactive property for available categories

  property var categoryIcons: ({
                                 "all": "apps",
                                 "Pinned": "pin",
                                 "AudioVideo": "music",
                                 "Chat": "message-circle",
                                 "Development": "code",
                                 "Education": "school" // Includes Science
                                              ,
                                 "Game": "device-gamepad",
                                 "Graphics": "brush",
                                 "Network": "wifi",
                                 "Office": "file-text",
                                 "System": "device-desktop" // Includes Settings and Utility
                                           ,
                                 "Misc": "dots",
                                 "WebBrowser": "world"
                               })

  function getCategoryName(category) {
    const names = {
      "all": I18n.tr("launcher.categories.all"),
      "Pinned": I18n.tr("launcher.categories.pinned"),
      "AudioVideo": I18n.tr("launcher.categories.audiovideo"),
      "Chat": I18n.tr("launcher.categories.chat"),
      "Development": I18n.tr("launcher.categories.development"),
      "Education": I18n.tr("launcher.categories.education"),
      "Game": I18n.tr("launcher.categories.game"),
      "Graphics": I18n.tr("launcher.categories.graphics"),
      "Network": I18n.tr("common.network"),
      "Office": I18n.tr("launcher.categories.office"),
      "System": I18n.tr("launcher.categories.system"),
      "Misc": I18n.tr("launcher.categories.misc"),
      "WebBrowser": I18n.tr("launcher.categories.webbrowser")
    };
    return names[category] || category;
  }

  function init() {
    loadApplications();
    migrateLegacyUsageKeys();
  }

  function onOpened() {
    // Just update available categories in case pinned apps changed
    updateAvailableCategories();
    // Default to Pinned if there are pinned apps, otherwise all
    if (availableCategories.includes("Pinned")) {
      selectedCategory = "Pinned";
    } else {
      selectedCategory = "all";
    }
    // Set category mode initially (will be updated when getResults is called)
    showsCategories = true;
  }

  // Reload applications when desktop entries change on disk
  Connections {
    target: typeof DesktopEntries !== 'undefined' ? DesktopEntries.applications : null
    function onValuesChanged() {
      Logger.d("ApplicationsProvider", "Desktop entries changed, reloading applications");
      loadApplications();
    }
  }

  function selectCategory(category) {
    selectedCategory = category;
    if (launcher) {
      launcher.updateResults();
    }
  }

  function getAppCategories(app) {
    if (!app)
      return [];

    const result = [];

    if (app.categories) {
      if (Array.isArray(app.categories)) {
        for (let cat of app.categories) {
          if (cat && cat.trim && cat.trim() !== '') {
            result.push(cat.trim());
          } else if (cat && typeof cat === 'string' && cat.trim() !== '') {
            result.push(cat.trim());
          }
        }
      } else if (typeof app.categories === 'string') {
        const cats = app.categories.split(';').filter(c => c && c.trim() !== '');
        for (let cat of cats) {
          const trimmed = cat.trim();
          if (trimmed && !result.includes(trimmed)) {
            result.push(trimmed);
          }
        }
      } else if (app.categories.length !== undefined) {
        try {
          for (let i = 0; i < app.categories.length; i++) {
            const cat = app.categories[i];
            if (cat && cat.trim && typeof cat.trim === 'function' && cat.trim() !== '') {
              result.push(cat.trim());
            } else if (cat && typeof cat === 'string' && cat.trim() !== '') {
              result.push(cat.trim());
            }
          }
        } catch (e) {}
      }
    }

    if (app.Categories) {
      const cats = app.Categories.split(';').filter(c => c && c.trim() !== '');
      for (let cat of cats) {
        const trimmed = cat.trim();
        if (trimmed && !result.includes(trimmed)) {
          result.push(trimmed);
        }
      }
    }

    return result;
  }

  function getAppCategory(app) {
    const appCategories = getAppCategories(app);
    if (appCategories.length === 0)
      return null;

    const priorityCategories = ["AudioVideo", "Chat", "WebBrowser", "Game", "Development", "Graphics", "Office", "Education", "System", "Network", "Misc"];

    for (let cat of appCategories) {
      if (cat === "AudioVideo" || cat === "Audio" || cat === "Video") {
        return "AudioVideo";
      }
    }

    if (appCategories.includes("Chat") || appCategories.includes("InstantMessaging")) {
      return "Chat";
    }

    if (appCategories.includes("WebBrowser")) {
      return "WebBrowser";
    }

    // Map Science to Education
    if (appCategories.includes("Science")) {
      return "Education";
    }

    // Map Settings to System
    if (appCategories.includes("Settings")) {
      return "System";
    }

    // Map Utility to System
    if (appCategories.includes("Utility")) {
      return "System";
    }

    for (let priorityCat of priorityCategories) {
      if (appCategories.includes(priorityCat) && root.categories.includes(priorityCat)) {
        return priorityCat;
      }
    }

    return "Misc";
  }

  // Helper function to normalize app IDs for case-insensitive matching
  function normalizeAppId(appId) {
    if (!appId || typeof appId !== 'string')
      return "";
    return appId.toLowerCase().trim();
  }

  // Helper function to check if an app is pinned
  function isAppPinned(app) {
    if (!app)
      return false;
    const pinnedApps = Settings.data.appLauncher.pinnedApps || [];
    const appId = getAppKey(app);
    const normalizedId = normalizeAppId(appId);
    return pinnedApps.some(pinnedId => normalizeAppId(pinnedId) === normalizedId);
  }

  function appMatchesCategory(app, category) {
    // Check if app matches the selected category
    if (category === "all")
      return true;

    // Handle Pinned category separately
    if (category === "Pinned") {
      return isAppPinned(app);
    }

    // Get the primary category for this app (first matching standard category)
    const primaryCategory = getAppCategory(app);

    // If app has no matching standard category, don't show it in any category (only in "all")
    if (!primaryCategory)
      return false;

    // Map Audio/Video to AudioVideo
    if (category === "AudioVideo") {
      const appCategories = getAppCategories(app);
      // Show if app has AudioVideo, Audio, or Video
      return appCategories.includes("AudioVideo") || appCategories.includes("Audio") || appCategories.includes("Video");
    }

    // Map Science to Education
    if (category === "Education") {
      const appCategories = getAppCategories(app);
      return appCategories.includes("Education") || appCategories.includes("Science");
    }

    // Map Settings and Utility to System
    if (category === "System") {
      const appCategories = getAppCategories(app);
      return appCategories.includes("System") || appCategories.includes("Settings") || appCategories.includes("Utility");
    }

    // Only show app in its primary category to avoid overlap
    // This ensures each app appears in exactly one category tab
    return category === primaryCategory;
  }

  function getAvailableCategories() {
    const categorySet = new Set();
    let hasAudioVideo = false;
    let hasEducation = false;
    let hasSystem = false;
    let hasPinned = false;

    // Check if there are any pinned apps
    const pinnedApps = Settings.data.appLauncher.pinnedApps || [];
    if (pinnedApps.length > 0) {
      // Verify that at least one pinned app exists in entries
      for (let app of entries) {
        if (isAppPinned(app)) {
          hasPinned = true;
          break;
        }
      }
    }

    for (let app of entries) {
      const appCategories = getAppCategories(app);
      const primaryCategory = getAppCategory(app);

      if (appCategories.includes("AudioVideo") || appCategories.includes("Audio") || appCategories.includes("Video")) {
        hasAudioVideo = true;
      } else if (appCategories.includes("Education") || appCategories.includes("Science")) {
        hasEducation = true;
      } else if (appCategories.includes("System") || appCategories.includes("Settings") || appCategories.includes("Utility")) {
        hasSystem = true;
      } else if (primaryCategory && root.categories.includes(primaryCategory)) {
        categorySet.add(primaryCategory);
      }
    }

    const result = [];

    // Add Pinned category first if there are pinned apps
    if (hasPinned) {
      result.push("Pinned");
    }

    result.push("all");

    if (hasAudioVideo) {
      categorySet.add("AudioVideo");
    }
    if (hasEducation) {
      categorySet.add("Education");
    }
    if (hasSystem) {
      categorySet.add("System");
    }

    for (let cat of root.categories) {
      if (cat !== "all" && cat !== "Pinned" && cat !== "Misc" && categorySet.has(cat)) {
        result.push(cat);
      }
    }

    if (categorySet.has("Misc")) {
      result.push("Misc");
    }

    if (result.length === 1) {
      const fallback = root.categories.filter(c => c !== "Misc");
      fallback.push("Misc");
      return fallback;
    }

    return result;
  }

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

    const allApps = DesktopEntries.applications.values || [];
    const seen = new Map(); // Map of appId -> exec command

    entries = allApps.filter(app => {
                               if (!app || app.noDisplay || app.hidden)
                               return false;

                               const appId = app.id || app.name;
                               const execCmd = getExecutableName(app);

                               // Check if we've seen this app ID before
                               if (seen.has(appId)) {
                                 const previousExec = seen.get(appId);

                                 // If exec is different, it's a legitimate different entry - keep it
                                 if (previousExec !== execCmd) {
                                   Logger.d("ApplicationsProvider", `Keeping variant of ${appId}: ${execCmd} (differs from ${previousExec})`);
                                   // Add with modified ID to make it unique
                                   app.id = `${appId}_${execCmd}`;
                                   seen.set(app.id, execCmd);
                                   return true;
                                 }

                                 // Same appId AND same exec = true duplicate, skip it
                                 Logger.d("ApplicationsProvider", `Skipping duplicate: ${appId}`);
                                 return false;
                               }

                               seen.set(appId, execCmd);
                               return true;
                             }).map(app => {
                                      app.executableName = getExecutableName(app);
                                      return app;
                                    });

    Logger.d("ApplicationsProvider", `Loaded ${entries.length} applications`);
    updateAvailableCategories();
  }

  function updateAvailableCategories() {
    availableCategories = getAvailableCategories();
  }

  Connections {
    target: Settings.data.appLauncher
    function onPinnedAppsChanged() {
      const wasViewingPinned = selectedCategory === "Pinned";
      updateAvailableCategories();

      // If we were viewing Pinned category and it's no longer available, switch to "all"
      if (wasViewingPinned && !availableCategories.includes("Pinned")) {
        selectedCategory = "all";
      }

      // Update results if we're currently viewing the Pinned category
      if (selectedCategory === "Pinned" && launcher) {
        launcher.updateResults();
      } else if (wasViewingPinned && selectedCategory === "all" && launcher) {
        // Also update results when switching to "all"
        launcher.updateResults();
      }
    }
  }

  function getExecutableName(app) {
    if (!app)
      return "";

    // Try to get executable name from command array
    if (app.command && Array.isArray(app.command) && app.command.length > 0) {
      const cmd = app.command[0];
      // Extract just the executable name from the full path
      const parts = cmd.split('/');
      const executable = parts[parts.length - 1];
      // Remove any arguments or parameters
      return executable.split(' ')[0];
    }

    // Try to get from exec property if available
    if (app.exec) {
      const parts = app.exec.split('/');
      const executable = parts[parts.length - 1];
      return executable.split(' ')[0];
    }

    // Fallback to app id (desktop file name without .desktop)
    if (app.id) {
      return app.id.replace('.desktop', '');
    }

    return "";
  }

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

    // Set category mode based on whether there's a query
    const isSearching = !!(query && query.trim() !== "");
    showsCategories = !isSearching;

    // Filter by category only when NOT searching
    let filteredEntries = entries;
    if (!isSearching && selectedCategory && selectedCategory !== "all") {
      filteredEntries = entries.filter(app => appMatchesCategory(app, selectedCategory));
    }

    if (!query || query.trim() === "") {
      // Return filtered apps, optionally sorted by usage
      let sorted;
      if (Settings.data.appLauncher.sortByMostUsed) {
        sorted = filteredEntries.slice().sort((a, b) => {
                                                // Pinned first
                                                const aPinned = isAppPinned(a);
                                                const bPinned = isAppPinned(b);
                                                if (aPinned !== bPinned)
                                                return aPinned ? -1 : 1;

                                                const ua = getUsageCount(a);
                                                const ub = getUsageCount(b);
                                                if (ub !== ua)
                                                return ub - ua;
                                                return (a.name || "").toLowerCase().localeCompare((b.name || "").toLowerCase());
                                              });
      } else {
        sorted = filteredEntries.slice().sort((a, b) => {
                                                const aPinned = isAppPinned(a);
                                                const bPinned = isAppPinned(b);
                                                if (aPinned !== bPinned)
                                                return aPinned ? -1 : 1;
                                                return (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, filteredEntries, {
                                          "keys": ["name", "comment", "genericName", "executableName"],
                                          "limit": 20
                                        });

      // Sort pinned first within fuzzy results while preserving fuzzysort order otherwise
      const pinned = [];
      const nonPinned = [];
      for (const r of fuzzyResults) {
        const app = r.obj;
        if (isAppPinned(app))
          pinned.push(r);
        else
          nonPinned.push(r);
      }
      return pinned.concat(nonPinned).map(result => createResultEntry(result.obj, result.score));
    } else {
      // Fallback to simple search
      const searchTerm = query.toLowerCase();
      return filteredEntries.filter(app => {
                                      const name = (app.name || "").toLowerCase();
                                      const comment = (app.comment || "").toLowerCase();
                                      const generic = (app.genericName || "").toLowerCase();
                                      const executable = getExecutableName(app).toLowerCase();
                                      return name.includes(searchTerm) || comment.includes(searchTerm) || generic.includes(searchTerm) || executable.includes(searchTerm);
                                    }).sort((a, b) => {
                                              // Prioritize name matches, then executable matches
                                              const aName = a.name.toLowerCase();
                                              const bName = b.name.toLowerCase();
                                              const aExecutable = getExecutableName(a).toLowerCase();
                                              const bExecutable = getExecutableName(b).toLowerCase();
                                              const aStarts = aName.startsWith(searchTerm);
                                              const bStarts = bName.startsWith(searchTerm);
                                              const aExecStarts = aExecutable.startsWith(searchTerm);
                                              const bExecStarts = bExecutable.startsWith(searchTerm);

                                              // Prioritize name matches first
                                              if (aStarts && !bStarts)
                                              return -1;
                                              if (!aStarts && bStarts)
                                              return 1;

                                              // Then prioritize executable matches
                                              if (aExecStarts && !bExecStarts)
                                              return -1;
                                              if (!aExecStarts && bExecStarts)
                                              return 1;

                                              return aName.localeCompare(bName);
                                            }).slice(0, 20).map(app => createResultEntry(app));
    }
  }

  function createResultEntry(app, score) {
    return {
      "appId": getAppKey(app),
      "usageKey": getAppKey(app),
      "name": app.name || "Unknown",
      "description": app.genericName || app.comment || "",
      "icon": app.icon || "application-x-executable",
      "isImage": false,
      "_score": (score !== undefined ? score : 0),
      "provider": root,
      "onActivate": function () {
        // Close the launcher/SmartPanel immediately without any animations.
        // Ensures we are not preventing the future focusing of the app
        launcher.closeImmediately();

        // Defer execution to next event loop iteration to ensure panel is fully closed
        Qt.callLater(() => {
                       Logger.d("ApplicationsProvider", `Launching: ${app.name} (App ID: ${app.id || "unknown"})`);

                       const execString = (app.exec !== undefined && app.exec !== null) ? String(app.exec) : "";
                       const commandArgs = Array.isArray(app.command) ? app.command : (app.command && app.command.length !== undefined) ? Array.from(app.command) : [];
                       let hasQuotedArgs = execString.includes("\"") || execString.includes("'");
                       let hasSpaceArgs = false;
                       if (!hasQuotedArgs) {
                         hasQuotedArgs = commandArgs.some(arg => {
                                                            const text = String(arg);
                                                            return text.includes("\"") || text.includes("'");
                                                          });
                       }
                       if (!hasSpaceArgs) {
                         hasSpaceArgs = commandArgs.some(arg => String(arg).includes(" "));
                       }
                       if (app.execute && (hasQuotedArgs || hasSpaceArgs)) {
                         Logger.d("ApplicationsProvider", `Detected quoted/space arguments in Exec for ${app.name}, using app.execute()`);
                         app.execute();
                         return;
                       }

                       if (Settings.data.appLauncher.customLaunchPrefixEnabled && Settings.data.appLauncher.customLaunchPrefix) {
                         // Use custom launch prefix
                         const prefix = Settings.data.appLauncher.customLaunchPrefix.split(" ");
                         Logger.d("ApplicationsProvider", `Using custom launch prefix: ${Settings.data.appLauncher.customLaunchPrefix}`);

                         if (app.runInTerminal) {
                           const terminal = Settings.data.appLauncher.terminalCommand.split(" ");
                           const command = prefix.concat(terminal.concat(app.command));
                           Logger.d("ApplicationsProvider", `Executing command (with prefix and terminal): ${command.join(" ")}`);
                           Quickshell.execDetached(command);
                         } else {
                           const command = prefix.concat(app.command);
                           Logger.d("ApplicationsProvider", `Executing command (with prefix): ${command.join(" ")}`);
                           Quickshell.execDetached(command);
                         }
                       } else {
                         if (app.runInTerminal) {
                           Logger.d("ApplicationsProvider", "Executing terminal app manually: " + app.name);
                           const terminal = Settings.data.appLauncher.terminalCommand.split(" ");
                           const command = terminal.concat(app.command);
                           Logger.d("ApplicationsProvider", "Executing command (manual terminal): " + command.join(" "));
                           CompositorService.spawn(command);
                         } else if (app.command && app.command.length > 0) {
                           Logger.d("ApplicationsProvider", "Executing command: " + app.command.join(" "));
                           CompositorService.spawn(app.command);
                         } else if (app.execute) {
                           Logger.d("ApplicationsProvider", "Calling app.execute() for: " + app.name);
                           app.execute();
                         } else {
                           Logger.w("ApplicationsProvider", `Could not launch: ${app.name}. No valid launch method.`);
                         }
                       }
                     });
      }
    };
  }

  // -------------------------
  // Item actions for launcher delegate
  function getItemActions(item) {
    if (!item || !item.appId)
      return [];
    return [
          {
            "icon": isAppPinned({
                                  "id": item.appId
                                }) ? "unpin" : "pin",
            "tooltip": isAppPinned({
                                     "id": item.appId
                                   }) ? I18n.tr("common.unpin") : I18n.tr("common.pin"),
            "action": function () {
              togglePin(item.appId);
            }
          }
        ];
  }

  function togglePin(appId) {
    if (!appId)
      return;
    const normalizedId = normalizeAppId(appId);
    let arr = (Settings.data.appLauncher.pinnedApps || []).slice();
    const idx = arr.findIndex(pinnedId => normalizeAppId(pinnedId) === normalizedId);
    if (idx >= 0)
      arr.splice(idx, 1);
    else
      arr.push(appId);
    Settings.data.appLauncher.pinnedApps = arr;
  }

  // -------------------------
  // 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) {
    return ShellState.getLauncherUsageCount(getAppKey(app));
  }

  // Migrate legacy command-based usage keys to canonical app-id keys at startup
  function migrateLegacyUsageKeys() {
    for (let i = 0; i < entries.length; i++) {
      const app = entries[i];
      if (app && app.id && app.command && app.command.join) {
        const key = getAppKey(app);
        const legacyKey = app.command.join(" ");
        if (legacyKey !== key && ShellState.getLauncherUsageCount(legacyKey) > 0) {
          ShellState.migrateLauncherUsage(legacyKey, key);
          Logger.d("ApplicationsProvider", `Migrated usage: "${legacyKey}" → "${key}"`);
        }
      }
    }
  }
}
