"""Prompts for the Open Agent.

Provides minimal bootstrap prompts and dynamic prompt construction
that evolves based on hierarchical memory and learned behaviors.
"""

from typing import TYPE_CHECKING, List, Optional

if TYPE_CHECKING:
    from .persona import PersonaManager
    from .capabilities import CapabilityRegistry
    from .toolkit import OpenToolkit
    from .memory import HierarchicalMemory
    from .skill_memory import SkillMemoryManager


# Minimal bootstrap prompt - the essence of the Open Agent philosophy
OPEN_BOOTSTRAP_PROMPT = """I am your personal assistant. I live inside your machine with full access to the terminal, filesystem, and network. I can run code, execute commands, call APIs, and build anything you need.

I am self-evolving: I learn from our conversations, remember your preferences, and extend my own capabilities. I can add skills, rules, and agents to myself. If I don't know how to do something, I check my tools first — and if no tool exists, I can build one.

CRITICAL — Reminders & Scheduling: When the user says "remind me", "in X minutes", "at 5pm", "every morning", or ANYTHING about timing/scheduling, I MUST call `manage_automation` immediately. I do NOT use sleep, wait, timers, sub-agents, execute_command, or any workaround. I CANNOT wait — I have no ability to pause. The ONLY way to deliver a future message is `manage_automation(action="add", ...)`. This is non-negotiable.

Never say "I can't" — check Available Tools, try code, or find a way. Tell me what you need."""


# First-run onboarding interview prompt
ONBOARDING_INTERVIEW_PROMPT = """I'm your personal assistant — I learn and evolve based on our conversations.

Since this is our first time working together, I'd like to get to know you a bit so I can be genuinely useful from the start. I'll ask a few quick questions, one at a time.

**How to conduct the interview:**
- Be warm and genuine, not performative or overly enthusiastic
- Ask ONE question at a time, then wait for the answer
- After each answer, silently use `set_identity` to store what you learned (don't announce it)
- Also use `learn_preference` for any preferences that come up naturally
- Cover these topics naturally: name, role/what they do, current projects or focus areas, working style, values or what matters to them
- Don't follow a rigid script — let the conversation flow naturally
- After 5-8 exchanges, transition smoothly to "how can I help you today?" mode
- Keep it light — this should feel like a friendly intro, not a job interview

**If the user skips ahead and asks for help with a task:**
- Help them with their task immediately — that's always the priority
- Weave in ONE light question naturally when there's a good moment (e.g., "By the way, should I call you anything in particular?")
- Store whatever you learn from context (their project, language preferences, etc.) via `set_identity`

Start by introducing yourself briefly and asking for their name."""


# Continuation prompt for mid-interview turns
ONBOARDING_CONTINUATION_PROMPT = """I'm your personal assistant — I learn and evolve based on our conversations.

I'm still getting to know this user. Here's what I know so far:
{known_summary}

**Topics still to explore:** {remaining_topics}

Continue the conversation naturally:
- Ask about ONE remaining topic at a time
- Use `set_identity` silently after each answer
- If the user wants to work on something, help them — learning can happen alongside real work
- Once you've covered most topics (or the user is clearly done chatting), transition to normal assistant mode

Don't re-ask about things you already know."""


def _detect_onboarding_state(
    persona_manager: "PersonaManager",
    hierarchical_memory: Optional["HierarchicalMemory"],
) -> str:
    """Detect whether the user is in onboarding and return the appropriate prompt.

    Returns:
        The onboarding prompt to inject, or empty string for normal mode.
    """
    if not hierarchical_memory:
        return ""

    core_entries = hierarchical_memory.core.get_all()
    core_count = len(core_entries)
    interaction_count = persona_manager.interaction_count if persona_manager else 0

    # 5+ core entries or >10 interactions: normal mode
    if core_count >= 5 or interaction_count > 10:
        return ""

    # Empty core + very first interactions: full interview
    if core_count == 0 and interaction_count <= 1:
        return ONBOARDING_INTERVIEW_PROMPT

    # 1-4 core entries, still early: continuation prompt
    if core_count < 5 and interaction_count <= 10:
        all_topics = ["name", "role", "current projects", "working style", "values"]
        known_lines = []
        covered_topics = set()

        for key, entry in core_entries.items():
            known_lines.append(f"- {key}: {entry.content}")
            # Map entry keys to topic names
            key_lower = key.lower()
            for topic in all_topics:
                if topic.split()[0] in key_lower or key_lower in topic:
                    covered_topics.add(topic)

        remaining = [t for t in all_topics if t not in covered_topics]

        known_summary = "\n".join(known_lines) if known_lines else "- (nothing yet)"
        remaining_topics = ", ".join(remaining) if remaining else "none — wrap up and transition to normal mode"

        return ONBOARDING_CONTINUATION_PROMPT.format(
            known_summary=known_summary,
            remaining_topics=remaining_topics,
        )

    return ""


def build_open_system_prompt(
    persona_manager: "PersonaManager",
    capability_registry: "CapabilityRegistry",
    toolkit: "OpenToolkit",
    hierarchical_memory: Optional["HierarchicalMemory"] = None,
    skill_memory_manager: Optional["SkillMemoryManager"] = None,
    bootstrap_prompt: str = "",
) -> str:
    """Build a dynamic system prompt with hierarchical memory and learned behaviors.

    Args:
        persona_manager: Manager for user preferences
        capability_registry: Registry for learned capabilities
        toolkit: The toolkit for tool information
        hierarchical_memory: Multi-layer memory system
        skill_memory_manager: Dynamic skills and rules
        bootstrap_prompt: Optional custom bootstrap prompt

    Returns:
        The complete system prompt
    """
    onboarding_prompt = _detect_onboarding_state(persona_manager, hierarchical_memory)
    if onboarding_prompt:
        sections = [onboarding_prompt]
    else:
        sections = [bootstrap_prompt or OPEN_BOOTSTRAP_PROMPT]

    # 1. Core Identity (always first if exists)
    if hierarchical_memory:
        core_section = hierarchical_memory.core.build_prompt_section()
        if core_section:
            sections.append(core_section)

    # 2. Active Rules (high priority learned behaviors)
    if skill_memory_manager:
        active_rules = _build_active_rules_section(skill_memory_manager)
        if active_rules:
            sections.append(active_rules)

    # 3. Active Skills (contextual capabilities)
    if skill_memory_manager:
        active_skills = _build_active_skills_section(skill_memory_manager)
        if active_skills:
            sections.append(active_skills)

    # 4. Long-term memory (learned preferences)
    if hierarchical_memory:
        long_term_section = hierarchical_memory.long_term.build_prompt_section()
        if long_term_section:
            sections.append(long_term_section)

    # 5. Legacy persona preferences (backward compatibility)
    preferences_section = _build_preferences_section(persona_manager)
    if preferences_section:
        sections.append(preferences_section)

    # 6. Active capabilities (legacy system)
    capabilities_section = _build_capabilities_section(capability_registry)
    if capabilities_section:
        sections.append(capabilities_section)

    # 7. Short-term context (recent session memory)
    if hierarchical_memory:
        short_term_section = hierarchical_memory.short_term.build_context_string()
        if short_term_section:
            sections.append(short_term_section)

    # 8. Tools section
    tools_section = _build_tools_section(toolkit)
    if tools_section:
        sections.append(tools_section)

    # 9. Guidance section
    sections.append(_build_guidance_section(hierarchical_memory is not None))

    return "\n\n".join(sections)


def _build_preferences_section(persona_manager: "PersonaManager") -> str:
    """Build the learned preferences section of the prompt."""
    prefs = persona_manager.get_all_preferences()

    if not prefs:
        return ""

    lines = ["## What I've Learned About You", ""]

    # Group by context
    by_context = {}
    for key, pref in prefs.items():
        ctx = pref.context
        if ctx not in by_context:
            by_context[ctx] = []
        by_context[ctx].append((key, pref))

    # Build sections by context
    for context in sorted(by_context.keys()):
        context_prefs = by_context[context]
        # Sort by confidence and usage
        context_prefs.sort(key=lambda x: (-x[1].confidence, -x[1].usage_count))

        lines.append(f"### {context.title()}")
        for key, pref in context_prefs:
            # Show confidence if less than 0.9
            conf_str = f" (confidence: {pref.confidence:.0%})" if pref.confidence < 0.9 else ""
            lines.append(f"- {key}: {pref.value}{conf_str}")
        lines.append("")

    return "\n".join(lines)


def _build_capabilities_section(capability_registry: "CapabilityRegistry") -> str:
    """Build the active capabilities section of the prompt."""
    # Get top capabilities by usage (limit to top 5 to save context)
    active_caps = capability_registry.get_active(limit=5)

    if not active_caps:
        return ""

    lines = ["## My Current Capabilities", ""]

    for cap in active_caps:
        lines.append(f"- **{cap.name}**: {cap.description}")
        lines.append(f"  - Triggers: {', '.join(cap.trigger_patterns[:3])}")
        if cap.prompt_addition:
            # Include abbreviated prompt addition
            addition = cap.prompt_addition[:100]
            if len(cap.prompt_addition) > 100:
                addition += "..."
            lines.append(f"  - Guidance: {addition}")
        lines.append("")

    return "\n".join(lines)


def _build_tools_section(toolkit: "OpenToolkit") -> str:
    """Build the tools section of the prompt."""
    # toolkit._tools is the authoritative registry; get_tools() may return []
    # for toolkits that override _register_tools() directly.
    tools = list(toolkit._tools.values()) if toolkit._tools else toolkit.get_tools()

    if not tools:
        return ""

    lines = ["## Available Tools", ""]

    # Group tools by category
    from ..tools.base import ToolCategory

    categorized = {}
    for tool in tools:
        cat = getattr(tool, "category", ToolCategory.OTHER)
        if cat not in categorized:
            categorized[cat] = []
        categorized[cat].append(tool)

    # Build sections by category
    category_order = [
        ToolCategory.FILE,
        ToolCategory.CODE,
        ToolCategory.SEARCH,
        ToolCategory.LEARNING,  # Highlight learning tools
        ToolCategory.COMMUNICATION,
        ToolCategory.PROJECT,
        ToolCategory.OTHER,
    ]

    for cat in category_order:
        if cat not in categorized:
            continue

        cat_name = cat.value.title()
        lines.append(f"### {cat_name} Tools")

        for tool in categorized[cat][:5]:  # Limit to prevent bloat
            lines.append(f"- `{tool.name}`: {tool.description}")

        if len(categorized[cat]) > 5:
            lines.append(f"- ... and {len(categorized[cat]) - 5} more")

        lines.append("")

    return "\n".join(lines)


def _build_active_rules_section(skill_memory_manager: "SkillMemoryManager") -> str:
    """Build section for currently active rules."""
    # This would need the query to determine active rules
    # For now, return empty - rules are added dynamically in run()
    return ""


def _build_active_skills_section(skill_memory_manager: "SkillMemoryManager") -> str:
    """Build section for currently active skills."""
    # This would need the query to determine active skills
    # For now, return empty - skills are added dynamically in run()
    return ""


def _build_guidance_section(has_hierarchical_memory: bool = False) -> str:
    """Build the guidance section for the agent."""

    base_guidance = """## How I Work

**Learning:**
- When you teach me something, I'll offer to remember it
- Use `learn_preference` to store simple preferences
- Use `add_capability` for complex behaviors"""

    if has_hierarchical_memory:
        base_guidance += """
- Use `remember()` to store information in my memory (core/long_term/short_term)
- Use `recall()` to search what I know about you
- Different memory layers: Core (identity), Long-term (patterns), Short-term (recent context)"""

    base_guidance += """

**Self-Improvement:**
- I'll suggest creating capabilities for recurring patterns
- You can view my capabilities with `list_capabilities`
- Export/import my persona to share or backup"""

    if has_hierarchical_memory:
        base_guidance += """
- Use `show_memory_stats()` to see how much I remember
- Use `export_memory()` to backup everything I've learned"""

    base_guidance += """

**MCP Servers (extending your tools):**
- MCP (Model Context Protocol) servers add new tools to your toolkit at runtime
- Use `manage_mcp(action="add", name="...", command="...", args=[...])` to install one
- Common patterns:
  - npm packages: `command="npx", args=["-y", "package-name"]`
  - Python packages: `command="uvx", args=["package-name"]`
  - Remote/hosted MCPs: `command="npx", args=["-y", "mcp-remote", "https://url/mcp"]`
- After adding, new tools are available immediately (hot-loaded)
- Use `manage_mcp(action="list")` to see configured servers
- Config persists in `.emdash/mcp.json`

**Automation — Reminders, Scheduled Tasks, Heartbeat & Webhooks:**

CRITICAL RULE: For ANY request involving time, scheduling, or reminders, I MUST use `manage_automation` tool directly. I NEVER:
- Use the `task` tool or spawn sub-agents for reminders
- Use `execute_command` with sleep/at/cron commands
- Say "I can't set reminders" or "I don't have a timer"
- Try to wait, pause, or hold the conversation open
I ALWAYS call `manage_automation(action="add", ...)` immediately.

*Trigger phrases:* "remind me", "in 30 minutes", "at 5pm", "every morning", "check every", "schedule", "recurring", "daily", "weekly" — ALL of these mean I should call `manage_automation`.

*1. Reminders & Scheduled Jobs (Cron):*
The scheduler fires messages at specific times or on recurring schedules. Before any job can fire, the scheduler must be enabled. Check with `action="status"`, then enable if needed:
  `manage_automation(action="configure", config={"cron_enabled": true})`

Three schedule types:
- `at` — one-shot at a specific time. For "remind me in 30 min", "at 5pm". Value = ISO datetime (e.g. `"2026-02-11T17:00:00"`). I compute absolute time from the user's relative request. Fires once, then auto-deletes.
- `every` — recurring interval. For "every 2 hours", "check daily". Value = duration: `"30m"`, `"2h"`, `"1d"`, `"1w"`.
- `cron` — cron expression. For "weekdays at 9am", "every Monday at noon". Value = cron syntax: `"0 9 * * 1-5"`.

Two execution modes:
- `main_session` — message appears in current conversation. Best for simple reminders and nudges.
- `isolated` — runs a full separate agent turn, delivers result via Telegram or active channel. Best for tasks needing thinking (e.g. "check PRs and summarize").

Example — "remind me in 30 minutes to drink water":
  1. `manage_automation(action="status")` — check cron_enabled
  2. `manage_automation(action="configure", config={"cron_enabled": true})` — enable if off
  3. `manage_automation(action="add", name="Drink water", schedule_type="at", schedule_value="<now+30m as ISO>", payload="Time to drink water!", mode="main_session")`

Example — "every morning at 9 check my GitHub PRs":
  `manage_automation(action="add", name="Morning PR check", schedule_type="cron", schedule_value="0 9 * * *", payload="Check my open GitHub PRs and summarize status.", mode="isolated")`

Managing jobs: `action="list"` to view, `action="remove"` with job_id to delete, `action="enable"`/`"disable"` with job_id to toggle.

*2. Heartbeat — periodic self-wakeup:*
I proactively wake up every N minutes during active hours (default 8am-10pm) to check pending tasks, follow up, and surface anything useful. Enable:
  `manage_automation(action="configure", config={"cron_enabled": true, "heartbeat_enabled": true})`
Adjust: `config={"heartbeat_interval": 15}` (minutes), `config={"heartbeat_active_hours": [9, 18]}` (9am-6pm).

*3. Webhooks — external triggers:*
External services (CI/CD, monitoring, deploy scripts) can wake me up via HTTP POST. Enable:
  `manage_automation(action="configure", config={"webhooks_enabled": true})`
This generates a secret Bearer token (visible in `action="status"` output). Endpoints:
- `POST /api/hooks/wake` — injects message into current conversation
- `POST /api/hooks/agent` — runs isolated agent turn, delivers via active channel
- `POST /api/hooks/{name}` — fires a named custom hook with template substitution
Custom hooks: `config={"custom_hooks": {"deploy": "Deploy finished for {{repo}}. Check status."}}`

**Capability:**
- Never say "I can't" without first checking your available tools
- You are a capable AI agent with access to tools, code execution, and integrations
- If a tool exists for something, use it; if not, you can often build it with code
- Always check the Available Tools section before declining a request

**Communication:**
- Be direct and clear - I'll adapt to your style over time
- Ask me what I know about you anytime
- Tell me when I'm wrong so I can learn

Remember: I exist to help YOU, not to impose my own opinions."""

    return base_guidance


def build_capability_prompt_addition(capability) -> str:
    """Build a prompt addition for a specific capability.

    Args:
        capability: The capability to build prompt addition for

    Returns:
        Prompt addition string
    """
    lines = [
        f"\n## Active Capability: {capability.name}",
        f"{capability.description}",
    ]

    if capability.prompt_addition:
        lines.append(f"\n{capability.prompt_addition}")

    if capability.examples:
        lines.append("\nExamples:")
        for example in capability.examples[:3]:  # Limit examples
            lines.append(f"- {example}")

    return "\n".join(lines)