# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.

## Rumpk Layer 1: The Reactive Dispatcher (The Tyrant)
##
## Implements the Silence Doctrine (SPEC-102).
## - No Tick.
## - No Policy.
## - Only Physics.

{.push stackTrace: off, lineTrace: off.}

import fiber

# We might need to access the Fiber globals from fiber.nim
# fiber.nim exports current_fiber, but we need to iterate them.
# Currently kernel.nim manages the specific fibers variables (fiber_ion, fiber_ui, etc.)
# We need a centralized registry or a way to iterate.
#
# For the first pass, we can replicate the logic in kernel.nim which explicitly checks
# the known fibers, but structured as the Spectrum loop. 
# Or we can make kernel.nim pass the fibers to us.
#
# Let's keep it simple and stateless in sched.nim if possible, or have it manage the queue.
# Since kernel.nim holds the variables, sched.nim should probably define the *Strategy* 
# and kernel.nim calls it, OR sched.nim should import kernel (circular!).
#
# Better: fiber.nim holds a linked list of fibers? 
# Or sched.nim is just a helper module that kernel.nim uses.

# Let's define the Strategy here.

# To avoid circular imports, kernel.nim will likely INCLUDE sched.nim or sched.nim 
# will act on a passed context. 
# BUT, SPEC-102 implies sched.nim *is* the logic.
#
# Let's define the Harmonic logic. 
# We need access to `current_fiber` (from fiber.nim) and `get_now_ns` (helper).

proc sched_get_now_ns*(): uint64 {.importc: "rumpk_timer_now_ns", cdecl.}

# Forward declaration for channel data check (provided by kernel/channels)
proc fiber_can_run_on_channels*(id: uint64, mask: uint64): bool {.importc, cdecl.}

proc is_runnable(f: ptr FiberObject, now: uint64): bool =
  if f == nil or f.state.sp == 0: return false # Can only run initialized fibers
  if now < f.sleep_until: return false
  if f.is_blocked:
    if fiber_can_run_on_channels(f.id, f.blocked_on_mask):
      f.is_blocked = false # Latched unblock
      return true
    return false
  return true

proc sched_tick_spectrum*(fibers: openArray[ptr FiberObject]): bool =
  let now = sched_get_now_ns()
  
  # =========================================================
  # Phase 1: PHOTON (Hard Real-Time / Hardware Driven)
  # =========================================================
  var run_photon = false
  for f in fibers:
    if f != nil and f.getSpectrum() == Spectrum.Photon:
      if is_runnable(f, now):
        if f != current_fiber:
           switch(f); return true
        else:
           run_photon = true
  if run_photon: return true

  # =========================================================
  # Phase 2: MATTER (Interactive / Latency Sensitive)
  # =========================================================
  var run_matter = false
  for f in fibers:
    if f != nil and f.getSpectrum() == Spectrum.Matter:
      if is_runnable(f, now):
         if f != current_fiber:
           switch(f); return true
         else:
           run_matter = true
  if run_matter: return true

  # =========================================================
  # Phase 3: GRAVITY (Throughput / Background)
  # =========================================================
  var run_gravity = false
  for f in fibers:
    if f != nil and f.getSpectrum() == Spectrum.Gravity:
      if is_runnable(f, now):
         if f != current_fiber:
           switch(f); return true
         else:
           run_gravity = true
  if run_gravity: return true

  # =========================================================
  # Phase 4: VOID (Scavenger)
  # =========================================================
  for f in fibers:
     if f != nil and f.getSpectrum() == Spectrum.Void:
       if is_runnable(f, now):
          if f != current_fiber:
             switch(f)
             return true
          else:
             return true
             
  # =========================================================
  # THE SILENCE
  # =========================================================
  # If we reached here, NO fiber is runnable.
  return false

proc sched_get_next_wakeup*(fibers: openArray[ptr FiberObject]): uint64 =
  var min_wakeup: uint64 = 0xFFFFFFFFFFFFFFFF'u64
  let now = sched_get_now_ns()

  for f in fibers:
    if f != nil and f.sleep_until > now:
      if f.sleep_until < min_wakeup:
        min_wakeup = f.sleep_until
  
  return min_wakeup

# =========================================================
# THE RATCHET (Post-Execution Analysis)
# =========================================================

proc console_write(p: pointer, len: csize_t) {.importc, cdecl.}

proc sched_log(msg: string) =
  if msg.len > 0:
    console_write(unsafeAddr msg[0], csize_t(msg.len))

proc sched_demote(f: ptr FiberObject) =
  ## Demote a fiber to a lower Spectrum tier
  let current = f.getSpectrum()
  case current:
  of Spectrum.Photon:
    f.setSpectrum(Spectrum.Matter)
    console_write(cstring("[Ratchet] DEMOTED fiber to Matter\n"), 34)
  of Spectrum.Matter:
    f.setSpectrum(Spectrum.Gravity)
    console_write(cstring("[Ratchet] DEMOTED fiber to Gravity\n"), 35)
  of Spectrum.Gravity:
    f.setSpectrum(Spectrum.Void)
    console_write(cstring("[Ratchet] DEMOTED fiber to Void\n"), 32)
  of Spectrum.Void:
    discard # Already at the bottom

proc sched_analyze_burst*(f: ptr FiberObject, burst_ns: uint64) =
  ## Analyze the burst duration of a fiber after it yields/switches
  ## Implements the 3-strike rule for budget violations
  if f == nil: return
  
  f.last_burst_ns = burst_ns
  
  # Update moving average (exponential: 75% old, 25% new)
  if f.avg_burst == 0:
    f.avg_burst = burst_ns
  else:
    f.avg_burst = (f.avg_burst * 3 + burst_ns) div 4
  
  # Budget enforcement
  if f.budget_ns > 0 and burst_ns > f.budget_ns:
    f.violations += 1
    console_write(cstring("[Ratchet] Violation: fiber exceeded budget\n"), 42)
    
    if f.violations >= 3:
      sched_demote(f)
      f.violations = 0 # Reset after demotion

{.pop.}
