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

///|
/// Rendering helpers for Editor (ported from `cosmic-text/src/edit/editor.rs`).
fn int_from_float(v : Float) -> Int {
  v.to_double().to_int()
}

///|
fn uint_from_int(v : Int) -> UInt {
  if v <= 0 {
    0U
  } else {
    v.reinterpret_as_uint()
  }
}

///|
fn width_int(width_opt : Float?) -> Int {
  match width_opt {
    None => 0
    Some(w) => int_from_float(w)
  }
}

///|
fn x_for_index(run : LayoutRun, index : Int) -> Float {
  let glyphs = run.glyphs
  if glyphs.length() == 0 {
    return 0.0F
  }
  // Order-independent mapping (glyphs may be in visual order, not start-index order).
  let mut min_start = glyphs[0].start
  let mut min_start_x0 = glyphs[0].x
  let mut min_start_x1 = glyphs[0].x + glyphs[0].w
  let mut max_end = glyphs[0].end
  let mut max_end_x0 = glyphs[0].x
  let mut max_end_x1 = glyphs[0].x + glyphs[0].w
  for g in glyphs {
    if g.start < min_start {
      min_start = g.start
      min_start_x0 = g.x
      min_start_x1 = g.x + g.w
    }
    if g.end > max_end {
      max_end = g.end
      max_end_x0 = g.x
      max_end_x1 = g.x + g.w
    }
    if index == g.start {
      return if run.rtl { g.x + g.w } else { g.x }
    }
    if index == g.end {
      return if run.rtl { g.x } else { g.x + g.w }
    }
    if index > g.start && index < g.end {
      let span = g.end - g.start
      let off = index - g.start
      let t = Float::from_double(off.to_double() / span.to_double())
      return if run.rtl { g.x + g.w - g.w * t } else { g.x + g.w * t }
    }
  }
  if index <= min_start {
    if run.rtl {
      min_start_x1
    } else {
      min_start_x0
    }
  } else if index >= max_end {
    if run.rtl {
      max_end_x0
    } else {
      max_end_x1
    }
  } else {
    0.0F
  }
}

///|
fn cursor_position_in_run(cursor : Cursor, run : LayoutRun) -> (Int, Int)? {
  if cursor.line != run.line_i {
    return None
  }
  if run.glyphs.length() == 0 {
    return Some((0, int_from_float(run.line_top)))
  }
  // Prefer the run whose glyph span contains the index.
  let mut min_start = run.glyphs[0].start
  let mut max_end = run.glyphs[0].end
  for g in run.glyphs {
    if g.start < min_start {
      min_start = g.start
    }
    if g.end > max_end {
      max_end = g.end
    }
  }
  if cursor.index < min_start || cursor.index > max_end {
    return None
  }
  let x = x_for_index(run, cursor.index)
  Some((int_from_float(x), int_from_float(run.line_top)))
}

///|
/// Get X and Y position of the top-left corner of the cursor.
pub fn Editor::cursor_position(self : Editor) -> (Int, Int)? {
  let buffer = self.buffer.layout_all()
  let mut fallback : (Int, Int)? = None
  for run in buffer.layout_runs() {
    if run.line_i == self.cursor.line {
      // Keep last run on this line as a fallback (for wrapped lines).
      fallback = Some(
        (int_from_float(run.line_w), int_from_float(run.line_top)),
      )
    }
    match cursor_position_in_run(self.cursor, run) {
      None => ()
      Some(pos) => return Some(pos)
    }
  }
  fallback
}

///|
/// Render editor contents with selection and cursor highlights.
pub fn[R : Renderer] Editor::render(
  self : Editor,
  renderer : R,
  text_color : Color,
  cursor_color : Color,
  selection_color : Color,
  selected_text_color : Color,
) -> Unit {
  let selection_bounds = self.selection_bounds()
  let buffer = self.buffer.layout_all()
  let width_opt = buffer.size().0
  for run in buffer.layout_runs() {
    // Highlight selection.
    match selection_bounds {
      None => ()
      Some((start, end)) =>
        if run.line_i >= start.line && run.line_i <= end.line {
          let mut range_opt : (Int, Int)? = None
          for glyph in run.glyphs {
            // Split the glyph's cluster into grapheme-sized subcells (upstream behavior).
            let cluster = slice_string(run.text, glyph.start, glyph.end)
            let graphemes = grapheme_indices_uax29(cluster)
            let total = graphemes.length()
            if total == 0 {
              continue
            }
            let c_w = glyph.w / Float::from_double(total.to_double())
            let mut c_x = glyph.x
            for r in graphemes {
              let c_start = glyph.start + r.0
              let c_end = glyph.start + r.1
              if (start.line != run.line_i || c_end > start.index) &&
                (end.line != run.line_i || c_start < end.index) {
                let x0 = int_from_float(c_x)
                let x1 = int_from_float(c_x + c_w)
                match range_opt {
                  None => range_opt = Some((x0, x1))
                  Some((min0, max0)) => {
                    let min1 = if x0 < min0 { x0 } else { min0 }
                    let max1 = if x1 > max0 { x1 } else { max0 }
                    range_opt = Some((min1, max1))
                  }
                }
              } else {
                match range_opt {
                  None => ()
                  Some((min0, max0)) => {
                    renderer.rectangle(
                      min0,
                      int_from_float(run.line_top),
                      uint_from_int(max0 - min0),
                      uint_from_int(int_from_float(run.line_height)),
                      selection_color,
                    )
                    range_opt = None
                  }
                }
              }
              c_x = c_x + c_w
            }
          }
          if run.glyphs.length() == 0 && end.line > run.line_i {
            // Highlight internal empty lines.
            range_opt = Some((0, width_int(width_opt)))
          }
          match range_opt {
            None => ()
            Some((min0, max0)) => {
              let mut min = min0
              let mut max = max0
              if end.line > run.line_i {
                // Draw to end of line.
                let w = width_int(width_opt)
                if run.rtl {
                  min = 0
                } else {
                  max = w
                }
              }
              renderer.rectangle(
                min,
                int_from_float(run.line_top),
                uint_from_int(max - min),
                uint_from_int(int_from_float(run.line_height)),
                selection_color,
              )
            }
          }
        }
    }

    // Draw cursor.
    match cursor_position_in_run(self.cursor, run) {
      None => ()
      Some((x, y)) =>
        renderer.rectangle(
          x,
          y,
          1U,
          uint_from_int(int_from_float(run.line_height)),
          cursor_color,
        )
    }

    // Draw glyphs.
    for g in run.glyphs {
      let physical = g.physical((0.0F, run.line_y), 1.0F)
      let mut glyph_color = match g.color_opt {
        None => text_color
        Some(c) => c
      }
      if text_color != selected_text_color {
        match selection_bounds {
          None => ()
          Some((start, end)) =>
            if run.line_i >= start.line &&
              run.line_i <= end.line &&
              (start.line != run.line_i || g.end > start.index) &&
              (end.line != run.line_i || g.start < end.index) {
              glyph_color = selected_text_color
            }
        }
      }
      renderer.glyph(physical, glyph_color)
    }
  }
}