//! In-memory snapshot of compiled routes, partitioned by `app_id`.
//!
//! The orchestrator looks up the app's slice by id after `AppDomainTable`
//! has resolved Host → app_id, then runs the existing matcher on that
//! slice. The matcher is unchanged; this type is just a per-app bucket.

use std::collections::HashMap;
use std::sync::RwLock;

use picloud_shared::AppId;

use super::matcher::{r#match, CompiledRoute, MatchResult};

/// Per-app compiled-route tables. Single MVP-mode writer (the manager,
/// via `replace_all`); contention against readers is minimal so a plain
/// `RwLock` is fine.
#[derive(Default)]
pub struct RouteTable {
    inner: RwLock<HashMap<AppId, Vec<CompiledRoute>>>,
}

impl RouteTable {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Replace every per-app slice atomically. The manager calls this
    /// after each successful route CRUD operation; in cluster mode the
    /// orchestrator's HTTP-fed receiver will too.
    pub fn replace_all(&self, routes: Vec<CompiledRoute>) {
        let mut by_app: HashMap<AppId, Vec<CompiledRoute>> = HashMap::new();
        for r in routes {
            by_app.entry(r.app_id).or_default().push(r);
        }
        let mut guard = self.inner.write().expect("route table poisoned");
        *guard = by_app;
    }

    /// Dispatch a request to a matching route within `app_id`, or
    /// `None`. Returns `None` when the app has no routes at all.
    #[must_use]
    pub fn match_request_for_app(
        &self,
        app_id: AppId,
        host: &str,
        method: &str,
        path: &str,
    ) -> Option<MatchResult> {
        let guard = self.inner.read().expect("route table poisoned");
        let slice = guard.get(&app_id)?;
        r#match(slice.iter(), host, method, path)
    }

    /// Returns a clone of the currently compiled routes for `app_id`;
    /// intended for admin endpoints like "list this app's routes".
    #[must_use]
    pub fn snapshot_for_app(&self, app_id: AppId) -> Vec<CompiledRoute> {
        self.inner
            .read()
            .expect("route table poisoned")
            .get(&app_id)
            .cloned()
            .unwrap_or_default()
    }

    /// All compiled routes across all apps. Used by tests and the
    /// global admin "every route on this install" view.
    #[must_use]
    pub fn snapshot_all(&self) -> Vec<CompiledRoute> {
        self.inner
            .read()
            .expect("route table poisoned")
            .values()
            .flat_map(|v| v.iter().cloned())
            .collect()
    }
}
