//! Project service — public API for the project domain.
//!
//! Exposes functions to open, close, query, and manage known projects.
//! HTTP handlers call these functions instead of touching `io::fs` or session
//! state directly.
//!
//! Conventions: `docs/architecture/service-modules.md`

pub(super) mod io;
/// Pure project selection and path-matching logic.
pub mod selection;

use crate::state::SessionState;
use crate::store::StoreOps;
use std::path::PathBuf;

// ── Error type ────────────────────────────────────────────────────────────────

/// Typed errors returned by `service::project` functions.
///
/// HTTP handlers map these to specific status codes:
/// - [`Error::PathNotFound`] → 404 Not Found
/// - [`Error::NotADirectory`] → 400 Bad Request
/// - [`Error::Internal`] → 500 Internal Server Error
#[derive(Debug)]
pub enum Error {
    /// The given path does not exist on the filesystem.
    PathNotFound(String),
    /// The given path exists but is not a directory.
    NotADirectory(String),
    /// An internal error occurred (lock poisoned, store I/O failure, task panic).
    Internal(String),
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::PathNotFound(msg) => write!(f, "Project not found: {msg}"),
            Self::NotADirectory(msg) => write!(f, "Invalid project path: {msg}"),
            Self::Internal(msg) => write!(f, "Internal error: {msg}"),
        }
    }
}

// ── Public API ────────────────────────────────────────────────────────────────

/// Open a project, scaffolding it when needed, and persist the selection.
///
/// Validates that `path` exists and is a directory. On success, returns the
/// canonical path string. The path is promoted to the front of the known list.
pub async fn open_project(
    path: String,
    state: &SessionState,
    store: &dyn StoreOps,
    port: u16,
) -> Result<String, Error> {
    let p = PathBuf::from(&path);
    io::ensure_scaffold(p.clone(), port).await?;
    io::validate_path(&p).await?;
    io::set_project_root(state, Some(p))?;
    let known = io::read_known_projects(store);
    let updated = selection::promote_to_front(known, &path);
    io::persist_open_project(&path, &updated, store)?;
    Ok(path)
}

/// Close the current project and remove it from the persisted selection.
pub fn close_project(state: &SessionState, store: &dyn StoreOps) -> Result<(), Error> {
    io::set_project_root(state, None)?;
    io::clear_project(store)
}

/// Return the currently open project path, if any.
///
/// Checks in-memory state first, then falls back to the store.
/// If the store has a valid path, restores it into state for future calls.
pub fn get_current_project(
    state: &SessionState,
    store: &dyn StoreOps,
) -> Result<Option<String>, Error> {
    if let Some(path) = io::get_project_root_from_state(state)? {
        return Ok(Some(path.to_string_lossy().to_string()));
    }
    io::restore_from_store(state, store)
}

/// Return all known (previously opened) project paths from the store.
pub fn get_known_projects(store: &dyn StoreOps) -> Result<Vec<String>, Error> {
    Ok(io::read_known_projects(store))
}

/// Remove a path from the known-projects list.
///
/// Returns `Ok(())` whether or not the path was present (idempotent).
pub fn forget_known_project(path: String, store: &dyn StoreOps) -> Result<(), Error> {
    let known = io::read_known_projects(store);
    let before = known.len();
    let updated: Vec<String> = known.into_iter().filter(|p| p != &path).collect();
    if updated.len() == before {
        return Ok(());
    }
    io::save_known_projects(&updated, store)
}
