//! Shared appview handler state.
//!
//! Read endpoints are backed entirely by the SQLite projection (records
//! live in `record_json`, handles in `identities`, profiles in `profiles`). The
//! read path therefore needs no Hydrant handle. `resolver` canonicalizes handle
//! authorities to DIDs for `getProfile`; `bsky` fetches the
//! `app.bsky.actor.defs#profileViewDetailed` union variant from the public
//! AppView for actors with no polymodel profile.
//!
//! It also carries the confidential OAuth client, single-origin web config, and
//! private cookie-jar key. `AppState` is the Axum router state type (local, so
//! the [`FromRef`] impls for [`OAuthWebConfig`] and the cookie [`Key`] satisfy
//! the orphan rule); it is cheaply `Clone` (pool/resolver/oauth are
//! `Arc`-backed).

use std::sync::Arc;

use axum::extract::FromRef;
use axum_extra::extract::cookie::Key;
use jacquard::client::BasicClient;
use jacquard::identity::PublicResolver;
use jacquard::oauth::client::OAuthClient;
use jacquard_axum::oauth::{OAuthWebConfig, OAuthWebState};
use sqlx::SqlitePool;
use tokio::sync::Mutex;

use crate::oauth::{OAuthBootstrap, SqliteAuthStore};

/// State shared by all XRPC read handlers and the OAuth routes.
#[derive(Clone)]
pub struct AppState {
    /// SQLite projection: content, social counts, profiles, identities.
    pub pool: SqlitePool,
    /// Handle→DID resolution for `getProfile` actor canonicalization.
    pub resolver: PublicResolver,
    /// Unauthenticated client aimed at the public Bluesky AppView
    /// (`https://public.api.bsky.app`) for the bluesky-only profile variant.
    /// `Arc`-wrapped because `BasicClient` (`Agent`) is not `Clone`.
    pub bsky: Arc<BasicClient>,
    /// Confidential OAuth client over the SQLite auth store.
    pub oauth: Arc<OAuthClient<PublicResolver, SqliteAuthStore>>,
    /// Single-origin OAuth web config (cookie name + route paths).
    pub oauth_config: OAuthWebConfig,
    /// Signing key for the private `jacquard_oauth_session` cookie jar.
    pub cookie_key: Key,
    /// Serializes server-mediated writes that make decisions from local
    /// projection state before issuing PDS mutations. This avoids app-process
    /// TOCTOU races such as deleting a child record while another PM-28 write is
    /// attaching it elsewhere.
    pub write_lock: Arc<Mutex<()>>,
}

impl AppState {
    /// Construct read + OAuth state from the shared SQLite pool and the
    /// startup-built OAuth bootstrap (keyset, config, cookie key).
    pub fn new(pool: SqlitePool, bootstrap: OAuthBootstrap) -> Self {
        Self {
            pool,
            resolver: jacquard::identity::slingshot_resolver_default(),
            bsky: Arc::new(jacquard::client::BasicClient::unauthenticated()),
            oauth: Arc::new(bootstrap.client),
            oauth_config: bootstrap.config,
            cookie_key: bootstrap.cookie_key,
            write_lock: Arc::new(Mutex::new(())),
        }
    }
}

/// Expose the OAuth client to `jacquard_axum::oauth` routes/extractors.
impl OAuthWebState<PublicResolver, SqliteAuthStore> for AppState {
    fn oauth_client(&self) -> &OAuthClient<PublicResolver, SqliteAuthStore> {
        self.oauth.as_ref()
    }
}

impl FromRef<AppState> for OAuthWebConfig {
    fn from_ref(state: &AppState) -> Self {
        state.oauth_config.clone()
    }
}

impl FromRef<AppState> for Key {
    fn from_ref(state: &AppState) -> Self {
        state.cookie_key.clone()
    }
}
