pragma Singleton

import QtQuick
import Qt.labs.folderlistmodel
import Quickshell
import Quickshell.Io
import qs.Commons

Singleton {
  id: root

  readonly property ListModel fillModeModel: ListModel {}
  readonly property string defaultDirectory: Settings.preprocessPath(Settings.data.wallpaper.directory)

  // All available wallpaper transitions
  readonly property ListModel transitionsModel: ListModel {}

  // All transition keys but filter out "none" and "random" so we are left with the real transitions
  readonly property var allTransitions: Array.from({
                                                     "length": transitionsModel.count
                                                   }, (_, i) => transitionsModel.get(i).key).filter(key => key !== "random" && key != "none")

  property var wallpaperLists: ({})
  property int scanningCount: 0
  readonly property bool scanning: (scanningCount > 0)

  // Cache for current wallpapers - can be updated directly since we use signals for notifications
  property var currentWallpapers: ({})

  property bool isInitialized: false

  // Signals for reactive UI updates
  signal wallpaperChanged(string screenName, string path)
  // Emitted when a wallpaper changes
  signal wallpaperDirectoryChanged(string screenName, string directory)
  // Emitted when a monitor's directory changes
  signal wallpaperListChanged(string screenName, int count)

  // Emitted when available wallpapers list changes
  Connections {
    target: Settings.data.wallpaper
    function onDirectoryChanged() {
      root.refreshWallpapersList()
      // Emit directory change signals for monitors using the default directory
      if (!Settings.data.wallpaper.enableMultiMonitorDirectories) {
        // All monitors use the main directory
        for (var i = 0; i < Quickshell.screens.length; i++) {
          root.wallpaperDirectoryChanged(Quickshell.screens[i].name, root.defaultDirectory)
        }
      } else {
        // Only monitors without custom directories are affected
        for (var i = 0; i < Quickshell.screens.length; i++) {
          var screenName = Quickshell.screens[i].name
          var monitor = root.getMonitorConfig(screenName)
          if (!monitor || !monitor.directory) {
            root.wallpaperDirectoryChanged(screenName, root.defaultDirectory)
          }
        }
      }
    }
    function onEnableMultiMonitorDirectoriesChanged() {
      root.refreshWallpapersList()
      // Notify all monitors about potential directory changes
      for (var i = 0; i < Quickshell.screens.length; i++) {
        var screenName = Quickshell.screens[i].name
        root.wallpaperDirectoryChanged(screenName, root.getMonitorDirectory(screenName))
      }
    }
    function onRandomEnabledChanged() {
      root.toggleRandomWallpaper()
    }
    function onRandomIntervalSecChanged() {
      root.restartRandomWallpaperTimer()
    }
    function onRecursiveSearchChanged() {
      root.refreshWallpapersList()
    }
  }

  // -------------------------------------------------
  function init() {
    Logger.i("Wallpaper", "Service started")

    translateModels()

    // Rebuild cache from settings
    currentWallpapers = ({})
    var monitors = Settings.data.wallpaper.monitors || []
    for (var i = 0; i < monitors.length; i++) {
      if (monitors[i].name && monitors[i].wallpaper) {
        currentWallpapers[monitors[i].name] = monitors[i].wallpaper
      }
    }

    isInitialized = true
    Logger.d("Wallpaper", "Triggering initial wallpaper scan")
    Qt.callLater(refreshWallpapersList)
  }

  // -------------------------------------------------
  function translateModels() {
    // Wait for i18n to be ready by retrying every time
    if (!I18n.isLoaded) {
      Qt.callLater(translateModels)
      return
    }

    // Populate fillModeModel with translated names
    fillModeModel.append({
                           "key": "center",
                           "name": I18n.tr("wallpaper.fill-modes.center"),
                           "uniform": 0.0
                         })
    fillModeModel.append({
                           "key": "crop",
                           "name": I18n.tr("wallpaper.fill-modes.crop"),
                           "uniform": 1.0
                         })
    fillModeModel.append({
                           "key": "fit",
                           "name": I18n.tr("wallpaper.fill-modes.fit"),
                           "uniform": 2.0
                         })
    fillModeModel.append({
                           "key": "stretch",
                           "name": I18n.tr("wallpaper.fill-modes.stretch"),
                           "uniform": 3.0
                         })

    // Populate transitionsModel with translated names
    transitionsModel.append({
                              "key": "none",
                              "name": I18n.tr("wallpaper.transitions.none")
                            })
    transitionsModel.append({
                              "key": "random",
                              "name": I18n.tr("wallpaper.transitions.random")
                            })
    transitionsModel.append({
                              "key": "fade",
                              "name": I18n.tr("wallpaper.transitions.fade")
                            })
    transitionsModel.append({
                              "key": "disc",
                              "name": I18n.tr("wallpaper.transitions.disc")
                            })
    transitionsModel.append({
                              "key": "stripes",
                              "name": I18n.tr("wallpaper.transitions.stripes")
                            })
    transitionsModel.append({
                              "key": "wipe",
                              "name": I18n.tr("wallpaper.transitions.wipe")
                            })
  }

  // -------------------------------------------------------------------
  function getFillModeUniform() {
    for (var i = 0; i < fillModeModel.count; i++) {
      const mode = fillModeModel.get(i)
      if (mode.key === Settings.data.wallpaper.fillMode) {
        return mode.uniform
      }
    }
    // Fallback to crop
    return 1.0
  }

  // -------------------------------------------------------------------
  // Get specific monitor wallpaper data
  function getMonitorConfig(screenName) {
    var monitors = Settings.data.wallpaper.monitors
    if (monitors !== undefined) {
      for (var i = 0; i < monitors.length; i++) {
        if (monitors[i].name !== undefined && monitors[i].name === screenName) {
          return monitors[i]
        }
      }
    }
  }

  // -------------------------------------------------------------------
  // Get specific monitor directory
  function getMonitorDirectory(screenName) {
    if (!Settings.data.wallpaper.enableMultiMonitorDirectories) {
      return root.defaultDirectory
    }

    var monitor = getMonitorConfig(screenName)
    if (monitor !== undefined && monitor.directory !== undefined) {
      return Settings.preprocessPath(monitor.directory)
    }

    // Fall back to the main/single directory
    return root.defaultDirectory
  }

  // -------------------------------------------------------------------
  // Set specific monitor directory
  function setMonitorDirectory(screenName, directory) {
    var monitors = Settings.data.wallpaper.monitors || []
    var found = false

    // Create a new array with updated values
    var newMonitors = monitors.map(function (monitor) {
      if (monitor.name === screenName) {
        found = true
        return {
          "name": screenName,
          "directory": directory,
          "wallpaper": monitor.wallpaper || ""
        }
      }
      return monitor
    })

    if (!found) {
      newMonitors.push({
                         "name": screenName,
                         "directory": directory,
                         "wallpaper": ""
                       })
    }

    // Update Settings with new array to ensure proper persistence
    Settings.data.wallpaper.monitors = newMonitors.slice()
    root.wallpaperDirectoryChanged(screenName, Settings.preprocessPath(directory))
  }

  // -------------------------------------------------------------------
  // Get specific monitor wallpaper - now from cache
  function getWallpaper(screenName) {
    return currentWallpapers[screenName] || Settings.data.wallpaper.defaultWallpaper
  }

  // -------------------------------------------------------------------
  function changeWallpaper(path, screenName) {
    if (screenName !== undefined) {
      _setWallpaper(screenName, path)
    } else {
      // If no screenName specified change for all screens
      for (var i = 0; i < Quickshell.screens.length; i++) {
        _setWallpaper(Quickshell.screens[i].name, path)
      }
    }
  }

  // -------------------------------------------------------------------
  function _setWallpaper(screenName, path) {
    if (path === "" || path === undefined) {
      return
    }

    if (screenName === undefined) {
      Logger.w("Wallpaper", "setWallpaper", "no screen specified")
      return
    }

    //Logger.i("Wallpaper", "setWallpaper on", screenName, ": ", path)

    // Check if wallpaper actually changed
    var oldPath = currentWallpapers[screenName] || ""
    var wallpaperChanged = (oldPath !== path)

    if (!wallpaperChanged) {
      // No change needed
      return
    }

    // Update cache directly
    currentWallpapers[screenName] = path

    // Update Settings - still need immutable update for Settings persistence
    // The slice() ensures Settings detects the change and saves properly
    var monitors = Settings.data.wallpaper.monitors || []
    var found = false

    var newMonitors = monitors.map(function (monitor) {
      if (monitor.name === screenName) {
        found = true
        return {
          "name": screenName,
          "directory": Settings.preprocessPath(monitor.directory) || getMonitorDirectory(screenName),
          "wallpaper": path
        }
      }
      return monitor
    })

    if (!found) {
      newMonitors.push({
                         "name": screenName,
                         "directory": getMonitorDirectory(screenName),
                         "wallpaper": path
                       })
    }

    Settings.data.wallpaper.monitors = newMonitors.slice()

    // Emit signal for this specific wallpaper change
    root.wallpaperChanged(screenName, path)

    // Restart the random wallpaper timer
    if (randomWallpaperTimer.running) {
      randomWallpaperTimer.restart()
    }
  }

  // -------------------------------------------------------------------
  function setRandomWallpaper() {
    Logger.d("Wallpaper", "setRandomWallpaper")

    if (Settings.data.wallpaper.enableMultiMonitorDirectories) {
      // Pick a random wallpaper per screen
      for (var i = 0; i < Quickshell.screens.length; i++) {
        var screenName = Quickshell.screens[i].name
        var wallpaperList = getWallpapersList(screenName)

        if (wallpaperList.length > 0) {
          var randomIndex = Math.floor(Math.random() * wallpaperList.length)
          var randomPath = wallpaperList[randomIndex]
          changeWallpaper(randomPath, screenName)
        }
      }
    } else {
      // Pick a random wallpaper common to all screens
      // We can use any screenName here, so we just pick the primary one.
      var wallpaperList = getWallpapersList(Screen.name)
      if (wallpaperList.length > 0) {
        var randomIndex = Math.floor(Math.random() * wallpaperList.length)
        var randomPath = wallpaperList[randomIndex]
        changeWallpaper(randomPath, undefined)
      }
    }
  }

  // -------------------------------------------------------------------
  function toggleRandomWallpaper() {
    Logger.d("Wallpaper", "toggleRandomWallpaper")
    if (Settings.data.wallpaper.randomEnabled) {
      restartRandomWallpaperTimer()
      setRandomWallpaper()
    }
  }

  // -------------------------------------------------------------------
  function restartRandomWallpaperTimer() {
    if (Settings.data.wallpaper.isRandom) {
      randomWallpaperTimer.restart()
    }
  }

  // -------------------------------------------------------------------
  function getWallpapersList(screenName) {
    if (screenName != undefined && wallpaperLists[screenName] != undefined) {
      return wallpaperLists[screenName]
    }
    return []
  }

  // -------------------------------------------------------------------
  function refreshWallpapersList() {
    Logger.d("Wallpaper", "refreshWallpapersList", "recursive:", Settings.data.wallpaper.recursiveSearch)
    scanningCount = 0

    if (Settings.data.wallpaper.recursiveSearch) {
      // Use Process-based recursive search for all screens
      for (var i = 0; i < Quickshell.screens.length; i++) {
        var screenName = Quickshell.screens[i].name
        var directory = getMonitorDirectory(screenName)
        scanDirectoryRecursive(screenName, directory)
      }
    } else {
      // Use FolderListModel (non-recursive)
      // Force refresh by toggling each scanner's currentDirectory
      for (var i = 0; i < wallpaperScanners.count; i++) {
        var scanner = wallpaperScanners.objectAt(i)
        if (scanner) {
          // Capture scanner in closure
          (function (s) {
            var directory = root.getMonitorDirectory(s.screenName)
            // Trigger a change by setting to /tmp (always exists) then back to the actual directory
            // Note: This causes harmless Qt warnings (QTBUG-52262) but is necessary to force FolderListModel to re-scan
            s.currentDirectory = "/tmp"
            Qt.callLater(function () {
              s.currentDirectory = directory
            })
          })(scanner)
        }
      }
    }
  }

  // Process instances for recursive scanning (one per screen)
  property var recursiveProcesses: ({})

  // -------------------------------------------------------------------
  function scanDirectoryRecursive(screenName, directory) {
    if (!directory || directory === "") {
      Logger.w("Wallpaper", "Empty directory for", screenName)
      wallpaperLists[screenName] = []
      wallpaperListChanged(screenName, 0)
      return
    }

    // Cancel any existing scan for this screen
    if (recursiveProcesses[screenName]) {
      Logger.d("Wallpaper", "Cancelling existing scan for", screenName)
      recursiveProcesses[screenName].running = false
      recursiveProcesses[screenName].destroy()
      delete recursiveProcesses[screenName]
      scanningCount--
    }

    scanningCount++
    Logger.i("Wallpaper", "Starting recursive scan for", screenName, "in", directory)

    // Create Process component inline
    var processComponent = Qt.createComponent("", root)
    var processString = `
    import QtQuick
    import Quickshell.Io
    Process {
    id: process
    command: ["find", "-L", "` + directory + `", "-type", "f", "(", "-iname", "*.jpg", "-o", "-iname", "*.jpeg", "-o", "-iname", "*.png", "-o", "-iname", "*.gif", "-o", "-iname", "*.pnm", "-o", "-iname", "*.bmp", ")"]
    stdout: StdioCollector {}
    stderr: StdioCollector {}
    }
    `

    var processObject = Qt.createQmlObject(processString, root, "RecursiveScan_" + screenName)

    // Store reference to avoid garbage collection
    recursiveProcesses[screenName] = processObject

    var handler = function (exitCode) {
      scanningCount--
      Logger.d("Wallpaper", "Process exited with code", exitCode, "for", screenName)
      if (exitCode === 0) {
        var lines = processObject.stdout.text.split('\n')
        var files = []
        for (var i = 0; i < lines.length; i++) {
          var line = lines[i].trim()
          if (line !== '') {
            files.push(line)
          }
        }
        // Sort files for consistent ordering
        files.sort()
        wallpaperLists[screenName] = files
        Logger.i("Wallpaper", "Recursive scan completed for", screenName, "found", files.length, "files")
        wallpaperListChanged(screenName, files.length)
      } else {
        Logger.w("Wallpaper", "Recursive scan failed for", screenName, "exit code:", exitCode, "(directory might not exist)")
        wallpaperLists[screenName] = []
        wallpaperListChanged(screenName, 0)
      }
      // Clean up
      delete recursiveProcesses[screenName]
      processObject.destroy()
    }

    processObject.exited.connect(handler)
    Logger.d("Wallpaper", "Starting process for", screenName)
    processObject.running = true
  }

  // -------------------------------------------------------------------
  // -------------------------------------------------------------------
  // -------------------------------------------------------------------
  Timer {
    id: randomWallpaperTimer
    interval: Settings.data.wallpaper.randomIntervalSec * 1000
    running: Settings.data.wallpaper.randomEnabled
    repeat: true
    onTriggered: setRandomWallpaper()
    triggeredOnStart: false
  }

  // Instantiator (not Repeater) to create FolderListModel for each monitor
  Instantiator {
    id: wallpaperScanners
    model: Quickshell.screens
    delegate: FolderListModel {
      property string screenName: modelData.name
      property string currentDirectory: root.getMonitorDirectory(screenName)

      folder: "file://" + currentDirectory
      nameFilters: ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.pnm", "*.bmp"]
      showDirs: false
      sortField: FolderListModel.Name

      // Watch for directory changes via property binding
      onCurrentDirectoryChanged: {
        folder = "file://" + currentDirectory
      }

      Component.onCompleted: {
        // Connect to directory change signal
        root.wallpaperDirectoryChanged.connect(function (screen, directory) {
          if (screen === screenName) {
            currentDirectory = directory
          }
        })
      }

      onStatusChanged: {
        if (status === FolderListModel.Null) {
          // Flush the list
          root.wallpaperLists[screenName] = []
          root.wallpaperListChanged(screenName, 0)
        } else if (status === FolderListModel.Loading) {
          // Flush the list
          root.wallpaperLists[screenName] = []
          scanningCount++
        } else if (status === FolderListModel.Ready) {
          var files = []
          for (var i = 0; i < count; i++) {
            var directory = root.getMonitorDirectory(screenName)
            var filepath = directory + "/" + get(i, "fileName")
            files.push(filepath)
          }

          // Update the list
          root.wallpaperLists[screenName] = files

          scanningCount--
          Logger.d("Wallpaper", "List refreshed for", screenName, "count:", files.length)
          root.wallpaperListChanged(screenName, files.length)
        }
      }
    }
  }
}
