//! Tool schema definitions for story, bug, spike, refactor, and pipeline management.

use serde_json::{Value, json};

/// JSON schema fragment for the `origin` argument required by every `create_*`
/// tool (bug 1102).  The caller MUST identify itself — empty `id` is rejected
/// server-side so every work item carries a usable provenance trail.
fn origin_schema() -> Value {
    json!({
        "type": "object",
        "description": "Required: identifies the calling actor so every work item carries provenance. Empty/missing id is rejected (bug 1102). Examples: { \"kind\": \"agent\", \"id\": \"coder-1@story=42\" }, { \"kind\": \"chat-bot\", \"id\": \"Timmy@!room:home.local\" }, { \"kind\": \"user\", \"id\": \"dave\" }.",
        "properties": {
            "kind": {
                "type": "string",
                "description": "One of: \"agent\" (LLM coder/mergemaster/qa), \"chat-bot\" (Timmy or other chat-routed bot), \"user\" (human via CLI/MCP), \"system\" (server-automation)."
            },
            "id": {
                "type": "string",
                "description": "Non-empty identifier of the caller. For agents include the story id (e.g. \"coder-1@story=42\"); for chat-bots include the room/session (e.g. \"Timmy@!room:home.local\"); for users the user id or short name."
            },
            "ts": {
                "type": "number",
                "description": "Optional unix-seconds timestamp. Defaults to the server's clock when absent."
            }
        },
        "required": ["kind", "id"]
    })
}

/// Returns tool schemas for story/work-item lifecycle management.
pub(super) fn story_tools() -> Vec<Value> {
    vec![
        json!({
            "name": "create_story",
            "description": "Create a new story file with front matter in upcoming/. Returns the story_id.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Human-readable story name"
                    },
                    "user_story": {
                        "type": "string",
                        "description": "Optional user story text (As a..., I want..., so that...)"
                    },
                    "description": {
                        "type": "string",
                        "description": "Optional description / background context for the story"
                    },
                    "acceptance_criteria": {
                        "type": "array",
                        "items": { "type": "string" },
                        "minItems": 1,
                        "description": "List of acceptance criteria (at least one required)"
                    },
                    "depends_on": {
                        "type": "array",
                        "items": { "type": "integer" },
                        "description": "Optional list of story IDs this story depends on; written as a YAML inline sequence in front matter"
                    },
                    "commit": {
                        "type": "boolean",
                        "description": "If true, git-add and git-commit the new story file to the current branch"
                    },
                    "origin": origin_schema()
                },
                "required": ["name", "acceptance_criteria", "origin"]
            }
        }),
        json!({
            "name": "validate_stories",
            "description": "Validate front matter on all current and upcoming story files.",
            "inputSchema": {
                "type": "object",
                "properties": {}
            }
        }),
        json!({
            "name": "list_upcoming",
            "description": "List all upcoming stories with their names and any parsing errors.",
            "inputSchema": {
                "type": "object",
                "properties": {}
            }
        }),
        json!({
            "name": "get_story_todos",
            "description": "Get unchecked acceptance criteria (todos) for a story file in current/.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (filename stem, e.g. '28_my_story')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "record_tests",
            "description": "Record test results for a story. Only one failing test at a time is allowed.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier"
                    },
                    "unit": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": { "type": "string" },
                                "status": { "type": "string", "enum": ["pass", "fail"] },
                                "details": { "type": "string" }
                            },
                            "required": ["name", "status"]
                        },
                        "description": "Unit test results"
                    },
                    "integration": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": { "type": "string" },
                                "status": { "type": "string", "enum": ["pass", "fail"] },
                                "details": { "type": "string" }
                            },
                            "required": ["name", "status"]
                        },
                        "description": "Integration test results"
                    }
                },
                "required": ["story_id", "unit", "integration"]
            }
        }),
        json!({
            "name": "ensure_acceptance",
            "description": "Check whether a story can be accepted. Returns acceptance status with reasons if blocked.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "accept_story",
            "description": "Accept a story: moves it from current/ to done/ and auto-commits to master.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (filename stem, e.g. '28_my_story')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "check_criterion",
            "description": "Check off an acceptance criterion (- [ ] → - [x]) by 0-based index among unchecked items, then auto-commit to master. Use get_story_todos to see the current list of unchecked criteria.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (filename stem, e.g. '28_my_story')"
                    },
                    "criterion_index": {
                        "type": "integer",
                        "description": "0-based index of the unchecked criterion to check off"
                    }
                },
                "required": ["story_id", "criterion_index"]
            }
        }),
        json!({
            "name": "edit_criterion",
            "description": "Update the text of an existing acceptance criterion in place, preserving its checked/unchecked state. Uses a 0-based index counting all criteria (both checked and unchecked).",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (filename stem, e.g. '28_my_story')"
                    },
                    "criterion_index": {
                        "type": "integer",
                        "description": "0-based index of the criterion to edit (counts all criteria)"
                    },
                    "new_text": {
                        "type": "string",
                        "description": "New text for the criterion (without the '- [ ] ' or '- [x] ' prefix)"
                    }
                },
                "required": ["story_id", "criterion_index", "new_text"]
            }
        }),
        json!({
            "name": "add_criterion",
            "description": "Add an acceptance criterion to an existing story file. Appends '- [ ] {criterion}' after the last existing criterion in the '## Acceptance Criteria' section. Auto-commits via the filesystem watcher.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (filename stem, e.g. '28_my_story')"
                    },
                    "criterion": {
                        "type": "string",
                        "description": "The acceptance criterion text to add (without the '- [ ] ' prefix)"
                    }
                },
                "required": ["story_id", "criterion"]
            }
        }),
        json!({
            "name": "remove_criterion",
            "description": "Remove an acceptance criterion from a story by its 0-based index (counting all criteria, both checked and unchecked). Returns an error if the index is out of range. Auto-commits via the filesystem watcher.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (filename stem, e.g. '28_my_story')"
                    },
                    "criterion_index": {
                        "type": "integer",
                        "description": "0-based index of the criterion to remove (counts all criteria)"
                    }
                },
                "required": ["story_id", "criterion_index"]
            }
        }),
        json!({
            "name": "update_story",
            "description": "Update an existing story file. Can rename the story, replace the '## User Story' and/or '## Description' section content, and/or set YAML front matter fields (e.g. agent, qa). Auto-commits via the filesystem watcher.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (filename stem, e.g. '28_my_story')"
                    },
                    "name": {
                        "type": "string",
                        "description": "New human-readable name for the story (stored as a CRDT field; does not change the story_id or any references)"
                    },
                    "user_story": {
                        "type": "string",
                        "description": "New user story text to replace the '## User Story' section content"
                    },
                    "description": {
                        "type": "string",
                        "description": "New description text to replace the '## Description' section content"
                    },
                    "agent": {
                        "type": "string",
                        "description": "Set or change the 'agent' YAML front matter field"
                    },
                    "epic": {
                        "type": "string",
                        "description": "Set or change the 'epic' field — the numeric epic ID this item belongs to (e.g. '880'). Agents spawned for this item will have the epic context prepended to their system prompt."
                    },
                    "front_matter": {
                        "type": "object",
                        "description": "Arbitrary YAML front matter key-value pairs to set or update. Values may be strings, booleans, integers, numbers, or arrays (e.g. [490, 491]).",
                        "additionalProperties": {
                            "oneOf": [
                                {"type": "string"},
                                {"type": "boolean"},
                                {"type": "integer"},
                                {"type": "number"},
                                {"type": "array"}
                            ]
                        }
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "create_spike",
            "description": "Create a spike file in .huskies/work/1_backlog/ with a deterministic filename and YAML front matter. Returns the spike_id.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Human-readable spike name"
                    },
                    "description": {
                        "type": "string",
                        "description": "Optional description / question the spike aims to answer"
                    },
                    "acceptance_criteria": {
                        "type": "array",
                        "items": { "type": "string" },
                        "minItems": 1,
                        "description": "List of acceptance criteria (at least one required)"
                    },
                    "origin": origin_schema()
                },
                "required": ["name", "acceptance_criteria", "origin"]
            }
        }),
        json!({
            "name": "create_bug",
            "description": "Create a bug file in work/1_backlog/ with a deterministic filename and auto-commit to master. Returns the bug_id.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Short human-readable bug name"
                    },
                    "description": {
                        "type": "string",
                        "description": "Description of the bug"
                    },
                    "steps_to_reproduce": {
                        "type": "string",
                        "description": "Steps to reproduce the bug"
                    },
                    "actual_result": {
                        "type": "string",
                        "description": "What actually happens"
                    },
                    "expected_result": {
                        "type": "string",
                        "description": "What should happen"
                    },
                    "acceptance_criteria": {
                        "type": "array",
                        "items": { "type": "string" },
                        "minItems": 1,
                        "description": "List of acceptance criteria for the fix (at least one required)"
                    },
                    "depends_on": {
                        "type": "array",
                        "items": { "type": "integer" },
                        "description": "Optional list of story numbers this bug depends on (e.g. [42, 43]). Persisted as depends_on in YAML front matter."
                    },
                    "origin": origin_schema()
                },
                "required": ["name", "description", "steps_to_reproduce", "actual_result", "expected_result", "acceptance_criteria", "origin"]
            }
        }),
        json!({
            "name": "list_bugs",
            "description": "List all open bugs in work/1_backlog/ matching the _bug_ naming convention.",
            "inputSchema": {
                "type": "object",
                "properties": {}
            }
        }),
        json!({
            "name": "create_refactor",
            "description": "Create a refactor work item in work/1_backlog/ with a deterministic filename and YAML front matter. Returns the refactor_id.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Short human-readable refactor name"
                    },
                    "description": {
                        "type": "string",
                        "description": "Optional description of the desired state after refactoring"
                    },
                    "acceptance_criteria": {
                        "type": "array",
                        "items": { "type": "string" },
                        "minItems": 1,
                        "description": "List of acceptance criteria (at least one required)"
                    },
                    "depends_on": {
                        "type": "array",
                        "items": { "type": "integer" },
                        "description": "Optional list of story numbers this refactor depends on (e.g. [42, 43]). Persisted as depends_on in YAML front matter."
                    },
                    "origin": origin_schema()
                },
                "required": ["name", "acceptance_criteria", "origin"]
            }
        }),
        json!({
            "name": "list_refactors",
            "description": "List all open refactors in work/1_backlog/ matching the _refactor_ naming convention.",
            "inputSchema": {
                "type": "object",
                "properties": {}
            }
        }),
        json!({
            "name": "create_epic",
            "description": "Create an epic: a shared-context container for chains of related stories. Epics are not pipeline-driven but inject their goal and context into agent prompts for all member work items. Returns the epic_id.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Human-readable epic name"
                    },
                    "goal": {
                        "type": "string",
                        "description": "What this epic aims to achieve"
                    },
                    "motivation": {
                        "type": "string",
                        "description": "Optional: why this epic matters"
                    },
                    "key_files": {
                        "type": "string",
                        "description": "Optional: key files or modules relevant to this epic"
                    },
                    "success_criteria": {
                        "type": "array",
                        "items": { "type": "string" },
                        "description": "Optional: list of high-level success criteria for the epic"
                    },
                    "origin": origin_schema()
                },
                "required": ["name", "goal", "origin"]
            }
        }),
        json!({
            "name": "list_epics",
            "description": "List all epics with their member work item counts and n/m done rollup.",
            "inputSchema": {
                "type": "object",
                "properties": {}
            }
        }),
        json!({
            "name": "show_epic",
            "description": "Show details for a single epic: its content and all member work items with their pipeline stages.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "epic_id": {
                        "type": "string",
                        "description": "Epic identifier (e.g. '880')"
                    }
                },
                "required": ["epic_id"]
            }
        }),
        json!({
            "name": "close_bug",
            "description": "Archive a bug from work/2_current/ or work/1_backlog/ to work/5_done/ and auto-commit to master.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "bug_id": {
                        "type": "string",
                        "description": "Bug identifier (e.g. 'bug-3-login_crash')"
                    }
                },
                "required": ["bug_id"]
            }
        }),
        json!({
            "name": "merge_agent_work",
            "description": "Run the mergemaster pipeline for a completed story. Blocks until the merge completes or fails, then returns the full result — no polling needed. The pipeline squash-merges the feature branch into master, runs quality gates, moves the story to done, and cleans up.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (e.g. '52_story_mergemaster_agent_role')"
                    },
                    "agent_name": {
                        "type": "string",
                        "description": "Optional: name of the coder agent whose work is being merged (for logging)"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "get_merge_status",
            "description": "Check the cached result of a merge_agent_work job. Returns the full merge report immediately — no polling needed. Useful if merge_agent_work already returned but you need the result again.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (same as passed to merge_agent_work)"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "move_story_to_merge",
            "description": "Move a story or bug from work/2_current/ to work/4_merge/ to queue it for the mergemaster pipeline and automatically spawn the mergemaster agent to squash-merge, run quality gates, and archive.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (filename stem, e.g. '28_my_story')"
                    },
                    "agent_name": {
                        "type": "string",
                        "description": "Agent name to use for merging (defaults to 'mergemaster')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "report_merge_failure",
            "description": "Report that a merge failed for a story. Leaves the story in work/4_merge/ and logs the failure reason. Use this when merge_agent_work returns success=false instead of manually moving the story file.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (e.g. '52_story_mergemaster_agent_role')"
                    },
                    "reason": {
                        "type": "string",
                        "description": "Human-readable explanation of why the merge failed"
                    }
                },
                "required": ["story_id", "reason"]
            }
        }),
        json!({
            "name": "request_qa",
            "description": "Trigger QA review of a completed story worktree: moves the item from work/2_current/ to work/3_qa/ and starts the qa agent to run quality gates, tests, and generate a manual testing plan.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (e.g. '53_story_qa_agent_role')"
                    },
                    "agent_name": {
                        "type": "string",
                        "description": "Agent name to use for QA (defaults to 'qa')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "approve_qa",
            "description": "Approve a story that passed machine QA and is awaiting human review. Moves the story from work/3_qa/ to work/4_merge/ and starts the mergemaster agent.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (e.g. '247_story_human_qa_gate')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "reject_qa",
            "description": "Reject a story during human QA review. Moves the story from work/3_qa/ back to work/2_current/ with rejection notes so the coder agent can fix the issues.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier (e.g. '247_story_human_qa_gate')"
                    },
                    "notes": {
                        "type": "string",
                        "description": "Explanation of what is broken or needs fixing"
                    }
                },
                "required": ["story_id", "notes"]
            }
        }),
        json!({
            "name": "launch_qa_app",
            "description": "Launch the app from a story's worktree for manual QA testing. Automatically assigns a free port, writes it to .huskies_port, and starts the backend server. Only one QA app instance runs at a time.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier whose worktree app to launch"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "get_pipeline_status",
            "description": "Return a structured snapshot of the full work item pipeline. Each item includes only slim fields: story_id, name (capped at 120 chars), stage, agent (with agent_name/model/status), and optional boolean flags blocked and retry_count. Active stages (current, qa, merge, done) appear in the 'active' array; backlog items in 'backlog'. For full story details, use show(story_id).",
            "inputSchema": {
                "type": "object",
                "properties": {}
            }
        }),
        json!({
            "name": "pipeline_query",
            "description": "Slim, filterable query over the CRDT-backed pipeline. Unlike get_pipeline_status (a full snapshot), this returns only the requested fields for items matching an optional stage filter, keeping responses far smaller. Items are always projected from live CRDT state, never markdown-migration rows. Archived items are excluded unless include_archived=true. An unknown stage value returns an error listing valid stages; a filter with no matches returns an empty array.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "stage": {
                        "type": "string",
                        "description": "Filter to items in this stage. One of: backlog, coding, qa, merge, done, closed, archived. Omit to return items across all stages (archived still excluded unless include_archived is true)."
                    },
                    "include_archived": {
                        "type": "boolean",
                        "description": "Include archived items in results. Default false: archived items are excluded unless explicitly opted in."
                    },
                    "fields": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "Which fields to include per item. One or more of: story_id, name, stage, status, depends_on. Defaults to [\"story_id\", \"name\", \"stage\"] when omitted."
                    }
                }
            }
        }),
        json!({
            "name": "delete_story",
            "description": "Delete a work item from the pipeline entirely. Stops any running agent, removes the worktree, and deletes the story file. Use only for removing obsolete or duplicate items.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Work item identifier (filename stem, e.g. '28_story_my_feature')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "purge_story",
            "description": "Write a CRDT tombstone op for a story (story 521). Marks the in-memory CRDT item as deleted, persists the tombstone to crdt_ops so it survives restart, and drops the in-memory content store entry. Does NOT touch running agents, worktrees, the pipeline_items shadow table, timers.json, or filesystem shadows — compose with stop_agent / remove_worktree / etc. for a full cleanup. Use this when a story has gone zombie in the running server's in-memory state and direct sqlite deletes alone are not enough to clear it.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Work item identifier (filename stem, e.g. '28_story_my_feature')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "move_story",
            "description": "Move a work item (story, bug, spike, or refactor) to an arbitrary pipeline stage. Prefer dedicated tools when available: use accept_story to mark items done, move_story_to_merge to queue for merging, or request_qa to trigger QA review. Use move_story only for arbitrary moves that lack a dedicated tool — for example, moving a story back to backlog or recovering a ghost story by moving it back to current.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Work item identifier (filename stem, e.g. '28_story_my_feature')"
                    },
                    "target_stage": {
                        "type": "string",
                        "enum": ["backlog", "current", "qa", "merge", "done"],
                        "description": "Target pipeline stage: backlog (1_backlog), current (2_current), qa (3_qa), merge (4_merge), done (5_done)"
                    }
                },
                "required": ["story_id", "target_stage"]
            }
        }),
        json!({
            "name": "unblock_story",
            "description": "Clear the blocked flag and reset retry_count to 0 on a work item. Use this when an agent is stuck and needs to be restarted from a clean state.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Work item identifier (filename stem, e.g. '42_story_my_feature')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "convert_item_type",
            "description": "Convert a work item's type in place (e.g. spike → story). The story_id, ACs, epic association, and all other registers are preserved; only the item_type register changes. Rejected for archived items.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Work item identifier (filename stem, e.g. '42_spike_my_spike')"
                    },
                    "new_type": {
                        "type": "string",
                        "enum": ["story", "bug", "spike", "refactor", "epic"],
                        "description": "Target item type"
                    }
                },
                "required": ["story_id", "new_type"]
            }
        }),
        json!({
            "name": "freeze_story",
            "description": "Freeze a work item at its current pipeline stage, suppressing pipeline advancement and auto-assign until unfrozen.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Work item identifier (filename stem, e.g. '42_story_my_feature')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "unfreeze_story",
            "description": "Unfreeze a work item, resuming normal pipeline behaviour.",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Work item identifier (filename stem, e.g. '42_story_my_feature')"
                    }
                },
                "required": ["story_id"]
            }
        }),
        json!({
            "name": "show",
            "description": "Show full details for a work item in any pipeline stage: front matter, AC checklist, and (for coding-stage items) active worktree/branch, git diff --stat since master, last 5 commits, and last 20 lines of the most recent agent log. Accepts a full story_id ('42_story_my_feature') or just the numeric prefix ('42').",
            "inputSchema": {
                "type": "object",
                "properties": {
                    "story_id": {
                        "type": "string",
                        "description": "Story identifier — full stem (e.g. '42_story_my_feature') or just the numeric prefix (e.g. '42')"
                    }
                },
                "required": ["story_id"]
            }
        }),
    ]
}
