"""Intake questionnaire questions, phase labels, and question metadata.

# See README: "Project Intake Questionnaire" — 30 questions across 7 phases
#
# All 30 questions are defined here as a static dict rather than generated by
# the LLM. This is a deliberate design choice:
# - Deterministic: every user gets the same questions in the same order.
# - Trackable: the QuestionnaireState can use question numbers as keys.
# - Extensible: follow-up probing (a separate TODO) will add LLM-powered
#   refinement on top of these fixed questions later.
#
# Note: PHASE_LABELS and PHASE_INTROS use string keys (not QuestionnairePhase
# enum) to avoid a circular import: intake → agent.state → agent/__init__ →
# agent.graph → agent.nodes → intake. Since QuestionnairePhase is a StrEnum,
# dict lookups still work with enum values (e.g. PHASE_LABELS[qs.current_phase]).

# QUESTION_METADATA marks 8 of 30 questions as single-choice with predefined
# options. The REPL renders numbered menus for these, and the intake node skips
# vagueness checks (choices are never vague). This is a REPL-side concern —
# no state schema changes required.
"""

from dataclasses import dataclass
from enum import StrEnum

# ---------------------------------------------------------------------------
# All 30 intake questions, keyed by question number (1-based).
# See README: "Project Intake Questionnaire" for rationale behind each phase.
# ---------------------------------------------------------------------------

# ---------------------------------------------------------------------------
# Answer provenance — tracks how each question was answered.
# See README: "Project Intake Questionnaire" — answer confidence signalling
#
# Used by the intake summary to show a confidence breakdown and by
# compute_prompt_quality() to identify low-confidence areas for downstream
# spike recommendations.
# ---------------------------------------------------------------------------


class AnswerSource(StrEnum):
    """How a questionnaire answer was obtained."""

    DIRECT = "direct"
    EXTRACTED = "extracted"
    DEFAULTED = "defaulted"
    PROBED = "probed"
    SCRUM_MD = "scrum_md"


INTAKE_QUESTIONS: dict[int, str] = {
    # Phase 1 — Project Context (Q1–Q5)
    1: "What is the project? Describe it in a few sentences, or point me to a repo/doc.",
    2: "Is this a greenfield project or are you building on an existing codebase?",
    3: "What problem does this project solve? Who are the end users?",
    4: "What does \"done\" look like? What's the end-state you're targeting?",
    5: "Are there any hard deadlines or milestones?",
    # Phase 2 — Team & Capacity (Q6–Q10)
    6: "How many engineers are working on this?",
    7: "What are the roles on the team? (e.g., 2 backend, 1 frontend, 1 fullstack)",
    8: "How long are your sprints? (e.g., 1 week, 2 weeks)",
    9: "Do you have a known velocity from previous sprints? If yes, what is it?",
    10: "How many sprints are you targeting to complete this project?",  # choice question — see QUESTION_METADATA
    # Phase 3 — Technical Context (Q11–Q14)
    11: "What is the tech stack? (languages, frameworks, databases, infra)",
    12: "Are there any existing APIs, services, or third-party integrations involved?",
    13: (
        "Are there any architectural constraints or decisions already made?"
        " (e.g., must use microservices, must deploy to AWS)"
    ),
    14: "Is there any existing documentation, PRDs, or design docs I should reference?",
    # Phase 3a — Codebase Context (Q15–Q20)
    15: "Does the project have an existing codebase, or is this a new build?",
    16: "Where is the code hosted? (GitHub, Azure DevOps, GitLab, Bitbucket, local only)",
    17: "Can you share the repo URL(s)? (the agent can connect and scan the repo for context)",
    18: "How is the repo structured? (monorepo, multi-repo, microservices, monolith)",
    19: "Is there an existing CI/CD pipeline or deployment setup?",
    20: "Is there any known technical debt? (legacy code, outdated dependencies, areas needing refactoring)",
    # Phase 4 — Risks & Unknowns (Q21–Q23)
    21: (
        "Are there any areas of the project you're uncertain or worried about?"
        " (technical risk, unclear requirements, dependencies on other teams)"
    ),
    22: "Are there any known blockers or dependencies on external teams/systems?",
    23: "Is there anything that's explicitly out of scope?",
    # Phase 5 — Preferences & Process (Q24–Q26)
    24: "How do you want stories estimated? (Fibonacci story points, T-shirt sizes, or no estimates)",
    25: "Do you have a Definition of Done the team follows?",
    26: "Do you want the output pushed to Jira, exported as Markdown, or both?",
    # Phase 6 — Capacity Planning (Q27–Q30)
    # See README: "Scrum Standards" — capacity planning
    # Based on analysis of Capacity_Plan_Template.xlsx: real feature capacity is
    # ~24% of gross after deductions (leave, unplanned, onboarding). These questions
    # capture the deductions so the sprint planner can compute net capacity.
    27: "Which sprint are you planning for?",
    28: "How many bank holidays fall within your planning window?",
    29: "What percentage of capacity is typically lost to unplanned absences? (sick days, emergencies)",
    30: "Are any engineers currently onboarding or ramping up? If so, how many and for how many sprints?",
}

# ---------------------------------------------------------------------------
# Sensible defaults for questions that can be skipped.
# See README: "Project Intake Questionnaire" — adaptive behavior
#
# When a user says "skip", "I don't know", etc., the agent uses these defaults
# so it can proceed with reasonable assumptions. The 7 "essential" questions
# (Q1–Q4, Q6, Q11, Q15) have no default — skipping those leaves a gap that
# is flagged in the intake summary.
# ---------------------------------------------------------------------------

QUESTION_DEFAULTS: dict[int, str] = {
    5: "No hard deadlines",
    7: "Roles not specified — assuming generalist/fullstack team",
    8: "2 weeks",
    9: "No historical velocity — will use default of 5 points per engineer per sprint",
    10: "No preference — let the agent decide",
    12: "No known third-party integrations",
    13: "No architectural constraints specified",
    14: "No existing documentation to reference",
    16: "GitHub",
    17: "No repo URL provided",
    18: "Monorepo",
    19: "No",
    20: "No known technical debt identified",
    21: "No specific risks identified",
    22: "No known blockers or external dependencies",
    23: "No explicit exclusions",
    24: "Fibonacci points",
    25: "No — use a recommended DoD",
    26: "Markdown",
    27: "None",
    28: "No bank holidays detected",
    29: "10%",
    30: "None",
}

# ---------------------------------------------------------------------------
# Human-readable phase labels for display in the REPL.
# Maps each QuestionnairePhase enum value to a formatted header string.
# ---------------------------------------------------------------------------

PHASE_LABELS: dict[str, str] = {
    "project_context": "Phase 1: Project Context",
    "team_and_capacity": "Phase 2: Team & Capacity",
    "technical_context": "Phase 3: Technical Context",
    "codebase_context": "Phase 3a: Codebase Context",
    "risks_and_unknowns": "Phase 4: Risks & Unknowns",
    "preferences": "Phase 5: Preferences & Process",
    "capacity_planning": "Phase 6: Capacity Planning",
}


# ---------------------------------------------------------------------------
# Conversational phase intros — shown once when entering a new phase.
# See README: "Project Intake Questionnaire" — phase transitions
#
# These replace the bare phase label with a warmer, conversational opener.
# The REPL or node prepends this in italics before the first question in
# each phase, making the 30-question flow feel less like a government form.
# ---------------------------------------------------------------------------

PHASE_INTROS: dict[str, str] = {
    "project_context": "Let's start with the big picture — what you're building and why.",
    "team_and_capacity": "Now let's talk about your team and how you work.",
    "technical_context": "Tell me about the technical side of things.",
    "codebase_context": "Let's look at the codebase and infrastructure.",
    "risks_and_unknowns": "Almost there — let's flag any risks or unknowns.",
    "preferences": "Last few questions — your preferences for the Scrum plan.",
    "capacity_planning": "Let's factor in real-world capacity — leave, holidays, and ramp-up time.",
}


# ---------------------------------------------------------------------------
# Question metadata — marks 8 of 30 questions as single-choice.
# See README: "Project Intake Questionnaire" — selection menus
#
# QuestionMeta is a frozen dataclass describing a question's input type.
# For single-choice questions, `options` lists the selectable values and
# `default_index` (if set) marks which option is pre-selected when the
# user types "defaults" or "skip".
#
# The REPL uses this metadata to render numbered option menus and resolve
# numeric input (e.g. "1" → "Greenfield"). The intake node uses it to
# skip vagueness checks on choice questions (choices are never vague) and
# to apply batch defaults when the user types "defaults".
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class QuestionMeta:
    """Metadata for a single intake question.

    # See README: "Project Intake Questionnaire" — selection menus

    Attributes:
        question_type: "free_text", "single_choice", or "multi_choice".
        options: Tuple of option strings for choice questions.
        default_index: Index into `options` for the default selection (single_choice),
            or None if no default (question is required / essential).
            Ignored for multi_choice.
    """

    question_type: str  # "free_text" | "single_choice" | "multi_choice"
    options: tuple[str, ...] = ()
    default_index: int | None = None


QUESTION_METADATA: dict[int, QuestionMeta] = {
    2: QuestionMeta(
        question_type="single_choice",
        options=("Greenfield", "Existing codebase", "Hybrid"),
        default_index=None,  # essential — no default
    ),
    7: QuestionMeta(
        question_type="multi_choice",
        options=("Backend", "Frontend", "Fullstack", "DevOps/Infra", "QA/Testing", "Design", "Data/ML"),
    ),
    8: QuestionMeta(
        question_type="single_choice",
        options=("1 week", "2 weeks", "3 weeks", "4 weeks"),
        default_index=1,  # 2 weeks
    ),
    10: QuestionMeta(
        question_type="single_choice",
        options=(
            "1–2 sprints",
            "3–5 sprints",
            "6–10 sprints",
            "10+ sprints",
            "No preference — let the agent decide",
        ),
        default_index=4,  # No preference
    ),
    13: QuestionMeta(
        question_type="multi_choice",
        options=("Microservices", "Monolith", "Serverless", "AWS", "GCP", "Azure", "On-premise", "Kubernetes/Docker"),
    ),
    16: QuestionMeta(
        question_type="single_choice",
        options=("GitHub", "Azure DevOps", "GitLab", "Bitbucket", "Local"),
        default_index=0,  # GitHub
    ),
    18: QuestionMeta(
        question_type="single_choice",
        options=("Monorepo", "Multi-repo", "Microservices", "Monolith"),
        default_index=0,  # Monorepo
    ),
    19: QuestionMeta(
        question_type="single_choice",
        options=("Yes", "No", "Partial/in progress"),
        default_index=1,  # No
    ),
    24: QuestionMeta(
        question_type="single_choice",
        options=("Fibonacci points", "T-shirt sizes", "No estimates"),
        default_index=0,  # Fibonacci points
    ),
    25: QuestionMeta(
        question_type="single_choice",
        options=("Yes — team has a standard DoD", "No — use a recommended DoD"),
        default_index=1,  # No — use recommended
    ),
    26: QuestionMeta(
        question_type="single_choice",
        options=("Jira", "Markdown", "Both"),
        default_index=1,  # Markdown
    ),
    27: QuestionMeta(
        question_type="single_choice",
        options=(),  # dynamic — populated at runtime when Jira is configured
        default_index=None,  # no default — auto-defaulted when no Jira
    ),
    28: QuestionMeta(
        question_type="single_choice",
        options=(),  # dynamic — populated at runtime from bank holiday detection
        default_index=None,
    ),
    29: QuestionMeta(
        question_type="single_choice",
        options=("0%", "5%", "10%", "15%", "20%"),
        default_index=2,  # 10%
    ),
    30: QuestionMeta(
        question_type="single_choice",
        options=("None", "1 engineer", "2 engineers", "3+ engineers"),
        default_index=0,  # None
    ),
}


# ---------------------------------------------------------------------------
# Intake mode constants — controls how many questions are asked.
# See README: "Project Intake Questionnaire" — smart intake
#
# Three modes:
#   "smart" (new default in REPL) — auto-apply extracted answers + defaults,
#       only ask unfilled essential gaps. Typical: 2-4 questions.
#   "quick" — only Q6 (team size) and Q11 (tech stack) as gaps; everything
#       else auto-filled with extraction + defaults + fallbacks.
#   "standard" — the original 30-question one-at-a-time flow.
#
# ESSENTIAL_QUESTIONS is the full set of questions with no sensible default.
# SMART_ESSENTIALS is the subset asked in smart mode (Q1 comes from the
# description, Q15 is derived from Q2). QUICK_ESSENTIALS is the minimal
# subset for quick mode.
# ---------------------------------------------------------------------------

ESSENTIAL_QUESTIONS: frozenset[int] = frozenset({1, 2, 3, 4, 6, 11, 15})
# Q1=from description, Q15=derived from Q2
# Q27 (sprint selection) is essential — auto-derived when no Jira, asked with choice menu when Jira configured.
# Q28 (bank holidays) is auto-detected via the detect_bank_holidays tool after Q27 — no longer asked
# interactively. The result is shown in the velocity breakdown at confirmation; user can override.
# Q29-Q30 (unplanned %, onboarding) are defaulted in smart mode — users can edit at confirmation.
# Q8 (sprint length) excluded — always defaults to "2 weeks" in smart mode.
SMART_ESSENTIALS: frozenset[int] = frozenset({2, 3, 4, 6, 10, 11, 27})
QUICK_ESSENTIALS: frozenset[int] = frozenset({6, 11})

# Conditional essentials — questions that become essential when their
# prerequisite has a real (non-defaulted) answer. This keeps smart mode
# lean while still asking useful follow-ups when context is available.
# See README: "Project Intake Questionnaire" — conditional essentials
CONDITIONAL_ESSENTIALS: dict[int, int] = {
    7: 6,  # ask team roles        when team size is answered
    12: 11,  # ask integrations      when tech stack is answered
    13: 2,  # ask constraints       when project type is answered
    29: 6,  # ask unplanned leave % when team size is answered
}

# Deterministic mapping from Q2 (project type) → Q15 (existing codebase?).
# Avoids an LLM call — Q15 is redundant when Q2 is answered.
Q2_TO_Q15_MAP: dict[str, str] = {
    "Greenfield": "New build (greenfield)",
    "Existing codebase": "Yes, existing codebase",
    "Hybrid": "Partial — extending existing codebase with new components",
}

# Merged prompt for Q3+Q4 — asked as a single question in smart mode.
# The answer is stored under both Q3 and Q4 keys so the project_analyzer
# still sees both fields populated.
MERGED_Q3_Q4_PROMPT: str = (
    "What problem does this project solve, who are the end users, "
    "and what does 'done' look like — what's the end-state you're targeting?"
)

# Fallback defaults for quick mode — used when extraction doesn't fill
# essential questions. These are deliberately generic so quick mode can
# produce a plan even with minimal input.
QUICK_FALLBACK_DEFAULTS: dict[int, str] = {
    2: "Greenfield",
    3: "Not specified — will infer from project description",
    4: "Not specified — will infer from project description",
    15: "New build (greenfield)",
}


# ---------------------------------------------------------------------------
# Short labels for table display — used by render_intake_summary() in formatters.py.
# Each label is 2-4 words, just enough to identify the question in a compact table.
# The full question text is in INTAKE_QUESTIONS above.
# ---------------------------------------------------------------------------

QUESTION_SHORT_LABELS: dict[int, str] = {
    1: "Project description",
    2: "Project type",
    3: "Problem to solve",
    4: "Definition of done",
    5: "Deadlines",
    6: "Team size",
    7: "Team roles",
    8: "Sprint length",
    9: "Known velocity",
    10: "Target sprints",
    11: "Tech stack",
    12: "Integrations",
    13: "Architecture constraints",
    14: "Existing docs",
    15: "Codebase status",
    16: "Code hosting",
    17: "Repo URL(s)",
    18: "Repo structure",
    19: "CI/CD pipeline",
    20: "Technical debt",
    21: "Risks & unknowns",
    22: "Blockers / deps",
    23: "Out of scope",
    24: "Estimation style",
    25: "Definition of Done",
    26: "Output format",
    27: "Sprint selection",
    28: "Holidays & leave",
    29: "Unplanned absence %",
    30: "Onboarding",
}


# ---------------------------------------------------------------------------
# Intake mode selection menu — shown in the REPL when no CLI flag is given.
# See README: "Project Intake Questionnaire" — smart intake
#
# Keeps display strings in prompts/ alongside PHASE_LABELS and PHASE_INTROS.
# The REPL renders this as a numbered menu ([1]/[2]/[3]) so the user picks
# a mode before the first question is asked.
# ---------------------------------------------------------------------------

INTAKE_MODE_MENU: dict[str, tuple[str, str]] = {
    "smart": (
        "Smart intake (recommended)",
        "I'll extract answers from your description and only ask 2-4 essential\n"
        "      follow-ups. Good for most projects.",
    ),
    "standard": (
        "Full intake",
        "All 30 questions, one at a time. Includes follow-up probes when answers\n"
        "      are vague. Best for complex projects spanning multiple years or\n"
        "      requiring multiple teams.",
    ),
    "offline": (
        "Offline questionnaire",
        "Export a blank template to fill in at your own pace, or import\n"
        "      a completed one. Best when you want to prepare answers offline.",
    ),
}

INTAKE_MODE_ORDER: tuple[str, ...] = ("smart", "standard", "offline")

# ---------------------------------------------------------------------------
# Offline questionnaire sub-menu — shown when the user picks [3] Offline.
# Two actions: export a blank template, or import a filled one.
# ---------------------------------------------------------------------------

OFFLINE_SUBMENU: dict[str, tuple[str, str]] = {
    "export": (
        "Export blank questionnaire",
        "Saves a template to scrum-questionnaire.md — fill it in, then\n      come back and import.",
    ),
    "import": (
        "Import filled questionnaire",
        "Load a completed .md file and jump straight to review.",
    ),
}

OFFLINE_SUBMENU_ORDER: tuple[str, ...] = ("export", "import")

# ---------------------------------------------------------------------------
# Top-level agent mode menu — shown once on startup before any REPL
# loop starts. Each entry maps mode_key → (title, description).
# ---------------------------------------------------------------------------

STARTUP_MODE_MENU: dict[str, tuple[str, str]] = {
    "project-planning": (
        "Project Planning",
        "Decompose your project into epics, user stories, tasks, and a sprint plan.\n"
        "      Includes smart / full / offline intake questionnaire.",
    ),
    "coming-soon-1": (
        "Code Review  [dim](coming soon)[/dim]",
        "Review pull requests and suggest improvements. [dim]Not yet available.[/dim]",
    ),
    "coming-soon-2": (
        "Sprint Review  [dim](coming soon)[/dim]",
        "Retrospective analysis and next-sprint recommendations. [dim]Not yet available.[/dim]",
    ),
}

STARTUP_MODE_ORDER: tuple[str, ...] = ("project-planning", "coming-soon-1", "coming-soon-2")


# ---------------------------------------------------------------------------
# Improvement hints — actionable suggestions for essential/high-value questions.
# Used by compute_prompt_quality() in nodes.py to generate suggestions when
# essential questions are defaulted or skipped.
# ---------------------------------------------------------------------------

QUESTION_IMPROVEMENT_HINTS: dict[int, str] = {
    # Essential questions (Q1-Q4, Q6, Q11, Q15)
    1: "Describe your project in detail for better feature decomposition",
    2: "Specify if this is greenfield or existing codebase — affects architecture decisions",
    3: "Clarify the problem and end users for more targeted user stories",
    4: "Define what 'done' looks like for clearer acceptance criteria",
    6: "Specify team size for accurate sprint capacity planning",
    11: "Specify your tech stack for more accurate task decomposition",
    15: "Clarify codebase status to tailor infrastructure tasks",
    # High-value non-essential questions
    14: "Link existing docs or PRDs so the agent can reference your design decisions",
    17: "Share repo URL(s) so the agent can scan your codebase for context",
}

# Hint for SCRUM.md — not a questionnaire question, shown when no user_context
# was loaded. The SCRUM.md file lets users provide free-form project context
# (URLs, design notes, tech decisions) that enriches the analysis.
SCRUM_MD_HINT: str = "Add a SCRUM.md file to your project root with links, design notes, or tech decisions"


# ---------------------------------------------------------------------------
# Step 2: Smarter Extraction — keyword maps for deterministic fallback
# See README: "Project Intake Questionnaire" — smart intake
#
# After LLM extraction, a deterministic keyword scan catches signals the
# LLM may have been too conservative to infer. Keywords are case-insensitive.
# ---------------------------------------------------------------------------

Q2_INFERENCE_KEYWORDS: dict[str, str] = {
    "refactor": "Existing codebase",
    "migrate": "Existing codebase",
    "legacy": "Existing codebase",
    "rewrite": "Existing codebase",
    "from scratch": "Greenfield",
    "new project": "Greenfield",
    "startup": "Greenfield",
    "greenfield": "Greenfield",
}

Q12_SERVICE_KEYWORDS: frozenset[str] = frozenset(
    {
        "stripe",
        "auth0",
        "firebase",
        "twilio",
        "sendgrid",
        "segment",
        "launchdarkly",
        "datadog",
        "pagerduty",
        "sentry",
        "okta",
        "plaid",
        "algolia",
        "cloudflare",
        "vercel",
    }
)

Q13_INFRA_KEYWORDS: frozenset[str] = frozenset(
    {
        "kubernetes",
        "k8s",
        "microservices",
        "serverless",
        "lambda",
        "aws",
        "gcp",
        "azure",
        "docker",
        "monolith",
        "on-premise",
        "terraform",
        "cloudformation",
        "ecs",
        "eks",
    }
)


# ---------------------------------------------------------------------------
# Step 3: Adaptive Question Text — templates that reference prior answers
# See README: "Project Intake Questionnaire" — adaptive question text
#
# When a template's dependencies are available (and not defaulted), the
# intake node renders the personalized version instead of the generic
# INTAKE_QUESTIONS text. Fallback is always the original question.
# ---------------------------------------------------------------------------

ADAPTIVE_QUESTION_TEMPLATES: dict[int, str] = {
    7: "You said {q6} engineers — what are their roles? (e.g., 2 backend, 1 frontend, 1 fullstack)",
    12: "You mentioned {q11} as your tech stack. Are there any existing APIs, services, or third-party integrations?",
    13: "Since this is a {q2} project, are there any architectural constraints? (e.g., {hint})",
}

Q2_CONSTRAINT_HINTS: dict[str, str] = {
    "Greenfield": "microservices vs monolith, cloud provider, language choices",
    "Existing codebase": "must preserve existing APIs, database migrations, backward compatibility",
    "Hybrid": "which parts are new vs existing, integration boundaries",
}


# ---------------------------------------------------------------------------
# Step 4: Follow-up Quality — custom follow-up templates per question
# See README: "Project Intake Questionnaire" — follow-up probing
#
# When _check_vague_answer detects a vague answer for one of these questions,
# the custom template is injected into the LLM prompt as a hint. This produces
# more targeted follow-ups than the generic "tell me more" approach.
# ---------------------------------------------------------------------------

FOLLOW_UP_TEMPLATES: dict[int, str] = {
    3: "Who experiences this problem? Can you give 2-3 user personas?",
    4: "What measurable outcome would tell you the project succeeded?",
    7: "Can you break down the team by specialty? (e.g., 2 backend, 1 frontend, 1 DevOps)",
    11: "What's the primary language and framework? And what database/storage?",
    12: "Which integrations are critical vs nice-to-have?",
    13: "Are these constraints firm (non-negotiable) or preferences?",
    20: "Which area of tech debt is the highest risk?",
    21: "Which risk should be addressed earliest? What's the worst-case impact?",
}


# ---------------------------------------------------------------------------
# Step 5: Cross-Question Validation — catch contradictions before analysis
# See README: "Project Intake Questionnaire" — cross-question validation
#
# Deterministic rules (no LLM call) that flag contradictory or unrealistic
# answer combinations. Warnings are advisory — the user can still confirm.
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class ValidationWarning:
    """A warning about contradictory or unrealistic intake answers."""

    question_nums: tuple[int, ...]
    message: str
    severity: str = "warning"  # "warning" | "info"


def is_choice_question(q_num: int) -> bool:
    """Check whether a question number has choice metadata (single or multi).

    Args:
        q_num: The 1-based question number.

    Returns:
        True if the question is a single_choice or multi_choice question.
    """
    meta = QUESTION_METADATA.get(q_num)
    return meta is not None and meta.question_type in ("single_choice", "multi_choice")
