// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// A simple mutable reference type that allows you to store and modify a value of any type.
/// 
/// ```mbt check
/// test {
///   let x = @ref.Ref(42)
///   @test.assert_eq(x.val, 42)
///   x.val = 100
///   @test.assert_eq(x.val, 100)
/// }
/// ```
pub(all) struct Ref[T] {
  mut val : T
}

///|
#deprecated("Use @debug.Debug instead of Show for debugging purposes. See https://github.com/moonbitlang/core/blob/main/debug/README.mbt.md")
pub impl[X : Show] Show for Ref[X]

///|
pub impl[X : Show] Show for Ref[X] with fn output(self, logger) {
  logger <+
    $|{val: \{self.val}}
}

///|
/// create a reference from value
#alias(new, deprecated)
#owned(x)
pub fn[T] Ref::Ref(x : T) -> Ref[T] {
  { val: x }
}

///|
test "to_string" {
  @debug.debug_inspect(
    new(3),
    content=(
      #|
    ),
  )
}

///|
/// Same as the `Ref` constructor.
#owned(x)
pub fn[T] new(x : T) -> Ref[T] {
  { val: x }
}

///|
/// Maps the value of a `Ref` using a given function.
///
/// # Example
///
/// ```mbt check
/// test {
///   @test.assert_eq(@ref.new(1).map(a => a + 1).val, 2)
/// }
/// ```
pub fn[T, R] Ref::map(self : Ref[T], f : (T) -> R raise?) -> Ref[R] raise? {
  { val: f(self.val) }
}

///|
/// This function allows you to temporarily replace the value of a reference with a new value,
/// execute a given function, and then restore the original value of the reference.
///
/// # Arguments
///
/// - `self`: The reference whose value will be temporarily replaced.
/// - `a`: The new value to assign to the reference.
/// - `f`: The function to execute while the reference value is replaced.
///
/// # Returns
///
/// The result of executing the provided function `f`.
///
/// # Example
///
/// ```mbt check
/// test {
///   let x = @ref.new(1)
///   x.protect(2, () => x.val = 3)
///   @test.assert_eq(x.val, 1)
/// }
/// ```
pub fn[T, R] Ref::protect(self : Ref[T], a : T, f : () -> R raise?) -> R raise? {
  let old = self.val
  self.val = a
  try f() catch {
    err => {
      self.val = old
      raise err
    }
  } noraise {
    r => {
      self.val = old
      r
    }
  }
}

///|
/// Swaps the values of two references.
///
/// # Example
///
/// ```mbt check
/// test {
///   let x = @ref.new(1)
///   let y = @ref.new(2)
///   @ref.swap(x, y)
///   @test.assert_eq(x.val, 2)
///   @test.assert_eq(y.val, 1)
/// }
/// ```
#as_free_fn
pub fn[T] Ref::swap(self : Ref[T], that : Ref[T]) -> Unit {
  let tmp = self.val
  self.val = that.val
  that.val = tmp
}

///|
test "swap" {
  let x = new(1)
  let y = new(2)
  swap(x, y)
  inspect(x.val, content="2")
  inspect(y.val, content="1")
}

///|
/// Applies `f` to the current value and stores the result.
pub fn[T] Ref::update(self : Ref[T], f : (T) -> T raise?) -> Unit raise? {
  self.val = f(self.val)
}

///|
test "decr" {
  let a = new(1)
  a.val -= 1
  inspect(a.val, content="0")
  a.val -= 5
  inspect(a.val, content="-5")
}

///|
test "incr" {
  let a = new(1)
  a.val += 1
  inspect(a.val, content="2")
  a.val += 5
  inspect(a.val, content="7")
}