pragma Singleton
import QtQuick
import "BorderGeometry.js" as Geometry

// Central border-spec factory for shell surfaces and controls. A spec carries
// color, optional gradient, and top/right/bottom/left widths so renderers can
// choose the cheap Rectangle path or the Shape-ring path without duplicating
// theme parsing logic.
QtObject {
  id: root

  function none() {
    return flat("transparent", 0)
  }

  function flat(color, width) {
    return {
      color: color || "transparent",
      widths: Geometry.parseWidthSpec(width, 0),
      gradient: { colors: [], angle: 0, enabled: false },
    }
  }

  function value(section, key) {
    var v = Color.shellValues[section + "." + key]
    return (v === undefined || v === null) ? "" : v
  }

  function valueOr(section, keys) {
    for (var i = 0; i < keys.length; i++) {
      var v = value(section, keys[i])
      if (String(v).length > 0) return v
    }
    return ""
  }

  function resolveValueRef(raw) {
    var s = String(raw || "").replace(/^\s+|\s+$/g, "")
    var seen = {}
    while (s.match(/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/) && !seen[s]) {
      seen[s] = true
      var next = Color.shellValues[s]
      if (next === undefined || next === null || String(next).length === 0) break
      s = String(next).replace(/^\s+|\s+$/g, "")
    }
    return s
  }

  function alpha(section, key, fallback) {
    var raw = value(section, key)
    if (String(raw).length === 0) return fallback
    var n = Number(raw)
    return isFinite(n) ? Geometry.clampAlpha(n) : fallback
  }

  function cssColor(color, opacity) {
    var a = opacity === undefined || opacity === null ? 1 : Geometry.clampAlpha(opacity)
    if (color && typeof color === "object" && color.r !== undefined) {
      if (typeof Qt !== "undefined" && Qt.rgba) {
        return Qt.rgba(color.r, color.g, color.b, (color.a === undefined ? 1 : color.a) * a)
      }
      return "#"
        + Geometry.padHex(color.r * 255)
        + Geometry.padHex(color.g * 255)
        + Geometry.padHex(color.b * 255)
        + Geometry.padHex((color.a === undefined ? 1 : color.a) * a * 255)
    }

    var s = String(color || "").replace(/^\s+|\s+$/g, "")
    var role = s.toLowerCase()
    if (role === "foreground" || role === "text") return cssColor(Color.foreground, a)
    if (role === "accent") return cssColor(Color.accent, a)
    if (role === "urgent") return cssColor(Color.urgent, a)
    if (role === "background") return cssColor(Color.background, a)
    if (role === "transparent") return "transparent"
    return Geometry.canonicalColor(s, a)
  }

  function resolvedGradient(raw, fallbackColor, opacity) {
    var s = String(raw || "").replace(/^\s+|\s+$/g, "")
    if (s.length === 0) return { colors: [], angle: 0, enabled: false }

    var parts = s.split(/\s+/)
    var colors = []
    var angle = 0
    for (var i = 0; i < parts.length; i++) {
      if (parts[i].match(/^-?\d+(?:\.\d+)?deg$/)) angle = Number(parts[i].replace(/deg$/, ""))
      else colors.push(cssColor(parts[i], opacity))
    }
    if (colors.length === 0) colors.push(cssColor(fallbackColor, opacity))
    return { colors: colors, angle: angle, enabled: colors.length > 1 }
  }

  function sameColor(a, b) {
    if (typeof Qt === "undefined" || !Qt.color) return String(a) === String(b)
    var ca = typeof a === "string" ? Qt.color(a) : a
    var cb = typeof b === "string" ? Qt.color(b) : b
    if (!ca || !cb || ca.r === undefined || cb.r === undefined) return String(a) === String(b)
    return Math.round(ca.r * 255) === Math.round(cb.r * 255)
      && Math.round(ca.g * 255) === Math.round(cb.g * 255)
      && Math.round(ca.b * 255) === Math.round(cb.b * 255)
      && Math.round((ca.a === undefined ? 1 : ca.a) * 255) === Math.round((cb.a === undefined ? 1 : cb.a) * 255)
  }

  function localOrSurfaceSpec(section, token, localColor, defaultColor, fallbackWidth, alphaKey) {
    if (!sameColor(localColor, defaultColor)) return flat(localColor, fallbackWidth)
    return surfaceSpec(section, token, localColor, fallbackWidth, alphaKey)
  }

  function surfaceWidths(section, token, fallbackWidth) {
    var base = valueOr(section, token === "border" ? ["border-width"] : [token + "-width", "border-width"])
    var widths = Geometry.parseWidthSpec(base, fallbackWidth)
    return Geometry.withSideOverrides(
      widths,
      valueOr(section, token === "border" ? ["border-width-top"] : [token + "-width-top", "border-width-top"]),
      valueOr(section, token === "border" ? ["border-width-right"] : [token + "-width-right", "border-width-right"]),
      valueOr(section, token === "border" ? ["border-width-bottom"] : [token + "-width-bottom", "border-width-bottom"]),
      valueOr(section, token === "border" ? ["border-width-left"] : [token + "-width-left", "border-width-left"])
    )
  }

  function borderValue(raw, fallbackColor, opacity, legacyGradientRaw) {
    var fallback = cssColor(fallbackColor, opacity)
    var primaryRaw = String(raw || "").replace(/^\s+|\s+$/g, "")
    var primary = resolvedGradient(primaryRaw.length > 0 ? primaryRaw : fallbackColor, fallbackColor, opacity)
    var color = primary.colors.length > 0 ? primary.colors[0] : fallback
    var gradient = primary.enabled ? primary : { colors: [], angle: 0, enabled: false }

    // Backward compatibility for existing configs written during the separate
    // border-gradient experiment. New configs put solid colors and gradients in
    // the same border token.
    if (!gradient.enabled && String(legacyGradientRaw || "").replace(/^\s+|\s+$/g, "").length > 0) {
      var legacy = resolvedGradient(legacyGradientRaw, color, opacity)
      if (legacy.enabled) gradient = legacy
    }

    return { color: color, gradient: gradient }
  }

  function surfaceSpec(section, token, fallbackColor, fallbackWidth, alphaKey) {
    var opacity = alpha(section, alphaKey || token + "-alpha", 1.0)
    var legacyGradientRaw = valueOr(section, token === "border" ? ["border-gradient"] : [token + "-gradient", "border-gradient"])
    var resolved = borderValue(resolveValueRef(value(section, token)), fallbackColor, opacity, legacyGradientRaw)

    return {
      color: resolved.color,
      widths: surfaceWidths(section, token, fallbackWidth),
      gradient: resolved.gradient,
    }
  }

  function hyprlandActiveSpec(fallbackColor, fallbackWidth) {
    var raw = value("hyprland", "active-border")
    var opacity = alpha("hyprland", "active-border-alpha", 1.0)

    // Existing generated themes predate [hyprland] and already keep the active
    // border under [notifications]. Use it as the compatibility source until
    // the next theme refresh writes the shared token.
    if (String(raw).length === 0) {
      raw = value("notifications", "border")
      opacity = alpha("notifications", "border-alpha", opacity)
    }

    var resolved = borderValue(resolveValueRef(raw), fallbackColor, opacity, "")
    return {
      color: resolved.color,
      widths: Geometry.parseWidthSpec(value("hyprland", "active-border-width"), fallbackWidth),
      gradient: resolved.gradient,
    }
  }

  function controlPrefix(state) {
    if (state === "hover" || state === "hot") return "hover-cursor"
    return state || "normal"
  }

  function controlColor(prefix, foreground, accent, urgent) {
    if (prefix === "focus") return Style.focusStateColor(foreground, accent, urgent)
    if (prefix === "hover-cursor") return Style.hoverStateColor(foreground, accent, urgent)
    if (prefix === "selected") return Style.selectedStateColor(foreground, accent, urgent)
    return Style.normalStateColor(foreground, accent, urgent)
  }

  function controlAlpha(prefix) {
    if (prefix === "focus") return Style.focusBorderAlpha
    if (prefix === "hover-cursor") return Style.hoverBorderAlpha
    if (prefix === "selected") return Style.selectedBorderAlpha
    return Style.normalBorderAlpha
  }

  function controlFallbackWidth(prefix) {
    if (prefix === "focus") return Style.focusBorderWidth
    if (prefix === "hover-cursor") return Style.hoverBorderWidth
    if (prefix === "selected") return Style.selectedBorderWidth
    return Style.normalBorderWidth
  }

  function controlWidths(state) {
    var prefix = controlPrefix(state)
    var fallbackWidth = controlFallbackWidth(prefix)
    var base = Style.styleOverrides[prefix + "-border-width"]
    var widths = Geometry.parseWidthSpec(base, fallbackWidth)
    return Geometry.withSideOverrides(
      widths,
      Style.styleOverrides[prefix + "-border-width-top"],
      Style.styleOverrides[prefix + "-border-width-right"],
      Style.styleOverrides[prefix + "-border-width-bottom"],
      Style.styleOverrides[prefix + "-border-width-left"]
    )
  }

  function controlHasWidth(state) {
    return Geometry.maxWidth(controlWidths(state)) > 0
  }

  function controlSpec(state, foreground, accent, urgent) {
    var prefix = controlPrefix(state)
    var resolved = borderValue(
      Style.styleOverrides[prefix + "-border"],
      controlColor(prefix, foreground, accent, urgent),
      controlAlpha(prefix),
      Style.styleOverrides[prefix + "-border-gradient"]
    )

    return { color: resolved.color, widths: controlWidths(prefix), gradient: resolved.gradient }
  }

  function withWidth(spec, width) {
    if (!spec) return flat("transparent", 0)
    return { color: spec.color, gradient: spec.gradient, widths: Geometry.parseWidthSpec(width, 0) }
  }

  function isNone(spec) { return !spec || Geometry.maxWidth(spec.widths) <= 0 }
  function needsOverlay(spec) { return Geometry.needsOverlay(spec) }
  function canUseNative(spec) { return Geometry.canUseNative(spec) }
  function top(spec) { return spec && spec.widths ? spec.widths.top : 0 }
  function right(spec) { return spec && spec.widths ? spec.widths.right : 0 }
  function bottom(spec) { return spec && spec.widths ? spec.widths.bottom : 0 }
  function left(spec) { return spec && spec.widths ? spec.widths.left : 0 }
  function uniformWidth(spec) { return spec && spec.widths ? spec.widths.top : 0 }
  function color(spec) { return spec ? spec.color : "transparent" }
}
