// Copyright 2026 Ling71671
//
// 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.

///|
/// Safe substring slice clamping start and end within valid string bounds.
pub fn string_slice(s : String, start : Int, end : Int) -> String {
  let len = s.length()
  let safe_start = if start < 0 { 0 } else if start > len { len } else { start }
  let safe_end = if end < safe_start {
    safe_start
  } else if end > len {
    len
  } else {
    end
  }
  s[safe_start:safe_end].to_owned()
}

///|
/// Returns true if the UTF-16 code point is a leading surrogate (high surrogate).
pub fn is_leading_surrogate(code : Int) -> Bool {
  code >= 0xD800 && code <= 0xDBFF
}

///|
/// Returns true if the UTF-16 code point is a trailing surrogate (low surrogate).
pub fn is_trailing_surrogate(code : Int) -> Bool {
  code >= 0xDC00 && code <= 0xDFFF
}

///|
/// Returns the safe cursor index stepping backwards by 1 Unicode scalar.
/// If preceded by a UTF-16 surrogate pair, steps back by 2 code units.
pub fn prev_char_boundary(text : String, pos : Int) -> Int {
  if pos <= 0 {
    0
  } else if pos >= 2 &&
    is_trailing_surrogate(text[pos - 1].to_int()) &&
    is_leading_surrogate(text[pos - 2].to_int()) {
    pos - 2
  } else {
    pos - 1
  }
}

///|
/// Returns the safe cursor index stepping forwards by 1 Unicode scalar.
/// If at the start of a UTF-16 surrogate pair, steps forwards by 2 code units.
pub fn next_char_boundary(text : String, pos : Int) -> Int {
  let len = text.length()
  if pos >= len {
    len
  } else if pos + 1 < len &&
    is_leading_surrogate(text[pos].to_int()) &&
    is_trailing_surrogate(text[pos + 1].to_int()) {
    pos + 2
  } else {
    pos + 1
  }
}

///|
/// Deletes 1 Unicode scalar before cursor_pos and returns (new_text, new_cursor_pos).
pub fn delete_prev_char(text : String, pos : Int) -> (String, Int) {
  if pos <= 0 || text.length() == 0 {
    (text, 0)
  } else {
    let prev = prev_char_boundary(text, pos)
    let left = string_slice(text, 0, prev)
    let right = string_slice(text, pos, text.length())
    (left + right, prev)
  }
}

///|
/// Deletes 1 Unicode scalar at cursor_pos and returns new_text.
pub fn delete_next_char(text : String, pos : Int) -> String {
  let len = text.length()
  if pos >= len || len == 0 {
    text
  } else {
    let next = next_char_boundary(text, pos)
    let left = string_slice(text, 0, pos)
    let right = string_slice(text, next, len)
    left + right
  }
}

///|
/// Returns true if the character code is in common East Asian / CJK script ranges.
pub fn is_cjk_char(code : Int) -> Bool {
  (code >= 0x4E00 && code <= 0x9FFF) || // CJK Unified Ideographs
  (code >= 0x3400 && code <= 0x4DBF) || // CJK Extension A
  (code >= 0xF900 && code <= 0xFAFF) || // CJK Compatibility
  (code >= 0x3000 && code <= 0x303F) || // CJK Symbols & Punctuation
  (code >= 0x3040 && code <= 0x309F) || // Hiragana
  (code >= 0x30A0 && code <= 0x30FF) || // Katakana
  (code >= 0xAC00 && code <= 0xD7AF) || // Hangul Syllables
  (code >= 0xFF00 && code <= 0xFFEF) // Halfwidth and Fullwidth Forms
}

///|
/// Returns true if the character code is a closing quotation or punctuation mark
/// that should not start a new line under standard CJK typesetting rules.
pub fn is_cjk_closing_punct(code : Int) -> Bool {
  match code {
    0x3001 | 0x3002 | 0xFF0C | 0xFF0E | 0xFF01 | 0xFF1F | 0xFF1B | 0xFF1A =>
      true
    0x3009 | 0x300B | 0x300D | 0x300F | 0x3011 | 0x3015 | 0xFF09 | 0xFF5D =>
      true
    0x201D | 0x2019 => true
    _ => false
  }
}