{{- $apps := .chezmoi.config.data.apps -}}

hl.config({
  group = {
    auto_group = true,
    insert_after_current = true,
    focus_removed_window = true,
    drag_into_group = 1,
    merge_groups_on_drag = true,
    merge_groups_on_groupbar = true,
    merge_floated_into_tiled_on_groupbar = true,
    group_on_movetoworkspace = false,
    -- colors to be integrated via theme table
    groupbar = {
      enabled = true,
      height = 16,
      font_family = "GeistMono Nerd Font",
      font_size = 8,
      font_weight_active = "semibold",
      font_weight_inactive = "normal",
      stacked = false,
      gradients = true,
      gradient_rounding = 6,
      gradient_rounding_power = 4.0,
      gradient_round_only_edges = false,
      indicator_height = 0,
      rounding = 0,
      render_titles = true,
      scrolling = true,
      text_padding = 4,
      priority = 3
    }
  }
})

-- Permissions (NEW in v0.55)
hl.permission({ binary = "/usr/bin/grim",                        type = "screencopy", mode = "allow" })
hl.permission({ binary = "/usr/bin/grimblast",                   type = "screencopy", mode = "allow" })
hl.permission({ binary = "/usr/bin/wf-recorder",                 type = "screencopy", mode = "allow" })
hl.permission({ binary = "/usr/lib/xdg-desktop-portal-hyprland", type = "screencopy", mode = "allow" })
hl.permission({ binary = "/usr/bin/hyprpm",                      type = "plugin",     mode = "allow" })

-- Window Rules
hl.window_rule({ match = { class = ".*" }, suppress_event = "maximize" })

-- XWayland fixes
hl.window_rule({ 
  match = { class = "^$", title = "^$", xwayland = true, float = true, fullscreen = false, pin = false },
  no_focus = true
})
hl.window_rule({ match = { xwayland = true }, no_initial_focus = true })

-- App placement (data-driven from .placement; one workspace per entry, monitor
-- binding lives on the matching workspace_rule in workspaces.lua).
{{- range $p := .placement }}
{{- if or (not (hasKey $p "requires")) (has $p.requires $apps) }}
{{- $classes := list }}{{ range $c := $p.apps }}{{ $classes = append $classes ($c | replace "." "\\\\.") }}{{ end }}
hl.window_rule({ match = { class = "^({{ join "|" $classes }})$" }, workspace = "{{ $p.id }}{{ if $p.silent }} silent{{ end }}" })
{{- end }}
{{- end }}

-- App-home reassertion: some apps (Joplin) ignore the static silent workspace
-- rule at map time and open on the focused workspace instead. For placements
-- flagged `reassert`, re-apply the assignment by address when the window opens.
local reassert_homes = {
{{- range $p := .placement }}
{{- if and (index $p "reassert" | default false) (or (not (hasKey $p "requires")) (has $p.requires $apps)) }}
{{- range $c := $p.apps }}
  ["{{ $c }}"] = "{{ $p.id }}",
{{- end }}
{{- end }}
{{- end }}
}
hl.on("window.open", function(w)
  if not (w and w.class and w.address) then return end
  local ws = reassert_homes[w.class]
  if ws then
    hl.dispatch(hl.dsp.window.move({ workspace = ws, window = "address:" .. w.address, follow = false }))
  end
end)

-- Game Content Bypass (Option A - per-monitor CTM bypass)
-- Automatically mark Steam games as "game" content type
hl.window_rule({ match = { class = "^steam_app_%d+$" }, content = "game" })

-- Dynamic bypass for custom environment variable NO_BLUELIGHT=1
local function get_process_env(pid, name)
  if not pid or pid <= 0 then return nil end
  local f = io.open("/proc/" .. tostring(pid) .. "/environ", "r")
  if not f then return nil end
  local content = f:read("*a")
  f:close()
  if not content then return nil end
  for var in content:gmatch("([^%z]+)") do
    local k, v = var:match("^([^=]+)=(.*)$")
    if k == name then
      return v
    end
  end
  return nil
end

-- Escape regex metacharacters so classes like "org.mozilla.firefox" match literally.
local function regex_escape(s)
  return (s:gsub("([%^%$%.%|%?%*%+%(%)%[%]%{%}\\])", "\\%1"))
end

-- window_rule registrations persist, so only add one per class (otherwise rules
-- accumulate on every matching window.open).
local bluelight_bypassed = {}
hl.on("window.open", function(w)
  if not (w.pid and w.pid > 0 and w.class) then return end
  if bluelight_bypassed[w.class] then return end
  if get_process_env(w.pid, "NO_BLUELIGHT") == "1" then
    bluelight_bypassed[w.class] = true
    hl.window_rule({ match = { class = "^" .. regex_escape(w.class) .. "$" }, content = "game" })
  end
end)

-- System
hl.window_rule({ match = { class = "com.saivert.pwvucontrol" }, float = true })

hl.window_rule({ 
  match = { class = "scrrec" }, 
  float = true, pin = true, idle_inhibit = "always", 
  rounding = 10, opacity = 0.6, border_size = 0,
  size = { 300, 100 }, move = { "1%", "1%" }, monitor = 0,
  no_initial_focus = true
})

hl.window_rule({ match = { class = "com.gabm.satty" }, float = true, min_size = { 700, 400 } })

-- Layer Rules
hl.layer_rule({ match = { namespace = "quickshell:.*" }, blur = true, ignore_alpha = 0.79 })
hl.layer_rule({ match = { namespace = "quickshell:popout" }, animation = "bouncy" })

-- ─── Urgent window signalling ────────────────────────────────────────────────
-- apex-neon: red is reserved for danger. A window that requests `urgent`
-- (xdg-activation) wears a red border until you look at it; focusing it consumes
-- the urgency and restores the normal violet-active / cyan-inactive pair. Glow is
-- global/active-only, so urgency rides on the border, not the glow.
--
-- `match` has no `address` key (it's a dispatcher selector, not a rule prop), so
-- a single tag-matched rule carries the colour and we toggle an `urgent` tag on
-- the window by address. Untagging drops the rule, reverting to the global
-- active/inactive pair automatically — no explicit reset needed.
local theme = require("theme")
local urgent_addrs = {}

hl.window_rule({ match = { tag = "urgent" }, border_color = theme.border_urgent })

hl.on("window.urgent", function(w)
  if not (w and w.address) then return end
  urgent_addrs[w.address] = true
  hl.dispatch(hl.dsp.window.tag({ tag = "+urgent", window = "address:" .. w.address }))
end)

hl.on("window.active", function(w)
  if not (w and w.address) then return end
  if not urgent_addrs[w.address] then return end
  urgent_addrs[w.address] = nil
  hl.dispatch(hl.dsp.window.tag({ tag = "-urgent", window = "address:" .. w.address }))
end)
