//! `space.polymodel.library.*` read endpoint handlers.
//!
//! All backed by the SQLite projection via [`super::views`]. `getThing`/`getModel`
//! hydrate ordered children via the junction tables; feeds/search return a
//! `feedView` of `thingViewBasic`.

use axum::extract::State;
use jacquard::identity::PublicResolver;
use jacquard_axum::oauth::OptionalBrowserOAuthSession;
use jacquard_axum::{ExtractXrpc, XrpcResponse};
use polymodel_api::space_polymodel::library::{
    get_author_things::{GetAuthorThingsOutput, GetAuthorThingsRequest},
    get_feed::{GetFeedOutput, GetFeedRequest},
    get_model::{GetModelOutput, GetModelRequest},
    get_thing::{GetThingOutput, GetThingRequest},
    search_things::{SearchThingsOutput, SearchThingsRequest},
};

use super::error::{AppResult, clamp_limit, invalid_request};
use super::state::AppState;
use super::views;
use super::views::viewer_did;
use crate::oauth::SqliteAuthStore;

pub(super) async fn get_thing(
    State(state): State<AppState>,
    OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession<
        PublicResolver,
        SqliteAuthStore,
    >,
    ExtractXrpc(req): ExtractXrpc<GetThingRequest>,
) -> AppResult<XrpcResponse<GetThingRequest>> {
    let viewer_owned = viewer_did(session.as_ref()).await;
    let viewer = viewer_owned.as_ref().map(|d| d.borrow());
    let (thing, models) = views::get_thing(&state, req.uri.as_ref(), viewer).await?;
    Ok(XrpcResponse(GetThingOutput {
        thing,
        models: Some(models),
        extra_data: None,
    }))
}

pub(super) async fn get_model(
    State(state): State<AppState>,
    OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession<
        PublicResolver,
        SqliteAuthStore,
    >,
    ExtractXrpc(req): ExtractXrpc<GetModelRequest>,
) -> AppResult<XrpcResponse<GetModelRequest>> {
    let viewer_owned = viewer_did(session.as_ref()).await;
    let viewer = viewer_owned.as_ref().map(|d| d.borrow());
    let detail = views::get_model(&state, req.uri.as_ref(), viewer).await?;
    Ok(XrpcResponse(GetModelOutput {
        model: detail.model,
        parts: Some(detail.parts),
        thing: detail.thing,
        extra_data: None,
    }))
}

pub(super) async fn get_author_things(
    State(state): State<AppState>,
    OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession<
        PublicResolver,
        SqliteAuthStore,
    >,
    ExtractXrpc(req): ExtractXrpc<GetAuthorThingsRequest>,
) -> AppResult<XrpcResponse<GetAuthorThingsRequest>> {
    let viewer_owned = viewer_did(session.as_ref()).await;
    let viewer = viewer_owned.as_ref().map(|d| d.borrow());
    let did = super::actor::resolve_actor(&state, &req.actor).await?;
    let feed = views::author_things(
        &state,
        did.as_ref(),
        clamp_limit(req.limit),
        req.cursor.as_deref(),
        viewer,
    )
    .await?;
    Ok(XrpcResponse(GetAuthorThingsOutput {
        value: feed,
        extra_data: None,
    }))
}

pub(super) async fn get_feed(
    State(state): State<AppState>,
    OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession<
        PublicResolver,
        SqliteAuthStore,
    >,
    ExtractXrpc(req): ExtractXrpc<GetFeedRequest>,
) -> AppResult<XrpcResponse<GetFeedRequest>> {
    let viewer_owned = viewer_did(session.as_ref()).await;
    let viewer = viewer_owned.as_ref().map(|d| d.borrow());
    let algorithm = req.algorithm.as_deref().unwrap_or("recent");
    let limit = clamp_limit(req.limit);
    let cursor = req.cursor.as_deref();
    let feed = match algorithm {
        "recent" => views::feed_recent(&state, limit, cursor, viewer).await?,
        "hot" => views::feed_hot(&state, limit, cursor, viewer).await?,
        "following" => match viewer {
            Some(viewer) => views::feed_following(&state, viewer, limit, cursor).await?,
            None => views::empty_feed(),
        },
        other => return Err(invalid_request(format!("unknown algorithm: {other}"))),
    };
    Ok(XrpcResponse(GetFeedOutput {
        value: feed,
        extra_data: None,
    }))
}

pub(super) async fn search_things(
    State(state): State<AppState>,
    OptionalBrowserOAuthSession(session): OptionalBrowserOAuthSession<
        PublicResolver,
        SqliteAuthStore,
    >,
    ExtractXrpc(req): ExtractXrpc<SearchThingsRequest>,
) -> AppResult<XrpcResponse<SearchThingsRequest>> {
    let viewer_owned = viewer_did(session.as_ref()).await;
    let viewer = viewer_owned.as_ref().map(|d| d.borrow());
    let feed = views::search_things(
        &state,
        req.q.as_str(),
        clamp_limit(req.limit),
        req.cursor.as_deref(),
        viewer,
    )
    .await?;
    Ok(XrpcResponse(SearchThingsOutput {
        value: feed,
        extra_data: None,
    }))
}
