use ribir_algo::Arc;
use ribir_types::{BoxClamp, Point, Rect, Size};

use crate::{
  paint::TextDrawPayload,
  style::{ParagraphStyle, SpanStyle, TextAlign, TextStyle},
};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
pub struct TextByteIndex(pub usize);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct TextRange {
  pub start: TextByteIndex,
  pub end: TextByteIndex,
}

impl TextRange {
  pub fn new(start: usize, end: usize) -> Self {
    Self { start: TextByteIndex(start), end: TextByteIndex(end) }
  }

  pub fn is_empty(&self) -> bool { self.start == self.end }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
pub struct ClusterIndex(pub usize);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
pub struct LineIndex(pub usize);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct VisualPosition {
  pub line: LineIndex,
  pub slot: usize,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CaretAffinity {
  Upstream,
  Downstream,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Caret {
  pub byte: TextByteIndex,
  pub affinity: CaretAffinity,
  pub visual: Option<VisualPosition>,
}

impl Default for Caret {
  fn default() -> Self {
    Self { byte: TextByteIndex(0), affinity: CaretAffinity::Downstream, visual: None }
  }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CaretMotion {
  Prev,
  Next,
  Up,
  Down,
  WordPrev,
  WordNext,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TextHitResult {
  pub caret: Caret,
  pub is_inside: bool,
}

#[derive(Debug, Clone, PartialEq)]
pub struct TextSpan<Brush> {
  pub range: TextRange,
  pub style: SpanStyle<Brush>,
}

pub type LayoutClamp = BoxClamp;
pub type ParagraphLayoutRef<Brush> = Arc<Box<dyn ParagraphLayout<Brush>>>;

pub trait Paragraph<Brush> {
  fn source_len(&self) -> TextByteIndex;

  fn min_intrinsic_width(&self, text_style: &TextStyle, paragraph_style: &ParagraphStyle) -> f32;

  fn max_intrinsic_width(&self, text_style: &TextStyle, paragraph_style: &ParagraphStyle) -> f32;

  fn layout(
    &self, text_style: &TextStyle, paragraph_style: &ParagraphStyle, clamp: BoxClamp,
  ) -> ParagraphLayoutRef<Brush>;
}

pub trait ParagraphLayout<Brush> {
  fn size(&self) -> Size;

  fn aligned(&self, text_align: TextAlign, size: Size) -> ParagraphLayoutRef<Brush>;

  fn draw_payload(&self) -> &TextDrawPayload<Brush>;

  fn hit_test_point(&self, point: Point) -> TextHitResult;

  fn caret_rect(&self, caret: Caret) -> Rect;

  fn selection_rects(&self, selection: TextRange) -> Box<[Rect]>;

  fn move_caret(&self, caret: Caret, motion: CaretMotion) -> Caret;

  fn line_start_caret(&self, caret: Caret) -> Caret;

  fn line_end_caret(&self, caret: Caret) -> Caret;
}
