// Copyright 2019 H�ctor Ram�n, Iced contributors
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MIT

use unicode_segmentation::UnicodeSegmentation;

/// The value of a [`TextInput`].
///
/// [`TextInput`]: crate::widget::TextInput
// TODO: Reduce allocations, cache results (?)
#[derive(Default, Debug, Clone, PartialEq)]
pub struct Value {
    graphemes: Vec<String>,
}

impl Value {
    /// Creates a new [`Value`] from a string slice.
    pub fn new(string: &str) -> Self {
        let graphemes = UnicodeSegmentation::graphemes(string, true)
            .map(String::from)
            .collect();

        Self { graphemes }
    }

    /// Returns whether the [`Value`] is empty or not.
    ///
    /// A [`Value`] is empty when it contains no graphemes.
    #[must_use]
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Returns the total amount of graphemes in the [`Value`].
    #[must_use]
    #[inline]
    pub fn len(&self) -> usize {
        self.graphemes.len()
    }

    /// Returns the position of the previous start of a word from the given
    /// grapheme `index`.
    #[must_use]
    pub fn previous_start_of_word(&self, index: usize) -> usize {
        let previous_string = &self.graphemes[..index.min(self.graphemes.len())].concat();

        UnicodeSegmentation::split_word_bound_indices(previous_string as &str).rfind(|(_, word)| !word.trim_start().is_empty())
            .map_or(0, |(i, previous_word)| {
                index
                    - UnicodeSegmentation::graphemes(previous_word, true).count()
                    - UnicodeSegmentation::graphemes(
                        &previous_string[i + previous_word.len()..] as &str,
                        true,
                    )
                    .count()
            })
    }

    /// Returns the position of the next end of a word from the given grapheme
    /// `index`.
    #[must_use]
    pub fn next_end_of_word(&self, index: usize) -> usize {
        let next_string = &self.graphemes[index..].concat();

        UnicodeSegmentation::split_word_bound_indices(next_string as &str)
            .find(|(_, word)| !word.trim_start().is_empty())
            .map_or(self.len(), |(i, next_word)| {
                index
                    + UnicodeSegmentation::graphemes(next_word, true).count()
                    + UnicodeSegmentation::graphemes(&next_string[..i] as &str, true).count()
            })
    }

    /// Returns a new [`Value`] containing the graphemes from `start` until the
    /// given `end`.
    #[must_use]
    #[inline]
    pub fn select(&self, start: usize, end: usize) -> Self {
        let graphemes = self.graphemes[start.min(self.len())..end.min(self.len())].to_vec();

        Self { graphemes }
    }

    /// Returns a new [`Value`] containing the graphemes until the given
    /// `index`.
    #[must_use]
    #[inline]
    pub fn until(&self, index: usize) -> Self {
        let graphemes = self.graphemes[..index.min(self.len())].to_vec();

        Self { graphemes }
    }

    /// Inserts a new `char` at the given grapheme `index`.
    #[inline]
    pub fn insert(&mut self, index: usize, c: char) {
        self.graphemes.insert(index, c.to_string());

        self.graphemes = UnicodeSegmentation::graphemes(&self.to_string() as &str, true)
            .map(String::from)
            .collect();
    }

    /// Inserts a bunch of graphemes at the given grapheme `index`.
    #[inline]
    pub fn insert_many(&mut self, index: usize, mut value: Value) {
        let _ = self
            .graphemes
            .splice(index..index, value.graphemes.drain(..));
    }

    /// Removes the grapheme at the given `index`.
    #[inline]
    pub fn remove(&mut self, index: usize) {
        let _ = self.graphemes.remove(index);
    }

    /// Removes the graphemes from `start` to `end`.
    #[inline]
    pub fn remove_many(&mut self, start: usize, end: usize) {
        let _ = self.graphemes.splice(start..end, std::iter::empty());
    }

    /// Returns a new [`Value`] with all its graphemes replaced with the
    /// dot ('•') character.
    #[must_use]
    pub fn secure(&self) -> Self {
        Self {
            graphemes: std::iter::repeat_n(String::from("•"), self.graphemes.len()).collect(),
        }
    }

    /// Converts a grapheme index to a byte index in the underlying string.
    #[must_use]
    pub fn byte_index_at_grapheme(&self, grapheme_index: usize) -> usize {
        self.graphemes[..grapheme_index.min(self.graphemes.len())]
            .iter()
            .map(|g| g.len())
            .sum()
    }

    /// Returns the grapheme index of the last occurrence of the given character,
    /// searching from the end.
    #[must_use]
    pub fn rfind_char(&self, ch: char) -> Option<usize> {
        let needle = ch.to_string();
        self.graphemes.iter().rposition(|g| g == &needle)
    }

    /// Converts a byte index to a grapheme index.
    #[must_use]
    pub fn grapheme_index_at_byte(&self, byte_index: usize) -> usize {
        let mut bytes = 0;
        for (i, g) in self.graphemes.iter().enumerate() {
            if bytes >= byte_index {
                return i;
            }
            bytes += g.len();
        }

        self.graphemes.len()
    }
}

impl std::fmt::Display for Value {
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.graphemes.concat())
    }
}
