// 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.

///|
/// Types that can enumerate "smaller" variants of a given value for
/// the classical QuickCheck shrinker. Each call to `shrink(x)` returns
/// an `Iter[Self]` of candidates strictly simpler than `x`; the driver
/// walks these candidates after a failure, keeping the first one that
/// still falsifies the property.
///
/// The return type is `Iter[Self]` rather than `Array[Self]` so large
/// or recursive shrink spaces stay lazy. The default body (`= _`)
/// means "no shrinks" — a conservative starting point for types where
/// shrinking isn't meaningful.
///
/// ```mbt check
/// test {
///   // `Int` has a built-in Shrink instance that walks toward 0.
///   let candidates : Array[Int] = Shrink::shrink(100).collect()
///   assert_true(candidates.contains(0))
///   assert_true(candidates.length() > 0)
///   // `Bool` shrinks `true` to `false`, and `false` has no shrinks.
///   @debug.assert_eq(Shrink::shrink(true).collect(), [false])
///   @debug.assert_eq(Shrink::shrink(false).collect(), [])
/// }
/// ```
pub(open) trait Shrink {
  fn shrink(Self) -> Iter[Self] = _
}

///|
impl Shrink with fn shrink(_a) {
  Iter::empty()
}

///|
pub impl Shrink for Int with fn shrink(x) {
  [|
    ..[ for z = x / 2; (x - z).abs() < x.abs(); z = z / 2 => x - z ].rev_iter(),
    ..if x != 0 {
      [0]
    },
  |]
}

///|
pub impl Shrink for Int64 with fn shrink(x) {
  [|
    ..[ for z = x / 2; (x - z).abs() < x.abs(); z = z / 2 => x - z ].rev_iter(),
    ..if x != 0 {
      [(0 : Int64)]
    },
  |]
}

///|
pub impl Shrink for UInt with fn shrink(x) {
  [|
    ..[ for z = x / 2; z > 0; z = z / 2 => x - z ].rev_iter(),
    ..if x != 0 {
      [(0 : UInt)]
    },
  |]
}

///|
pub impl Shrink for UInt64 with fn shrink(x) {
  [|
    ..[ for z = x / 2; z > 0; z = z / 2 => x - z ].rev_iter(),
    ..if x != 0 {
      [(0 : UInt64)]
    },
  |]
}

///|
pub impl Shrink for Bool with fn shrink(b) {
  if !b {
    Iter::empty()
  } else {
    Iter::singleton(false)
  }
}

///|
pub impl Shrink for Byte with fn shrink(x) {
  let xi = x.to_int()
  [|
    ..[ for z = xi / 2; z > 0; z = z / 2 => (xi - z).to_byte() ].rev_iter(),
    ..if xi != 0 {
      [Int::to_byte(0)]
    },
  |]
}

///|
pub impl Shrink for Char with fn shrink(c) {
  let candidates = [
    'a',
    'b',
    'c',
    ..if c.is_ascii_uppercase() {
      [c.to_ascii_lowercase()]
    },
    'A',
    'B',
    'C',
    '1',
    '2',
    '3',
    ' ',
    '\n',
  ]
  candidates.sort()
  candidates.dedup()
  let char_stamp = (c : Char) => {
    (
      (!c.is_ascii_lowercase(), !c.is_ascii_uppercase(), !c.is_ascii_digit()),
      (c != ' ', !c.is_whitespace(), c),
    )
  }
  let stamp = char_stamp(c)
  candidates.iter().filter(candidate => char_stamp(candidate) < stamp)
}

///|
pub impl Shrink for Double with fn shrink(x) {
  shrink_decimal(x)
}

///|
pub impl Shrink for Float with fn shrink(x) {
  shrink_decimal(x.to_double()).map(Float::from_double)
}

///|
pub impl Shrink for String with fn shrink(s) {
  Shrink::shrink(s.to_array()).map(chars => String::from_array(chars))
}

///|
pub impl Shrink for Bytes with fn shrink(bytes) {
  Shrink::shrink(bytes.to_array()).map(xs => Bytes::from_array(xs))
}

///|
pub impl Shrink for Unit