use crate::field_access;

/// Represents the state of an input list.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InputListState {
    offset: usize,
    selected: Option<usize>,
    length: usize,
}

impl InputListState {
    /// Creates a new [InputListState].
    pub const fn new() -> Self {
        Self {
            offset: 0,
            selected: None,
            length: 0,
        }
    }

    /// Creates a new [InputListState] with the provided list length.
    pub const fn create(length: usize) -> Self {
        Self {
            offset: 0,
            selected: None,
            length,
        }
    }

    /// Scrolls down the first by the provided amount.
    ///
    /// If the amount scrolls out-of-bounds, the first item is selected.
    pub fn scroll_up_by(&mut self, val: usize) -> usize {
        match self.selected.map(|s| s.saturating_sub(val)) {
            Some(s) if s > self.offset => {
                self.selected.replace(s);
                s
            }
            Some(_) | None => {
                let s = self.offset;
                self.selected.replace(s);
                s
            }
        }
    }

    /// Scrolls down the list by the provided amount.
    ///
    /// If the amount scrolls out-of-bounds, the last item is selected.
    pub fn scroll_down_by(&mut self, val: usize) -> usize {
        match self.selected.map(|s| s.saturating_add(val)) {
            Some(s) if s < self.length => {
                self.selected.replace(s);
                s
            }
            Some(_) => {
                let s = self.length - 1;
                self.selected.replace(s);
                s
            }
            None => {
                let s = match self.offset + val {
                    l if l < self.length => l,
                    _ => self.length - 1,
                };
                self.selected.replace(s);
                s
            }
        }
    }

    /// Selects the provided index.
    ///
    /// If the index is out-of-bounds:
    ///
    /// - below offset: offet is selected
    /// - above last item: the last item is selected
    pub fn select(&mut self, val: usize) -> usize {
        let s = match val {
            v if v < self.offset => self.offset,
            v if (self.offset..self.length).contains(&val) => v,
            _ => self.length - 1,
        };
        self.selected.replace(s);
        s
    }

    /// Selects the first list entry.
    pub fn select_first(&mut self) -> usize {
        self.selected.replace(self.offset);
        self.offset
    }

    /// Selects the last list entry.
    pub fn select_last(&mut self) -> usize {
        self.selected.replace(self.length - 1);
        self.offset
    }

    /// Selects the previous list entry.
    pub fn select_previous(&mut self) -> usize {
        let last = self.length.saturating_sub(1);
        let ps = match self.selected.map(|s| s.saturating_sub(1)) {
            Some(v) if v < self.offset => last,
            Some(v) => v,
            None => last,
        };
        self.selected.replace(ps);
        ps
    }

    /// Selects the next list entry.
    pub fn select_next(&mut self) -> usize {
        let s = self.selected.unwrap_or(self.offset);
        // select the next entry, wrap around to the beginning of the list (+ offset) on overflow
        let ns = match s.saturating_add(1) {
            s if s < self.length => s,
            s => (s % self.length) + self.offset,
        };
        self.selected.replace(ns);
        ns
    }

    /// Gets the current item.
    ///
    /// If no item is selected, returns the first item.
    pub fn current(&self) -> usize {
        self.selected.unwrap_or(self.offset)
    }
}

field_access! {
    InputListState {
        length: usize,
        offset: usize,
    }
}

field_access! {
    InputListState {
        selected: option { usize },
    }
}

impl Default for InputListState {
    fn default() -> Self {
        Self::new()
    }
}
