use std::{
    error::Error,
    fs::{read_to_string, OpenOptions},
    io::Write,
    path::{Path, PathBuf},
};

use serde::{de::DeserializeOwned, Serialize};
use tracing;

use crate::utils::misc::create_path;

pub trait Load
where
    Self: Sized + DeserializeOwned,
{
    fn load(path: impl Into<PathBuf>) -> Self {
        let contents = read_to_string(path.into()).unwrap();
        let deserialized: Self = serde_json::from_str(&contents).unwrap();

        deserialized
    }
}

pub trait Save
where
    Self: Serialize,
{
    fn save(&self, path: &str) -> Result<String, Box<dyn Error>> {
        create_path(path, true)?;

        tracing::trace!(path = path, "Attempting JSON serialization");
        let serialized = match serde_json::to_string_pretty(&self) {
            Ok(s) => s,
            Err(e) => {
                tracing::error!(path = path, error = %e, "JSON serialization failed");
                return Err(e.into());
            }
        };
        tracing::trace!(
            path = path,
            bytes = serialized.len(),
            "Serialization successful"
        );

        let mut file = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(Path::new(path))?;

        file.write_all(serialized.as_bytes())?;

        Ok(serialized)
    }
}

pub trait Reproduce: Load + Save {}

impl<T> Load for T where T: Sized + DeserializeOwned {}
impl<T> Save for T where T: Serialize {}
impl<T> Reproduce for T where T: Load + Save {}
