use std::{io::Read, path::PathBuf};

use miette::{SourceCode, SourceSpan, SpanContents};

#[derive(Debug)]
pub struct SourceFile {
    pub(crate) path: PathBuf,
}

impl SourceCode for SourceFile {
    fn read_span<'a>(
        &'a self,
        span: &miette::SourceSpan,
        context_lines_before: usize,
        context_lines_after: usize,
    ) -> std::result::Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
        let mut file = std::fs::File::open(&self.path)?;

        // read keeping the last

        let mut data = Vec::new();

        file.read_to_end(&mut data)?;

        let data = data.leak();

        let line = data.iter().filter(|&&x| x == b'\n').count();

        let mut start_new_lines = 0;

        let start_pos = data[0..span.offset()]
            .iter()
            .rposition(|&x| {
                if x == b'\n' {
                    if start_new_lines > context_lines_before {
                        true
                    } else {
                        start_new_lines += 1;
                        false
                    }
                } else {
                    false
                }
            })
            .map(|x| x + 1)
            .unwrap_or(0);

        let mut end_new_lines = 0;

        let end_pos = data[span.offset()..]
            .iter()
            .position(|&x| {
                if x == b'\n' {
                    if end_new_lines >= context_lines_after {
                        true
                    } else {
                        end_new_lines += 1;
                        false
                    }
                } else {
                    false
                }
            })
            .map(|x| x + span.offset())
            .unwrap_or(data.len());

        let compact_data = &data[start_pos..end_pos];

        Ok(Box::new(SourceFileSpanContents {
            span: SourceSpan::new(start_pos.into(), end_pos - start_pos),
            start_line_number: line - start_new_lines,
            line_count: start_new_lines + 1 + end_new_lines,
            path: self.path.clone(),
            data: compact_data,
        }))
    }
}

#[derive(Debug)]
struct SourceFileSpanContents {
    data: &'static [u8],
    span: SourceSpan,
    start_line_number: usize,
    line_count: usize,
    path: PathBuf,
}

impl<'a> SpanContents<'a> for SourceFileSpanContents {
    fn data(&'_ self) -> &'a [u8] {
        self.data
    }

    fn span(&self) -> &SourceSpan {
        &self.span
    }

    fn line(&self) -> usize {
        self.start_line_number
    }

    fn column(&self) -> usize {
        0
    }

    fn line_count(&self) -> usize {
        self.line_count
    }

    fn name(&self) -> Option<&str> {
        self.path.to_str()
    }

    fn language(&self) -> Option<&str> {
        None
    }
}
