//! Managed application state for the Tauri backend.

use brittle_core::{store::FsStore, Brittle, BrittleError};
use std::sync::Mutex;

/// Shared state held by the Tauri runtime for the lifetime of the application.
///
/// `brittle` is `None` until the user opens or creates a repository.
pub struct AppState {
    pub brittle: Mutex<Option<Brittle<FsStore>>>,
}

impl Default for AppState {
    fn default() -> Self {
        Self::new()
    }
}

impl AppState {
    pub fn new() -> Self {
        Self {
            brittle: Mutex::new(None),
        }
    }

    /// Run a closure with mutable access to the open repository.
    ///
    /// Errors if:
    /// - the mutex is poisoned,
    /// - no repository is currently open, or
    /// - the operation itself returns an error.
    pub fn with_repo<T, F>(&self, f: F) -> Result<T, String>
    where
        F: FnOnce(&mut Brittle<FsStore>) -> Result<T, BrittleError>,
    {
        let mut guard = self
            .brittle
            .lock()
            .map_err(|_| "internal: state lock poisoned".to_string())?;
        let brittle = guard
            .as_mut()
            .ok_or_else(|| "no repository open".to_string())?;
        f(brittle).map_err(|e| e.to_string())
    }

    /// Run a closure with read-only access to the open repository.
    pub fn with_repo_read<T, F>(&self, f: F) -> Result<T, String>
    where
        F: FnOnce(&Brittle<FsStore>) -> Result<T, BrittleError>,
    {
        let guard = self
            .brittle
            .lock()
            .map_err(|_| "internal: state lock poisoned".to_string())?;
        let brittle = guard
            .as_ref()
            .ok_or_else(|| "no repository open".to_string())?;
        f(brittle).map_err(|e| e.to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use brittle_core::EntryType;

    fn open_state() -> (AppState, tempfile::TempDir) {
        let tmp = tempfile::tempdir().unwrap();
        let state = AppState::new();
        let brittle = Brittle::create(tmp.path()).unwrap();
        *state.brittle.lock().unwrap() = Some(brittle);
        (state, tmp)
    }

    #[test]
    fn with_repo_fails_when_no_repo_open() {
        let state = AppState::new();
        let result = state.with_repo(|_| Ok(()));
        assert_eq!(result.unwrap_err(), "no repository open");
    }

    #[test]
    fn with_repo_read_fails_when_no_repo_open() {
        let state = AppState::new();
        let result = state.with_repo_read(|_| Ok(()));
        assert_eq!(result.unwrap_err(), "no repository open");
    }

    #[test]
    fn with_repo_succeeds_when_open() {
        let (state, _tmp) = open_state();
        let result = state.with_repo(|b| b.create_reference("test2024", EntryType::Article));
        assert!(result.is_ok());
        assert_eq!(result.unwrap().cite_key, "test2024");
    }

    #[test]
    fn with_repo_read_can_list_references() {
        let (state, _tmp) = open_state();
        let result = state.with_repo_read(|b| b.list_references());
        assert!(result.is_ok());
    }
}
