use crate::debugger::PlaceDescriptor;
use crate::ui::syntax;
use crate::ui::syntax::StylizedLine;
use std::cell::RefCell;
use std::collections::HashMap;
use std::io::BufRead;
use std::path::{Path, PathBuf};
use std::{fs, io};
use syntect::util::as_24_bit_terminal_escaped;

#[derive(Default)]
pub struct FileView {
    cached_lines: RefCell<HashMap<PathBuf, Box<[String]>>>,
}

impl FileView {
    #[allow(clippy::new_without_default)]
    pub fn new() -> Self {
        Self::default()
    }

    fn render(&self, file_path: &Path, start: u64, length: u64) -> anyhow::Result<String> {
        let mut cache = self.cached_lines.borrow_mut();
        let file_lines = match cache.get(file_path) {
            None => {
                let file = fs::File::open(file_path)?;
                let lines = io::BufReader::new(file)
                    .lines()
                    .map_while(Result::ok)
                    .collect::<Vec<_>>();
                cache.insert(file_path.to_path_buf(), lines.into_boxed_slice());
                cache.get(file_path).unwrap()
            }
            Some(lines) => lines,
        };

        let syntax_renderer = syntax::rust_syntax_renderer();
        let mut line_renderer = syntax_renderer.line_renderer();

        let mut i = 0;
        let result = file_lines
            .iter()
            .skip(start as usize)
            .take(length as usize)
            .try_fold(String::default(), |acc, line| -> anyhow::Result<String> {
                let line_number = start + 1 + i;
                i += 1;

                match line_renderer.render_line(line)? {
                    StylizedLine::NoneStyle(line) => Ok(format!("{acc}{line_number:>4} {line}\n")),
                    StylizedLine::Stylized(segments) => {
                        let escaped = as_24_bit_terminal_escaped(&segments, false);
                        Ok(format!("{acc}{line_number:>4} {escaped}\x1b[0m\n"))
                    }
                }
            })?;

        Ok(result)
    }

    pub fn render_source(&self, place: &PlaceDescriptor, bounds: u64) -> anyhow::Result<String> {
        let line_number = if place.line_number == 0 {
            1
        } else {
            place.line_number
        };
        let line_pos = line_number - 1;
        let start = line_pos.saturating_sub(bounds);

        self.render(place.file, start, bounds * 2 + 1)
    }

    pub fn render_source_range(
        &self,
        file: &Path,
        from_line: u64,
        to_line: u64,
    ) -> anyhow::Result<String> {
        let start = if from_line == 0 { 0 } else { from_line - 1 };
        let bound = to_line - from_line + 1;

        self.render(file, start, bound)
    }
}
