//! RAII depth / iteration counters for the interpreter.
//!
//! Each counter is an `Rc<Cell<u64>>` so that a [`DepthGuard`] can own a handle to it
//! *without borrowing the `Interpreter`*. This matters because the guarded region
//! (e.g. the body of `eval_expr`) needs `&mut self`; a borrow-based guard holding
//! `&mut self.eval_depth` would conflict with those calls. Sharing the cell via `Rc`
//! sidesteps the borrow checker while staying `unsafe`-free.
//!
//! Entering a counter returns a guard that decrements it on `Drop`, so the decrement
//! runs on every exit path; including the `?`/early-return paths that the previous
//! manual `+= 1` / `-= 1` code could (and in one spot did) skip.

use std::cell::Cell;
use std::rc::Rc;

/// A shared `u64` counter with RAII enter/exit semantics.
#[derive(Clone, Default)]
pub(super) struct DepthCounter(Rc<Cell<u64>>);

impl DepthCounter {
    /// Current counter value.
    pub(super) fn get(&self) -> u64 {
        self.0.get()
    }

    /// Overwrite the counter value (used to reset/restore per-call frames).
    pub(super) fn set(&self, value: u64) {
        self.0.set(value);
    }

    /// Increment the counter by one (for cumulative counters that are not scope-bound).
    pub(super) fn bump(&self) {
        self.0.set(self.0.get() + 1);
    }

    /// Enter one level: increment and return a guard that decrements on drop.
    ///
    /// Returns `None` *without incrementing* if the counter has already reached `max`,
    /// letting the caller raise a depth-limit error.
    pub(super) fn enter(&self, max: u64) -> Option<DepthGuard> {
        if self.0.get() >= max {
            return None;
        }
        self.0.set(self.0.get() + 1);
        Some(DepthGuard(self.0.clone()))
    }
}

/// Guard returned by [`DepthCounter::enter`]; decrements its counter when dropped.
pub(super) struct DepthGuard(Rc<Cell<u64>>);

impl Drop for DepthGuard {
    fn drop(&mut self) {
        self.0.set(self.0.get() - 1);
    }
}

/// Guard that resets the per-invocation counters to zero for the duration of a function
/// call, restoring their previous values on drop.
///
/// `block_depth`, `eval_depth`, and `loop_iterations` are reset on call entry so the
/// limits apply *per invocation* rather than accumulating across the whole call stack.
pub(super) struct CallFrameGuard {
    block_depth: DepthCounter,
    eval_depth: DepthCounter,
    loop_iterations: DepthCounter,
    saved: (u64, u64, u64),
}

impl CallFrameGuard {
    /// Snapshot the three per-invocation counters, reset them to zero, and return a
    /// guard that restores the snapshot on drop.
    pub(super) fn enter(
        block_depth: &DepthCounter,
        eval_depth: &DepthCounter,
        loop_iterations: &DepthCounter,
    ) -> Self {
        let saved = (block_depth.get(), eval_depth.get(), loop_iterations.get());
        block_depth.set(0);
        eval_depth.set(0);
        loop_iterations.set(0);
        Self {
            block_depth: block_depth.clone(),
            eval_depth: eval_depth.clone(),
            loop_iterations: loop_iterations.clone(),
            saved,
        }
    }
}

impl Drop for CallFrameGuard {
    fn drop(&mut self) {
        self.block_depth.set(self.saved.0);
        self.eval_depth.set(self.saved.1);
        self.loop_iterations.set(self.saved.2);
    }
}
