use super::error::{YamlError, YamlErrorKind};

#[derive(Clone, Copy)]
pub struct YamlLine<'a> {
    pub indent: usize,
    pub content: &'a str,
    pub offset: usize,
}

pub struct LineCursor<'a> {
    bytes: &'a [u8],
    pos: usize,
}

impl<'a> LineCursor<'a> {
    pub const fn new(bytes: &'a [u8]) -> Self {
        Self { bytes, pos: 0 }
    }

    pub const fn position(&self) -> usize {
        self.pos
    }

    pub fn peek(&self) -> Result<Option<YamlLine<'a>>, YamlError> {
        self.scan_from(self.pos)
    }

    pub fn next(&mut self) -> Result<Option<YamlLine<'a>>, YamlError> {
        let line = self.scan_from(self.pos)?;
        if let Some(ref l) = line {
            self.pos = l.offset - l.indent;
            self.advance_to_next_significant_line()?;
        }
        Ok(line)
    }

    fn advance_to_next_significant_line(&mut self) -> Result<(), YamlError> {
        while self.pos < self.bytes.len() {
            let line_end = find_line_end(self.bytes, self.pos);
            self.pos = if line_end < self.bytes.len() {
                line_end + 1
            } else {
                line_end
            };

            if self.scan_from(self.pos)?.is_some() {
                return Ok(());
            }
        }
        Ok(())
    }

    fn scan_from(&self, mut start: usize) -> Result<Option<YamlLine<'a>>, YamlError> {
        while start < self.bytes.len() {
            let line_end = find_line_end(self.bytes, start);
            let line_bytes = &self.bytes[start..line_end];

            let mut idx = 0usize;
            let mut indent = 0usize;
            while idx < line_bytes.len() {
                match line_bytes[idx] {
                    b' ' => {
                        indent += 1;
                        idx += 1;
                    }
                    b'\t' => {
                        return Err(YamlError::new(
                            YamlErrorKind::InvalidIndentation,
                            start + idx,
                        ));
                    }
                    _ => break,
                }
            }

            let content_start = idx;
            if content_start >= line_bytes.len() {
                start = if line_end < self.bytes.len() {
                    line_end + 1
                } else {
                    line_end
                };
                continue;
            }

            if line_bytes[content_start] == b'#' {
                start = if line_end < self.bytes.len() {
                    line_end + 1
                } else {
                    line_end
                };
                continue;
            }

            let mut content_end = line_bytes.len();
            while content_end > content_start && line_bytes[content_end - 1] == b' ' {
                content_end -= 1;
            }

            let content_bytes = &line_bytes[content_start..content_end];
            let content = core::str::from_utf8(content_bytes).map_err(|_| {
                YamlError::new(YamlErrorKind::UnexpectedToken, start + content_start)
            })?;

            return Ok(Some(YamlLine {
                indent,
                content,
                offset: start + content_start,
            }));
        }

        Ok(None)
    }
}

fn find_line_end(bytes: &[u8], mut start: usize) -> usize {
    while start < bytes.len() {
        if bytes[start] == b'\n' {
            return start;
        }
        start += 1;
    }
    bytes.len()
}
