//! MCP story tools — create, update, move, and manage stories, bugs, refactors,
//! spikes, and epics via MCP.
//!
//! This module is a thin adapter: it deserialises MCP payloads, delegates to
//! `crate::service::story` and `crate::http::workflow` for business logic,
//! and serialises responses.

mod bug;
mod criteria;
mod epic;
mod refactor;
mod spike;
mod story;

/// Build a compact origin JSON string for a newly-created work item (story 1088).
///
/// `args` must contain an `"origin"` object with a non-empty `id` field and an
/// optional `kind` (defaulting to `"user"`) and `ts` (defaulting to now).  The
/// `id` MUST identify the calling actor — e.g. `coder-1@story=42` for a coder
/// agent, `chat-bot:Timmy@<room_id>` for a chat-bot session, or a human's user
/// id for a CLI/MCP-direct call.  Empty / whitespace-only `id` is rejected so
/// that every work item carries a usable provenance trail (bug 1102 — we lost
/// 1102's attribution because the default was `id=""`).
///
/// Returns `(origin_json, origin_label)` on success: the canonical origin
/// JSON string (for `crdt_state::set_origin`) alongside a human-readable
/// `"{kind} {id}"` label (for creation-notification display, story 1201).
/// Returns `Err` with a human-readable explanation when the caller failed to
/// identify itself; the caller (`tool_create_*` handlers) must propagate the
/// error without creating the work item, so a missing-attribution call
/// leaves no half-state behind.
pub(super) fn build_origin(args: &serde_json::Value) -> Result<(String, String), String> {
    let ts = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs_f64();

    let origin_obj = args.get("origin").and_then(|v| v.as_object()).ok_or(
        "Missing required argument: 'origin'. Every create_* MCP call must \
         identify the calling actor. Pass origin = { \"kind\": \"agent\" | \
         \"chat-bot\" | \"user\" | \"system\", \"id\": \"<your-identifier>\", \
         \"ts\": <unix-seconds, optional> }. Example: { \"kind\": \"agent\", \
         \"id\": \"coder-1@story=42\" }.",
    )?;

    let id = origin_obj
        .get("id")
        .and_then(|v| v.as_str())
        .map(str::trim)
        .filter(|s| !s.is_empty())
        .ok_or(
            "origin.id must be a non-empty string identifying the calling \
             actor (e.g. \"coder-1@story=42\", \"chat-bot:Timmy@!room:home\", \
             or a human user id). See bug 1102 / story 1104 for the rationale.",
        )?;

    let kind = origin_obj
        .get("kind")
        .and_then(|v| v.as_str())
        .unwrap_or("user");
    let ts_val = origin_obj.get("ts").and_then(|v| v.as_f64()).unwrap_or(ts);

    let origin_json = serde_json::json!({"kind": kind, "id": id, "ts": ts_val}).to_string();
    Ok((origin_json, format!("{kind} {id}")))
}

pub(crate) use bug::{tool_close_bug, tool_create_bug, tool_list_bugs};
pub(crate) use criteria::{
    tool_add_criterion, tool_check_criterion, tool_edit_criterion, tool_ensure_acceptance,
    tool_get_story_todos, tool_record_tests, tool_remove_criterion,
};
pub(crate) use epic::{tool_create_epic, tool_list_epics, tool_show_epic};
pub(crate) use refactor::{tool_create_refactor, tool_list_refactors};
pub(crate) use spike::tool_create_spike;
pub(crate) use story::{
    tool_accept_story, tool_convert_item_type, tool_create_story, tool_delete_story,
    tool_freeze_story, tool_get_pipeline_status, tool_list_upcoming, tool_pipeline_query,
    tool_purge_story, tool_unblock_story, tool_unfreeze_story, tool_update_story,
    tool_validate_stories,
};
