use std::{borrow::Cow, collections::HashMap, path::PathBuf};
use typst::{
    diag::{FileError, FileResult},
    foundations::Bytes,
    syntax::{FileId, Source},
};
use crate::{
    util::{bytes_to_source, not_found},
    FileIdNewType, SourceNewType,
};
pub trait FileResolver {
    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>>;
    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>>;
}
#[derive(Debug, Clone)]
pub(crate) struct MainSourceFileResolver {
    main_source: Source,
}
impl MainSourceFileResolver {
    pub(crate) fn new(main_source: Source) -> Self {
        Self { main_source }
    }
}
impl FileResolver for MainSourceFileResolver {
    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>> {
        Err(not_found(id))
    }
    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>> {
        let Self { main_source } = self;
        if id == main_source.id() {
            return Ok(Cow::Borrowed(main_source));
        }
        Err(not_found(id))
    }
}
#[derive(Debug, Clone)]
pub struct StaticSourceFileResolver {
    sources: HashMap<FileId, Source>,
}
impl StaticSourceFileResolver {
    pub(crate) fn new<IS, S>(sources: IS) -> Self
    where
        IS: IntoIterator<Item = S>,
        S: Into<SourceNewType>,
    {
        let sources = sources
            .into_iter()
            .map(|s| {
                let SourceNewType(s) = s.into();
                (s.id(), s)
            })
            .collect();
        Self { sources }
    }
}
impl FileResolver for StaticSourceFileResolver {
    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>> {
        Err(not_found(id))
    }
    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>> {
        self.sources
            .get(&id)
            .map(|s| Cow::Borrowed(s))
            .ok_or_else(|| not_found(id))
    }
}
#[derive(Debug, Clone)]
pub struct StaticFileResolver {
    binaries: HashMap<FileId, Bytes>,
}
impl StaticFileResolver {
    pub(crate) fn new<IB, F, B>(binaries: IB) -> Self
    where
        IB: IntoIterator<Item = (F, B)>,
        F: Into<FileIdNewType>,
        B: Into<Bytes>,
    {
        let binaries = binaries
            .into_iter()
            .map(|(id, b)| {
                let FileIdNewType(id) = id.into();
                (id, b.into())
            })
            .collect();
        Self { binaries }
    }
}
impl FileResolver for StaticFileResolver {
    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>> {
        self.binaries
            .get(&id)
            .map(|b| Cow::Borrowed(b))
            .ok_or_else(|| not_found(id))
    }
    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>> {
        Err(not_found(id))
    }
}
#[derive(Debug, Clone)]
pub struct FileSystemResolver {
    root: PathBuf,
}
impl FileSystemResolver {
    pub fn new(root: PathBuf) -> Self {
        let mut root = root.clone();
        root.push("");
        Self { root }
    }
}
impl FileSystemResolver {
    fn resolve_bytes(&self, id: FileId) -> FileResult<Vec<u8>> {
        if id.package().is_some() {
            return Err(not_found(id));
        }
        let Self { root } = self;
        let path = id.vpath().resolve(&root).ok_or(FileError::AccessDenied)?;
        let content = std::fs::read(&path).map_err(|error| FileError::from_io(error, &path))?;
        Ok(content.into())
    }
}
impl FileResolver for FileSystemResolver {
    fn resolve_binary(&self, id: FileId) -> FileResult<Cow<Bytes>> {
        let b = self.resolve_bytes(id)?;
        Ok(Cow::Owned(b.into()))
    }
    fn resolve_source(&self, id: FileId) -> FileResult<Cow<Source>> {
        let file = self.resolve_bytes(id)?;
        let source = bytes_to_source(id, &file)?;
        Ok(Cow::Owned(source))
    }
}