use std::io::Write;
use std::path::PathBuf;
use clap::Args;
use pointbreak::model::{ActorId, EventId};
use pointbreak::session::{
EventSignatureRecordOptions, EventSignatureRecordResult, record_event_signature,
resolve_writer_actor_id,
};
use serde::Serialize;
use crate::cli::common::resolve_signer;
use crate::cli::json::DiagnosticDocument;
use crate::cli::output;
#[derive(Debug, Args)]
pub(super) struct EndorseArgs {
target: String,
#[arg(long)]
sign_key: Option<String>,
#[arg(long)]
actor: Option<String>,
#[arg(long, default_value = ".")]
repo: PathBuf,
#[command(flatten)]
format_args: output::FormatArgs,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct EndorseBody {
event_id: String,
target_event_id: String,
target_event_record_hash: String,
attesting_signer: String,
actor_id: String,
events_created: usize,
events_existing: usize,
}
pub(super) fn run(
args: EndorseArgs,
stdout: &mut dyn Write,
stderr: &mut dyn Write,
) -> Result<(), Box<dyn std::error::Error>> {
let explicit = args.actor.as_deref().map(ActorId::new);
let actor = resolve_writer_actor_id(&args.repo, explicit.as_ref());
let resolution = resolve_signer(&args.repo, &actor, args.sign_key.as_deref());
if let Some(diagnostic) = resolution.diagnostic.as_deref() {
let _ = writeln!(stderr, "{diagnostic}");
}
let signer = resolution.signer.ok_or_else(|| -> Box<dyn std::error::Error> {
"no signing key resolved: an endorsement has no unsigned form (the signature is its content). \
Set --sign-key / POINTBREAK_SIGNING_KEY, or run `pointbreak key init` and `pointbreak key enroll`."
.into()
})?;
let ids = crate::cli::id_resolver::IdResolver::new(&args.repo);
let target = ids.event(&args.target)?;
let result: EventSignatureRecordResult = record_event_signature(
EventSignatureRecordOptions::new(&args.repo, EventId::new(&target), signer)
.with_actor_id(actor.clone()),
)?;
let body = EndorseBody {
event_id: result.event_id.as_str().to_owned(),
target_event_id: result.target_event_id.as_str().to_owned(),
target_event_record_hash: result.target_event_record_hash,
attesting_signer: result.attesting_signer.as_str().to_owned(),
actor_id: actor.as_str().to_owned(),
events_created: result.events_created,
events_existing: result.events_existing,
};
let format = output::resolve_format(args.format_args.explicit(), output::OutputFormat::Json)?;
let text = matches!(format.format, output::OutputFormat::Text).then(|| {
format!(
"endorsed {} · signer {} · {} created ({} existing)",
output::short_ref(&body.target_event_id),
body.attesting_signer,
crate::cli::common::count_label(body.events_created, "event", "events"),
body.events_existing,
)
});
let document = DiagnosticDocument::new("pointbreak.review-endorse", body, Vec::new());
output::write_document(stdout, format, &document, || {
text.expect("text lane resolves the digest source")
})
}