//! Same-origin client calls for the PM-43 publish wizard.
//!
//! Thin typed wrappers over [`PolymodelClient`]'s raw app-request helpers for
//! the draft store, raw-byte file staging, and image upload. Every call rides
//! the browser cookie session (same-origin), so these are authenticated without
//! any explicit token handling.

use http::Method;
use polymodel_api::space_polymodel::library::Image;
use polymodel_api::space_polymodel::library::publish_thing::PublishThingOutput;
use polymodel_api::space_polymodel::library::stage_file::StageFileOutput;

use crate::client::{PolymodelClient, RawError};
use crate::publish::draft::{
    DeleteDraftResponse, DraftIdResponse, DraftSummary, DraftThingInput, ImageUploadResponse,
};

/// stageFile XRPC path (raw-body upload), reused from the existing write path.
const STAGE_FILE_PATH: &str = "/xrpc/space.polymodel.library.stageFile";
const DRAFTS_PATH: &str = "/app/drafts";
const IMAGES_PATH: &str = "/app/images";

/// Create a new server-side draft; returns the server-assigned opaque id.
pub async fn create_draft(
    client: &PolymodelClient,
    draft: &DraftThingInput,
) -> Result<String, RawError> {
    let response: DraftIdResponse = client
        .app_json(Method::POST, DRAFTS_PATH, Some(draft))
        .await?;
    Ok(response.draft_id)
}

/// Upsert an existing draft by id (autosave).
pub async fn put_draft(
    client: &PolymodelClient,
    draft_id: &str,
    draft: &DraftThingInput,
) -> Result<(), RawError> {
    let _: DraftIdResponse = client
        .app_json(Method::PUT, &draft_path(draft_id), Some(draft))
        .await?;
    Ok(())
}

/// Fetch a single draft payload (resume).
pub async fn get_draft(
    client: &PolymodelClient,
    draft_id: &str,
) -> Result<DraftThingInput, RawError> {
    client
        .app_json::<(), _>(Method::GET, &draft_path(draft_id), None)
        .await
}

/// List the signed-in actor's draft summaries.
pub async fn list_drafts(client: &PolymodelClient) -> Result<Vec<DraftSummary>, RawError> {
    client
        .app_json::<(), _>(Method::GET, DRAFTS_PATH, None)
        .await
}

/// Delete a draft; returns whether a row existed.
pub async fn delete_draft(client: &PolymodelClient, draft_id: &str) -> Result<bool, RawError> {
    let response: DeleteDraftResponse = client
        .app_json::<(), _>(Method::DELETE, &draft_path(draft_id), None)
        .await?;
    Ok(response.deleted)
}

/// Assemble + publish a draft to the actor's PDS; the server deletes the draft
/// only on success.
pub async fn publish_draft(
    client: &PolymodelClient,
    draft_id: &str,
) -> Result<PublishThingOutput, RawError> {
    let path = format!("{DRAFTS_PATH}/{draft_id}/publish");
    client.app_json::<(), _>(Method::POST, &path, None).await
}

/// Stage a geometry file: raw bytes → blobs on the actor's PDS + reassembly
/// metadata. Returns the `uploadId`/`file` the part references.
pub async fn stage_file(
    client: &PolymodelClient,
    filename: &str,
    content_type: &str,
    bytes: Vec<u8>,
) -> Result<StageFileOutput, RawError> {
    client
        .app_post_bytes(
            STAGE_FILE_PATH,
            content_type,
            &[("x-polymodel-filename", filename.to_string())],
            bytes,
        )
        .await
}

/// Upload an image and receive a fully-formed `#image` (blobref + server-decoded
/// aspect ratio + the given alt text).
pub async fn upload_image(
    client: &PolymodelClient,
    alt: &str,
    content_type: &str,
    bytes: Vec<u8>,
) -> Result<Image, RawError> {
    let response: ImageUploadResponse = client
        .app_post_bytes(
            IMAGES_PATH,
            content_type,
            &[("x-polymodel-alt", alt.to_string())],
            bytes,
        )
        .await?;
    Ok(response.image)
}

fn draft_path(draft_id: &str) -> String {
    format!("{DRAFTS_PATH}/{draft_id}")
}
