pragma Singleton

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

Singleton {
  id: root

  property var schemes: []
  property bool scanning: false
  property string schemesDirectory: Quickshell.shellDir + "/Assets/ColorScheme"
  property string colorsJsonFilePath: Settings.configDir + "colors.json"

  Connections {
    target: Settings.data.colorSchemes
    function onDarkModeChanged() {
      Logger.i("ColorScheme", "Detected dark mode change")
      if (!Settings.data.colorSchemes.useWallpaperColors && Settings.data.colorSchemes.predefinedScheme) {
        // Re-apply current scheme to pick the right variant
        applyScheme(Settings.data.colorSchemes.predefinedScheme)
      }
      // Toast: dark/light mode switched
      const enabled = !!Settings.data.colorSchemes.darkMode
      const label = enabled ? "Dark mode" : "Light mode"
      const description = enabled ? "Enabled" : "Enabled"
      ToastService.showNotice(label, description, "dark-mode")
    }
  }

  // --------------------------------
  function init() {
    // does nothing but ensure the singleton is created
    // do not remove
    Logger.i("ColorScheme", "Service started")
    loadColorSchemes()
  }

  function loadColorSchemes() {
    Logger.d("ColorScheme", "Load colorScheme")
    scanning = true
    schemes = []
    // Use find command to locate all scheme.json files
    findProcess.command = ["find", schemesDirectory, "-name", "*.json", "-type", "f"]
    findProcess.running = true
  }

  function getBasename(path) {
    if (!path)
      return ""
    var chunks = path.split("/")
    // Get the filename without extension
    var filename = chunks[chunks.length - 1]
    var schemeName = filename.replace(".json", "")
    // Convert back to display names for special cases
    if (schemeName === "Noctalia-default") {
      return "Noctalia (default)"
    } else if (schemeName === "Noctalia-legacy") {
      return "Noctalia (legacy)"
    } else if (schemeName === "Tokyo-Night") {
      return "Tokyo Night"
    }
    return schemeName
  }

  function resolveSchemePath(nameOrPath) {
    if (!nameOrPath)
      return ""
    if (nameOrPath.indexOf("/") !== -1) {
      return nameOrPath
    }
    // Handle special cases for Noctalia schemes
    var schemeName = nameOrPath.replace(".json", "")
    if (schemeName === "Noctalia (default)") {
      schemeName = "Noctalia-default"
    } else if (schemeName === "Noctalia (legacy)") {
      schemeName = "Noctalia-legacy"
    } else if (schemeName === "Tokyo Night") {
      schemeName = "Tokyo-Night"
    }
    return schemesDirectory + "/" + schemeName + "/" + schemeName + ".json"
  }

  function applyScheme(nameOrPath) {
    // Force reload by bouncing the path
    var filePath = resolveSchemePath(nameOrPath)
    schemeReader.path = ""
    schemeReader.path = filePath
  }

  function setPredefinedScheme(schemeName) {
    Logger.i("ColorScheme", "Attempting to set predefined scheme to:", schemeName)

    var resolvedPath = resolveSchemePath(schemeName)
    var basename = getBasename(schemeName)

    // Check if the scheme actually exists in the loaded schemes list
    var schemeExists = false
    for (var i = 0; i < schemes.length; i++) {
      if (getBasename(schemes[i]) === basename) {
        schemeExists = true
        break
      }
    }

    if (schemeExists) {
      Settings.data.colorSchemes.predefinedScheme = basename
      applyScheme(schemeName)
      ToastService.showNotice("Color Scheme", `Set to ${basename}`, "settings-color-scheme")
    } else {
      Logger.e("ColorScheme", "Scheme not found:", schemeName)
      ToastService.showError("Color Scheme", `Scheme '${basename}' not found!`)
    }
  }

  Process {
    id: findProcess
    running: false

    onExited: function (exitCode) {
      if (exitCode === 0) {
        var output = stdout.text.trim()
        var files = output.split('\n').filter(function (line) {
          return line.length > 0
        })
        files.sort(function (a, b) {
          var nameA = getBasename(a).toLowerCase()
          var nameB = getBasename(b).toLowerCase()
          return nameA.localeCompare(nameB)
        })
        schemes = files
        scanning = false
        Logger.d("ColorScheme", "Listed", schemes.length, "schemes")
        // Normalize stored scheme to basename and re-apply if necessary
        var stored = Settings.data.colorSchemes.predefinedScheme
        if (stored) {
          var basename = getBasename(stored)
          if (basename !== stored) {
            Settings.data.colorSchemes.predefinedScheme = basename
          }
          if (!Settings.data.colorSchemes.useWallpaperColors) {
            applyScheme(basename)
          }
        }
      } else {
        Logger.e("ColorScheme", "Failed to find color scheme files")
        schemes = []
        scanning = false
      }
    }

    stdout: StdioCollector {}
    stderr: StdioCollector {}
  }

  // Internal loader to read a scheme file
  FileView {
    id: schemeReader
    onLoaded: {
      try {
        var data = JSON.parse(text())
        var variant = data
        // If scheme provides dark/light variants, pick based on settings
        if (data && (data.dark || data.light)) {
          if (Settings.data.colorSchemes.darkMode) {
            variant = data.dark || data.light
          } else {
            variant = data.light || data.dark
          }
        }
        writeColorsToDisk(variant)
        Logger.i("ColorScheme", "Applying color scheme:", getBasename(path))

        // Generate Matugen templates if any are enabled and setting allows it
        if (Settings.data.colorSchemes.generateTemplatesForPredefined && hasEnabledTemplates()) {
          AppThemeService.generateFromPredefinedScheme(data)
        }
      } catch (e) {
        Logger.e("ColorScheme", "Failed to parse scheme JSON:", path, e)
      }
    }
  }

  // Check if any templates are enabled
  function hasEnabledTemplates() {
    const templates = Settings.data.templates
    for (const key in templates) {
      if (templates[key]) {
        return true
      }
    }
    return false
  }

  // Writer to colors.json using a JsonAdapter for safety
  FileView {
    id: colorsWriter
    path: colorsJsonFilePath
    onSaved: {

      // Logger.i("ColorScheme", "Colors saved")
    }
    JsonAdapter {
      id: out
      property color mPrimary: "#000000"
      property color mOnPrimary: "#000000"
      property color mSecondary: "#000000"
      property color mOnSecondary: "#000000"
      property color mTertiary: "#000000"
      property color mOnTertiary: "#000000"
      property color mError: "#000000"
      property color mOnError: "#000000"
      property color mSurface: "#000000"
      property color mOnSurface: "#000000"
      property color mSurfaceVariant: "#000000"
      property color mOnSurfaceVariant: "#000000"
      property color mOutline: "#000000"
      property color mShadow: "#000000"
      property color mHover: "#000000"
      property color mOnHover: "#000000"
    }
  }

  function writeColorsToDisk(obj) {
    function pick(o, a, b, fallback) {
      return (o && (o[a] || o[b])) || fallback
    }
    out.mPrimary = pick(obj, "mPrimary", "primary", out.mPrimary)
    out.mOnPrimary = pick(obj, "mOnPrimary", "onPrimary", out.mOnPrimary)
    out.mSecondary = pick(obj, "mSecondary", "secondary", out.mSecondary)
    out.mOnSecondary = pick(obj, "mOnSecondary", "onSecondary", out.mOnSecondary)
    out.mTertiary = pick(obj, "mTertiary", "tertiary", out.mTertiary)
    out.mOnTertiary = pick(obj, "mOnTertiary", "onTertiary", out.mOnTertiary)
    out.mError = pick(obj, "mError", "error", out.mError)
    out.mOnError = pick(obj, "mOnError", "onError", out.mOnError)
    out.mSurface = pick(obj, "mSurface", "surface", out.mSurface)
    out.mOnSurface = pick(obj, "mOnSurface", "onSurface", out.mOnSurface)
    out.mSurfaceVariant = pick(obj, "mSurfaceVariant", "surfaceVariant", out.mSurfaceVariant)
    out.mOnSurfaceVariant = pick(obj, "mOnSurfaceVariant", "onSurfaceVariant", out.mOnSurfaceVariant)
    out.mOutline = pick(obj, "mOutline", "outline", out.mOutline)
    out.mShadow = pick(obj, "mShadow", "shadow", out.mShadow)
    out.mHover = pick(obj, "mHover", "hover", out.mHover)
    out.mOnHover = pick(obj, "mOnHover", "onHover", out.mOnHover)

    // Force a rewrite by updating the path
    colorsWriter.path = ""
    colorsWriter.path = colorsJsonFilePath
    colorsWriter.writeAdapter()
  }
}
