use std::path::Path;

use comemo::{Prehashed, Track};
use typst::diag::{FileError, FileResult};
use typst::eval::{Bytes, Datetime, Library, Module};
use typst::font::{Font, FontBook};
use typst::syntax::{FileId, Source, VirtualPath};
use typst::World;

struct EvalWorld {
    library: Prehashed<Library>,
    font_book: Prehashed<FontBook>,
    main: Source,
}

impl EvalWorld {
    fn new(main_file: impl AsRef<Path>) -> std::io::Result<Self> {
        let main_data = std::fs::read_to_string(main_file.as_ref())?;
        let main_id = FileId::new(None, VirtualPath::new(main_file));

        Ok(Self {
            library: Prehashed::new(typst_library::build()),
            font_book: Prehashed::new(FontBook::new()),
            main: Source::new(main_id, main_data),
        })
    }
}

impl World for EvalWorld {
    fn library(&self) -> &Prehashed<Library> {
        &self.library
    }

    fn book(&self) -> &Prehashed<FontBook> {
        &self.font_book
    }

    fn main(&self) -> Source {
        self.main.clone()
    }

    fn source(&self, id: FileId) -> FileResult<Source> {
        let bytes = self.file(id)?;
        let text = String::from_utf8_lossy(bytes.as_slice());
        let source = Source::new(id, text.to_string());

        Ok(source)
    }

    fn file(&self, id: FileId) -> FileResult<Bytes> {
        let path = id.vpath().as_rootless_path();
        match std::fs::read(path) {
            Ok(bytes) => Ok(Bytes::from(bytes)),
            Err(io_error) => Err(FileError::from_io(io_error, path)),
        }
    }

    fn font(&self, _index: usize) -> Option<Font> {
        None
    }

    fn today(&self, _offset: Option<i64>) -> Option<Datetime> {
        None
    }
}

pub fn eval_file(main_file: impl AsRef<Path>) -> Module {
    let world = EvalWorld::new(main_file).unwrap();
    let route = typst::eval::Route::default();
    let mut tracer = typst::eval::Tracer::default();
    typst::eval::eval(
        (&world as &dyn World).track(),
        route.track(),
        tracer.track_mut(),
        &world.main(),
    )
    .unwrap()
}