/// Current cursor location
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct Cursor {
    /// Index of [`BufferLine`] in [`Buffer::lines`]
    pub line: usize,
    /// First-byte-index of glyph at cursor (will insert behind this glyph)
    pub index: usize,
    /// Whether to associate the cursor with the run before it or the run after it if placed at the
    /// boundary between two runs
    pub affinity: Affinity,
}

impl Cursor {
    /// Create a new cursor
    pub const fn new(line: usize, index: usize) -> Self {
        Self::new_with_affinity(line, index, Affinity::Before)
    }

    /// Create a new cursor, specifying the affinity
    pub const fn new_with_affinity(line: usize, index: usize, affinity: Affinity) -> Self {
        Self {
            line,
            index,
            affinity,
        }
    }
}

/// Whether to associate cursors placed at a boundary between runs with the run before or after it.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub enum Affinity {
    #[default]
    Before,
    After,
}

impl Affinity {
    pub fn before(&self) -> bool {
        *self == Self::Before
    }

    pub fn after(&self) -> bool {
        *self == Self::After
    }

    pub fn from_before(before: bool) -> Self {
        if before {
            Self::Before
        } else {
            Self::After
        }
    }

    pub fn from_after(after: bool) -> Self {
        if after {
            Self::After
        } else {
            Self::Before
        }
    }
}

/// The position of a cursor within a [`Buffer`].
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct LayoutCursor {
    /// Index of [`BufferLine`] in [`Buffer::lines`]
    pub line: usize,
    /// Index of [`LayoutLine`] in [`BufferLine::layout`]
    pub layout: usize,
    /// Index of [`LayoutGlyph`] in [`LayoutLine::glyphs`]
    pub glyph: usize,
}

impl LayoutCursor {
    /// Create a new [`LayoutCursor`]
    pub const fn new(line: usize, layout: usize, glyph: usize) -> Self {
        Self {
            line,
            layout,
            glyph,
        }
    }
}

/// A motion to perform on a [`Cursor`]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Motion {
    /// Apply specific [`LayoutCursor`]
    LayoutCursor(LayoutCursor),
    /// Move cursor to previous character ([Self::Left] in LTR, [Self::Right] in RTL)
    Previous,
    /// Move cursor to next character ([Self::Right] in LTR, [Self::Left] in RTL)
    Next,
    /// Move cursor left
    Left,
    /// Move cursor right
    Right,
    /// Move cursor up
    Up,
    /// Move cursor down
    Down,
    /// Move cursor to start of line
    Home,
    /// Move cursor to start of line, skipping whitespace
    SoftHome,
    /// Move cursor to end of line
    End,
    /// Move cursor to start of paragraph
    ParagraphStart,
    /// Move cursor to end of paragraph
    ParagraphEnd,
    /// Move cursor up one page
    PageUp,
    /// Move cursor down one page
    PageDown,
    /// Move cursor up or down by a number of pixels
    Vertical(i32),
    /// Move cursor to previous word boundary
    PreviousWord,
    /// Move cursor to next word boundary
    NextWord,
    /// Move cursor to next word boundary to the left
    LeftWord,
    /// Move cursor to next word boundary to the right
    RightWord,
    /// Move cursor to the start of the document
    BufferStart,
    /// Move cursor to the end of the document
    BufferEnd,
    /// Move cursor to specific line
    GotoLine(usize),
}

/// Scroll position in [`Buffer`]
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
pub struct Scroll {
    /// Index of [`BufferLine`] in [`Buffer::lines`]. This will be adjusted as needed if layout is
    /// out of bounds
    pub line: usize,
    /// Pixel offset from the start of the [`BufferLine`]. This will be adjusted as needed
    /// if it is negative or exceeds the height of the [`BufferLine::layout`] lines.
    pub vertical: f32,
    /// The horizontal position of scroll in fractional pixels
    pub horizontal: f32,
}

impl Scroll {
    /// Create a new scroll
    pub const fn new(line: usize, vertical: f32, horizontal: f32) -> Self {
        Self {
            line,
            vertical,
            horizontal,
        }
    }
}
