// Remember that Server functions parameters must not be references.
// They are coming from potentially other computers.
//
// Server functions must be:
//  * Be an async function
//  * Have arguments and a return type that both implement serialize and
//    deserialize (with serde).
//  * Return a Result with an error type of ServerFnError

#[cfg(feature = "server")]
use std::sync::Arc;

use dioxus::prelude::*;
#[cfg(feature = "server")]
use tavern::Database;
use tavern::{Adventurer, Lore, Tale};
#[cfg(feature = "server")]
use tokio::sync::OnceCell;



#[cfg(feature = "server")]
static BLOG_DATABASE: OnceCell<Arc<Database>> = OnceCell::const_new();



#[cfg(feature = "server")]
async fn get_database_instance<P>(
   path: P)
   -> Result<&'static Arc<Database>, ServerFnError>
   where P: AsRef<std::path::Path>
{
   BLOG_DATABASE.get_or_try_init(|| async
   {
      let db = Database::open(path).await
               .map_err(|e| ServerFnError::new(format!("Failed to open database: {}", e)))?;

      Ok(Arc::new(db))
   }).await
}

#[cfg(feature = "server")]
pub async fn init_database<P>(path: P) -> Result<(), ServerFnError>
   where P: AsRef<std::path::Path>
{
   match get_database_instance(path).await
   {
      Ok(_) => Ok(()),
      Err(e) => Err(e)
   }
}

#[cfg(feature = "server")]
async fn get_database() -> Result<&'static Arc<Database>, ServerFnError>
{
   get_database_instance("tavern.db").await
}



#[server]
pub async fn get_tags() -> Result<Vec<String>, ServerFnError>
{
   let db = get_database().await?;

   let tags = db.get_all_tags().await.map_err(|e| ServerFnError::new(e))?;

   Ok(tags)
}

#[server]
pub async fn get_blog_list(categories: Vec<String>)
                           -> Result<Vec<Lore>, ServerFnError>
{
   let db = get_database().await?;

   let summaries = db.get_tales_summary(&categories)
                     .await
                     .map_err(|e| ServerFnError::new(e))?;

   Ok(summaries)
}


#[server]
pub async fn get_blog_post(slug: String) -> Result<Tale, ServerFnError>
{
   let db = get_database().await?;

   let tale = db.get_tale_by_slug(&slug)
                .await
                .map_err(|e| ServerFnError::new(e))?;

   tale.ok_or(ServerFnError::new(format!("Post {} not found", slug)))
}


#[server]
pub async fn get_author(handle: String) -> Result<Option<Adventurer>, ServerFnError>
{
   let db = get_database().await?;
   let author = db.get_adventurer(&handle)
                  .await
                  .map_err(|e| ServerFnError::new(e))?;
   Ok(author)
}
