# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Proactive Notifications Skill

Daily briefings, smart reminders, deadline alerts.
Works with the scheduler to send proactive messages.
"""

import json
import logging
import os
from datetime import datetime, timedelta
from pathlib import Path

logger = logging.getLogger(__name__)

# Storage paths - use centralized paths


def _get_data_dir():
    """Get data directory (tenant-scoped in Reflection, default in standalone)."""
    try:
        from familiar.core import paths

        return paths.DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


try:
    from familiar.core.paths import DATA_DIR, ensure_dir
except ImportError:
    DATA_DIR = _get_data_dir()

    def ensure_dir(path):
        path.mkdir(parents=True, exist_ok=True)
        return path


PROACTIVE_FILE = DATA_DIR / "proactive.json"

ensure_dir(DATA_DIR)


def _load_config() -> dict:
    """Load proactive config."""
    if PROACTIVE_FILE.exists():
        try:
            return json.loads(PROACTIVE_FILE.read_text())
        except (json.JSONDecodeError, IOError) as e:
            logger.warning(f"Failed to load proactive config: {e}")
    return {
        "briefing_time": "08:00",
        "briefing_enabled": True,
        "deadline_alert_days": 3,
        "followup_enabled": True,
        "quiet_hours_start": "21:00",
        "quiet_hours_end": "08:00",
    }


def _save_config(config: dict):
    """Save proactive config."""
    PROACTIVE_FILE.write_text(json.dumps(config, indent=2))


def generate_briefing(data: dict) -> str:
    """Generate a comprehensive daily briefing."""
    sections = []

    # Date header
    now = datetime.now()
    sections.append(f"📅 **{now.strftime('%A, %B %d, %Y')}**\n")

    # === Calendar ===
    try:
        from .calendar.skill import get_events

        calendar = get_events({"date": "today", "days": 1})
        if calendar and "No events" not in calendar:
            sections.append("**Today's Schedule:**")
            sections.append(calendar)
            sections.append("")
    except Exception as e:
        logger.warning(f"Calendar not available: {e}")

    # === Tasks ===
    try:
        from familiar.skills.tasks.skill import get_daily_briefing as get_tasks

        tasks = get_tasks({})
        if tasks and "All clear" not in tasks:
            sections.append("**Tasks:**")
            sections.append(tasks)
            sections.append("")
    except Exception as e:
        logger.warning(f"Tasks not available: {e}")

    # === Email Summary ===
    try:
        from familiar.skills.triage.skill import get_category_summary

        email_summary = get_category_summary({"days_back": 1})
        if email_summary and "No unread" not in email_summary:
            sections.append("**Email:**")
            sections.append(email_summary)
            sections.append("")
    except Exception as e:
        logger.warning(f"Email triage not available: {e}")

    # === Grant Deadlines ===
    try:
        from familiar.skills.nonprofit.skill import grant_deadlines

        grants = grant_deadlines({"days": 14})
        if grants and "No grant deadlines" not in grants:
            sections.append("**Upcoming Grant Deadlines:**")
            sections.append(grants)
            sections.append("")
    except Exception as e:
        logger.warning(f"Nonprofit skill not available: {e}")

    # === Donors Needing Thanks ===
    try:
        from familiar.skills.nonprofit.skill import donors_needing_thanks

        thanks = donors_needing_thanks({"days": 7})
        if thanks and "All recent donors" not in thanks:
            sections.append("**Donor Follow-ups:**")
            sections.append(thanks)
    except Exception as e:
        logger.warning(f"Donor tracking not available: {e}")

    if len(sections) <= 1:
        sections.append("✨ No urgent items today!")

    return "\n".join(sections)


def generate_briefing_data() -> dict:
    """Generate structured briefing data for dashboard rendering.

    Calendar section is structured (list of event dicts with attendees).
    Other sections are raw strings — the LLM synthesizes them anyway.
    Sections included depend on the active job class so the briefing
    stays relevant to what the user actually does.
    """
    now = datetime.now()

    # Determine active job class key (e.g. "chef", "artist", "nonprofit_director")
    job_class_key = ""
    try:
        from familiar.jobs import get_active_job_class

        jc = get_active_job_class()
        if jc:
            job_class_key = jc.key
    except Exception:
        pass

    result = {
        "timestamp": now.isoformat(),
        "job_class": job_class_key,
        "sections": {
            "calendar": [],
            "tasks": "",
            "email": "",
            "grants": "",
            "donors": "",
            "news": {},
            "podcasts": [],
            "weather": [],
        },
    }

    # === Calendar (structured — all job classes) ===
    try:
        from familiar.skills.calendar.skill import get_events_structured

        result["sections"]["calendar"] = get_events_structured({"date": "today", "days": 1})
    except Exception as e:
        logger.warning(f"Calendar not available for structured briefing: {e}")

    # === Tasks (all job classes) ===
    try:
        from familiar.skills.tasks.skill import get_daily_briefing as get_tasks

        tasks = get_tasks({})
        if tasks and "All clear" not in tasks and not tasks.startswith("❌"):
            result["sections"]["tasks"] = tasks
    except Exception as e:
        logger.warning(f"Tasks not available: {e}")

    # === Email Summary (sorted cache preferred, triage fallback) ===
    try:
        # Prefer sorted cache from Email Intelligence monitor
        _used_sorted = False
        try:
            from familiar.skills.email.monitor import get_sorted_inbox

            cache = get_sorted_inbox()
            if cache.get("emails"):
                s = cache.get("summary", {})
                parts = []
                if s.get("urgent"):
                    parts.append(f"\U0001f534 {s['urgent']} urgent")
                if s.get("action"):
                    parts.append(f"\U0001f7e1 {s['action']} action")
                parts.append(f"{s.get('total', 0)} total sorted")
                result["sections"]["email"] = "\U0001f4ca " + " \u2022 ".join(parts)
                _used_sorted = True
        except Exception:
            pass

        if not _used_sorted:
            from familiar.skills.triage.skill import get_category_summary

            email_summary = get_category_summary({"days_back": 3})
            if (
                email_summary
                and "No unread" not in email_summary
                and not email_summary.startswith("\u274c")
                and "failed" not in email_summary.lower()
                and "not configured" not in email_summary.lower()
            ):
                result["sections"]["email"] = email_summary
            elif email_summary and email_summary.startswith("\u274c"):
                result["sections"]["email_error"] = email_summary
    except Exception as e:
        logger.warning(f"Email triage not available: {e}")
        result["sections"]["email_error"] = "\u274c Email not reachable"

    # === Calendar-worthy emails ===
    try:
        from familiar.core.tool_executor import _triage_ctx_var as _triage_ctx_br

        _tc = _triage_ctx_br.get()
        if _tc and isinstance(_tc, dict):
            cal_emails = [e for e in _tc.get("emails", []) if e.get("has_calendar_event")]
            if cal_emails:
                refs = ", ".join(f"#{e.get('index', '?')}" for e in cal_emails[:5])
                result["sections"]["calendar_emails"] = (
                    f"{len(cal_emails)} email(s) with calendar events: {refs}"
                )
    except Exception as e:
        logger.debug(f"Calendar email check in briefing: {e}")

    # === Weather Forecast (all job classes) ===
    try:
        from familiar.skills.weather.skill import get_briefing_forecast

        forecasts = get_briefing_forecast()
        if forecasts:
            result["sections"]["weather"] = forecasts
    except Exception as e:
        logger.debug(f"Weather forecast not available: {e}")

    # === News Brief (all job classes) ===
    try:
        from familiar.skills.media.news import get_news_brief

        news = get_news_brief(max_top=5, max_per_topic=2)
        if news.get("top") or news.get("topics"):
            result["sections"]["news"] = news
    except Exception as e:
        logger.debug(f"News brief not available: {e}")

    # === Podcast New Episodes (all job classes) ===
    try:
        from familiar.skills.media.favorites import check_new_episodes

        new_eps = check_new_episodes()
        if new_eps:
            result["sections"]["podcasts"] = new_eps
    except Exception as e:
        logger.debug(f"Podcast check not available: {e}")

    # === Job-class specific sections ===

    # Nonprofit Director — grants + donors
    if job_class_key in ("nonprofit_director", ""):
        try:
            from familiar.skills.nonprofit.skill import grant_deadlines

            grants = grant_deadlines({"days": 14})
            if grants and "No grant deadlines" not in grants:
                result["sections"]["grants"] = grants
        except Exception as e:
            logger.warning(f"Nonprofit skill not available: {e}")

        try:
            from familiar.skills.nonprofit.skill import donors_needing_thanks

            thanks = donors_needing_thanks({"days": 7})
            if thanks and "All recent donors" not in thanks:
                result["sections"]["donors"] = thanks
        except Exception as e:
            logger.warning(f"Donor tracking not available: {e}")

    # Chef — inventory low-stock + today's menu
    if job_class_key == "chef":
        try:
            from familiar.skills.chef.skill import get_daily_prep

            prep = get_daily_prep({})
            if prep:
                result["sections"]["prep"] = prep
        except Exception as e:
            logger.debug(f"Chef prep not available: {e}")

    # Artist — upcoming commissions + show deadlines
    if job_class_key == "artist":
        try:
            from familiar.skills.artist.skill import get_upcoming_deadlines

            deadlines = get_upcoming_deadlines({"days": 14})
            if deadlines:
                result["sections"]["deadlines"] = deadlines
        except Exception as e:
            logger.debug(f"Artist deadlines not available: {e}")

    # Social Worker — flagged cases
    if job_class_key == "social_worker":
        try:
            from familiar.skills.social_worker.skill import get_flagged_cases

            cases = get_flagged_cases({})
            if cases:
                result["sections"]["cases"] = cases
        except Exception as e:
            logger.debug(f"Social worker cases not available: {e}")

    # Business Buddy — overdue tasks + today's revenue snapshot (if available)
    if job_class_key == "business_buddy":
        try:
            from familiar.skills.business.skill import get_daily_snapshot

            snapshot = get_daily_snapshot({})
            if snapshot:
                result["sections"]["snapshot"] = snapshot
        except Exception as e:
            logger.debug(f"Business snapshot not available: {e}")

    return result


def set_briefing_time(data: dict) -> str:
    """Set the daily briefing time."""
    time_str = data.get("time", "08:00")
    enabled = data.get("enabled", True)

    # Validate time format
    try:
        datetime.strptime(time_str, "%H:%M")
    except ValueError:
        return "Invalid time format. Use HH:MM (e.g., 08:00, 14:30)"

    config = _load_config()
    config["briefing_time"] = time_str
    config["briefing_enabled"] = enabled
    _save_config(config)

    if enabled:
        return f"✅ Daily briefing set for {time_str}"
    else:
        return "✅ Daily briefing disabled"


def set_deadline_alerts(data: dict) -> str:
    """Configure deadline alert settings."""
    days = data.get("days_before", 3)

    config = _load_config()
    config["deadline_alert_days"] = days
    _save_config(config)

    return f"✅ Will alert {days} days before deadlines"


def set_quiet_hours(data: dict) -> str:
    """Set quiet hours (no notifications)."""
    start = data.get("start", "21:00")
    end = data.get("end", "08:00")

    try:
        datetime.strptime(start, "%H:%M")
        datetime.strptime(end, "%H:%M")
    except ValueError:
        return "Invalid time format. Use HH:MM"

    config = _load_config()
    config["quiet_hours_start"] = start
    config["quiet_hours_end"] = end
    _save_config(config)

    return f"✅ Quiet hours: {start} - {end}"


def check_deadlines(data: dict) -> str:
    """Check for approaching deadlines and generate alerts."""
    config = _load_config()
    days = config.get("deadline_alert_days", 3)

    alerts = []
    today = datetime.now().date()
    cutoff = today + timedelta(days=days)

    # Check tasks
    try:
        tasks_file = DATA_DIR / "tasks.json"
        if tasks_file.exists():
            tasks = json.loads(tasks_file.read_text())
            for task in tasks:
                if task.get("completed"):
                    continue
                if task.get("due_date"):
                    try:
                        due = datetime.fromisoformat(task["due_date"]).date()
                        if due <= cutoff:
                            days_left = (due - today).days
                            if days_left < 0:
                                alerts.append(f"🔴 OVERDUE: {task['title']}")
                            elif days_left == 0:
                                alerts.append(f"🟡 DUE TODAY: {task['title']}")
                            else:
                                alerts.append(f"⚠️ Due in {days_left}d: {task['title']}")
                    except (ValueError, KeyError) as e:
                        logger.debug(f"Invalid task date: {e}")
    except (json.JSONDecodeError, IOError) as e:
        logger.debug(f"Could not load tasks: {e}")

    # Check grants
    try:
        nonprofit_file = DATA_DIR / "nonprofit.json"
        if nonprofit_file.exists():
            data = json.loads(nonprofit_file.read_text())
            for grant in data.get("grants", []):
                for date_field in ["deadline", "report_due"]:
                    if grant.get(date_field):
                        try:
                            due = datetime.fromisoformat(grant[date_field]).date()
                            if due <= cutoff:
                                days_left = (due - today).days
                                field_name = "Application" if date_field == "deadline" else "Report"
                                if days_left < 0:
                                    alerts.append(f"🔴 OVERDUE: {grant['name']} {field_name}")
                                elif days_left <= 3:
                                    alerts.append(
                                        f"⚠️ {field_name} due in {days_left}d: {grant['name']}"
                                    )
                        except (ValueError, KeyError) as e:
                            logger.debug(f"Invalid grant date: {e}")
    except (json.JSONDecodeError, IOError) as e:
        logger.debug(f"Could not load nonprofit data: {e}")

    if not alerts:
        return f"✅ No deadlines in the next {days} days"

    return "⏰ Deadline Alerts:\n\n" + "\n".join(alerts)


def get_proactive_status(data: dict) -> str:
    """Get current proactive settings."""
    config = _load_config()

    lines = ["📋 Proactive Settings:\n"]

    briefing = "Enabled" if config.get("briefing_enabled") else "Disabled"
    lines.append(f"  Morning Briefing: {briefing} at {config.get('briefing_time', '08:00')}")
    lines.append(f"  Deadline Alerts: {config.get('deadline_alert_days', 3)} days before")
    lines.append(
        f"  Quiet Hours: {config.get('quiet_hours_start', '21:00')} - {config.get('quiet_hours_end', '08:00')}"
    )

    return "\n".join(lines)


# Function to be called by scheduler
def run_scheduled_briefing(task):
    """Called by scheduler to send morning briefing."""
    briefing = generate_briefing({})

    # This would be sent via the channel specified in the task
    # For now, just log it
    logger.info(f"Scheduled briefing:\n{briefing}")

    return briefing


def _add_nonprofit_intelligence(items: list, now: datetime):
    """Add nonprofit-specific proactive intelligence to heartbeat items.

    Checks donor touchpoints, grant deadlines, board meeting prep,
    and annual appeal season. Only called for nonprofit_director job class.
    """
    # Grant report deadlines (7/14/30 day lookahead)
    try:
        np_data_path = Path(os.path.expanduser("~/.familiar/data/nonprofit.json"))
        if np_data_path.exists():
            import json

            np_data = json.loads(np_data_path.read_text())

            # Grant report countdown
            grants = np_data.get("grants", [])
            for g in grants:
                report_due = g.get("report_due") or g.get("deadline")
                if not report_due:
                    continue
                try:
                    due_dt = datetime.fromisoformat(report_due)
                    days_until = (due_dt - now).days
                    funder = g.get("funder", "Unknown funder")
                    if 0 <= days_until <= 7:
                        items.append(
                            f"📊 Grant report for {funder} due in {days_until} day"
                            f"{'s' if days_until != 1 else ''}"
                        )
                    elif 7 < days_until <= 14:
                        items.append(f"📊 Grant report for {funder} due in {days_until} days")
                except (ValueError, TypeError):
                    pass

            # Major donor touchpoint gaps (90+ days since last interaction)
            donors = np_data.get("donors", [])
            lapsed_majors = []
            for d in donors:
                level = d.get("donor_level", "")
                if level not in ("major", "leadership"):
                    continue
                last_interaction = d.get("last_interaction_date")
                if not last_interaction:
                    continue
                try:
                    li_dt = datetime.fromisoformat(last_interaction)
                    gap_days = (now - li_dt).days
                    if gap_days >= 90:
                        name = d.get("name", "Unknown donor")
                        lapsed_majors.append((name, gap_days))
                except (ValueError, TypeError):
                    pass
            if lapsed_majors:
                lapsed_majors.sort(key=lambda x: -x[1])
                top = lapsed_majors[0]
                if len(lapsed_majors) == 1:
                    items.append(f"🤝 Major donor {top[0]} — {top[1]} days since last touchpoint")
                else:
                    items.append(
                        f"🤝 {len(lapsed_majors)} major donors need touchpoints"
                        f" (longest: {top[0]}, {top[1]} days)"
                    )
    except Exception as e:
        logger.debug(f"Nonprofit intelligence (data) error: {e}")

    # Board meeting prep (next 7 days)
    try:
        from familiar.skills.calendar.skill import calendar_today

        cal_result = calendar_today({})
        if isinstance(cal_result, str) and "board" in cal_result.lower():
            items.append("📋 Board meeting coming up — start board packet?")
    except Exception as e:
        logger.debug(f"Nonprofit intelligence (calendar) error: {e}")

    # Event milestones due soon / overdue
    try:
        from familiar.skills.events.skill import get_event_store

        ev_store = get_event_store()
        upcoming_ms = ev_store.get_upcoming_milestones(days=7)
        overdue_ms = [m for m in upcoming_ms if m.get("overdue")]
        due_soon = [m for m in upcoming_ms if not m.get("overdue")]
        if overdue_ms:
            items.append(
                f"⚠️ {len(overdue_ms)} overdue event milestone"
                f"{'s' if len(overdue_ms) != 1 else ''}"
                f" — {overdue_ms[0]['event_name']}: {overdue_ms[0]['milestone_name']}"
            )
        if due_soon:
            items.append(
                f"📅 {len(due_soon)} event milestone"
                f"{'s' if len(due_soon) != 1 else ''}"
                f" due this week"
            )
    except Exception as e:
        logger.debug(f"Nonprofit intelligence (events) error: {e}")

    # Upcoming legislative hearings
    try:
        from familiar.skills.advocacy.skill import get_bill_store

        bill_store = get_bill_store()
        hearings = bill_store.list_upcoming_hearings(days=7)
        if hearings:
            h = hearings[0]
            items.append(
                f"🏛️ Hearing in {h.get('hearing_date', 'TBD')}"
                f": {h['bill_number']} ({h.get('our_position', 'watch')})"
            )
            if len(hearings) > 1:
                items[-1] += f" + {len(hearings) - 1} more"
    except Exception as e:
        logger.debug(f"Nonprofit intelligence (advocacy) error: {e}")

    # Overdue stewardship actions
    try:
        from familiar.skills.nonprofit.stewardship import get_stewardship_store

        stew_store = get_stewardship_store()
        overdue_stew = stew_store.get_overdue()
        if overdue_stew:
            items.append(
                f"❗ {len(overdue_stew)} donor"
                f"{'s' if len(overdue_stew) != 1 else ''}"
                f" with overdue stewardship"
                f" — {overdue_stew[0]['donor_name']} needs follow-up"
            )
    except Exception as e:
        logger.debug(f"Nonprofit intelligence (stewardship) error: {e}")

    # Annual appeal season check (September-November prep window)
    if now.month in (9, 10, 11):
        try:
            np_data_path = Path(os.path.expanduser("~/.familiar/data/nonprofit.json"))
            if np_data_path.exists():
                import json

                np_data = json.loads(np_data_path.read_text())
                donors = np_data.get("donors", [])
                annual_donors = [
                    d
                    for d in donors
                    if d.get("giving_frequency") in ("annual", "one-time")
                    and d.get("donor_level") != "lapsed"
                ]
                if annual_donors:
                    items.append(
                        f"💌 Annual appeal season — {len(annual_donors)} donors may need letters"
                    )
        except Exception as e:
            logger.debug(f"Nonprofit intelligence (appeal) error: {e}")


def heartbeat_check(data: dict) -> str:
    """
    Heartbeat check-in — Article 2 + SILENCE_GUIDANCE.

    Runs every 4-6 hours during non-quiet-hours. Checks for actionable
    items only. If nothing to report, produces NOTHING — silence is service.

    Convergence Pipeline Week 1, Item 1B.
    """
    config = _load_config()

    # Respect quiet hours
    now = datetime.now()
    quiet_start = config.get("quiet_hours_start", "22:00")
    quiet_end = config.get("quiet_hours_end", "07:00")
    try:
        qs = datetime.strptime(quiet_start, "%H:%M").replace(
            year=now.year, month=now.month, day=now.day
        )
        qe = datetime.strptime(quiet_end, "%H:%M").replace(
            year=now.year, month=now.month, day=now.day
        )
        if qs > qe:  # Overnight quiet hours (e.g. 22:00 - 07:00)
            if now >= qs or now <= qe:
                logger.debug("Heartbeat: quiet hours — skipping")
                return ""
        elif qs <= now <= qe:
            logger.debug("Heartbeat: quiet hours — skipping")
            return ""
    except ValueError as e:
        logger.debug("Value conversion error suppressed: %s", e)
    # Check for actionable content
    items = []

    # Overdue tasks
    try:
        tasks_dir = Path(os.path.expanduser("~/.familiar/data/tasks"))
        if tasks_dir.exists():
            import json

            tasks_file = tasks_dir / "tasks.json"
            if tasks_file.exists():
                tasks = json.loads(tasks_file.read_text())
                overdue = [
                    t
                    for t in tasks
                    if t.get("due")
                    and t.get("status") != "done"
                    and datetime.fromisoformat(t["due"]) < now
                ]
                if overdue:
                    items.append(
                        f"📋 {len(overdue)} overdue task{'s' if len(overdue) != 1 else ''}"
                    )
    except Exception as e:
        logger.debug(f"Heartbeat task check error: {e}")

    # Calendar-worthy emails (only if recent triage data exists)
    try:
        from familiar.core.tool_executor import _triage_ctx_var as _triage_ctx_hb

        _tc = _triage_ctx_hb.get()
        if _tc and isinstance(_tc, dict):
            _emails = _tc.get("emails", [])
            cal_n = sum(1 for e in _emails if e.get("has_calendar_event"))
            if cal_n:
                items.append(
                    f"📅 {cal_n} email{'s' if cal_n != 1 else ''}"
                    " with calendar events not yet scheduled"
                )
    except Exception as e:
        logger.debug(f"Heartbeat calendar email check: {e}")

    # Pending calendar events from email (auto-detected, awaiting review)
    try:
        from familiar.skills.email.pending_events import get_pending_event_store

        pe_store = get_pending_event_store()
        pe_count = pe_store.count_pending()
        if pe_count:
            items.append(
                f"📅 {pe_count} meeting{'s' if pe_count != 1 else ''}"
                " detected in email — awaiting your review"
            )
    except Exception as e:
        logger.debug(f"Heartbeat pending events check: {e}")

    # Email action queue (nonprofit-specific follow-ups)
    try:
        from familiar.skills.email.action_queue import get_action_store

        aq_store = get_action_store()
        aq_summary = aq_store.summary_by_type()
        if aq_summary:
            parts = []
            type_labels = {
                "thank_you": "thank-you",
                "rsvp": "RSVP",
                "grant_report": "grant report",
                "board_prep": "board prep",
                "legislative_action": "advocacy",
            }
            for atype, count in aq_summary.items():
                label = type_labels.get(atype, atype.replace("_", " "))
                parts.append(f"{count} {label}")
            items.append(f"✉️ Emails needing action: {', '.join(parts)}")
    except Exception as e:
        logger.debug(f"Heartbeat action queue check: {e}")

    # Nonprofit intelligence (only for nonprofit_director job class)
    try:
        from familiar.jobs import get_active_job_class

        job_key = get_active_job_class()
    except Exception:
        job_key = None

    if job_key == "nonprofit_director":
        _add_nonprofit_intelligence(items, now)

    # Approaching deadlines (next 24h)
    deadline_info = check_deadlines({})
    if "approaching" in deadline_info.lower() or "due" in deadline_info.lower():
        # Only include if there's actually something, not "no deadlines"
        if "no " not in deadline_info.lower()[:20]:
            items.append("⏰ Deadlines approaching")

    # Gate: only speak when there's something worth saying
    if not items:
        logger.debug("Heartbeat: nothing actionable — staying silent")
        return ""

    # Build concise nudge
    lines = ["Quick check-in:\n"]
    for item in items:
        lines.append(f"  {item}")
    lines.append("\nWant me to help with any of these?")

    result = "\n".join(lines)
    logger.info(f"Heartbeat check-in: {len(items)} actionable items")
    return result


def generate_eod_summary(data: dict) -> str:
    """
    End-of-day summary — Article 2.

    "Here's what we got done today. Tomorrow: [upcoming items]."
    Uses audit trail and task completion to reconstruct the day.

    Convergence Pipeline Week 1, Item 1B.
    """
    now = datetime.now()
    today = now.strftime("%A, %B %d")

    lines = [f"📊 End of Day — {today}\n"]

    # Completed tasks today
    try:
        tasks_dir = Path(os.path.expanduser("~/.familiar/data/tasks"))
        if tasks_dir.exists():
            import json

            tasks_file = tasks_dir / "tasks.json"
            if tasks_file.exists():
                tasks = json.loads(tasks_file.read_text())
                completed = [
                    t
                    for t in tasks
                    if t.get("completed_at")
                    and datetime.fromisoformat(t["completed_at"]).date() == now.date()
                ]
                if completed:
                    lines.append(
                        f"✅ Completed: {len(completed)} task{'s' if len(completed) != 1 else ''}"
                    )
                    for t in completed[:5]:
                        lines.append(f"   • {t.get('title', 'Untitled')}")
    except Exception as e:
        logger.debug(f"EOD task summary error: {e}")

    # Interaction count today (constitution tracker integration pending)

    # Tomorrow preview
    lines.append("")
    tomorrow = (now + timedelta(days=1)).strftime("%A")
    lines.append(f"📅 Tomorrow ({tomorrow}):")

    # Calendar events tomorrow
    try:
        cal_dir = Path(os.path.expanduser("~/.familiar/data/calendar"))
        if cal_dir.exists():
            import json

            events_file = cal_dir / "events.json"
            if events_file.exists():
                events = json.loads(events_file.read_text())
                tomorrow_date = (now + timedelta(days=1)).date()
                tomorrow_events = [
                    e
                    for e in events
                    if datetime.fromisoformat(e.get("start", "")).date() == tomorrow_date
                ]
                if tomorrow_events:
                    for e in tomorrow_events[:5]:
                        time_str = datetime.fromisoformat(e["start"]).strftime("%I:%M %p")
                        lines.append(f"   {time_str} — {e.get('title', 'Event')}")
                else:
                    lines.append("   No events scheduled")
    except Exception as e:
        lines.append("   Calendar not connected")
        logger.debug(f"EOD calendar error: {e}")

    # Pending tasks
    try:
        tasks_dir = Path(os.path.expanduser("~/.familiar/data/tasks"))
        if tasks_dir.exists():
            import json

            tasks_file = tasks_dir / "tasks.json"
            if tasks_file.exists():
                tasks = json.loads(tasks_file.read_text())
                pending = [t for t in tasks if t.get("status") != "done"]
                if pending:
                    lines.append(
                        f"\n📋 {len(pending)} task{'s' if len(pending) != 1 else ''} still open"
                    )
    except Exception as e:
        logger.debug("Error suppressed: %s", e)
    lines.append("\nHave a good evening. 🌙")
    return "\n".join(lines)


def run_heartbeat(task):
    """Called by scheduler for heartbeat check-ins."""
    result = heartbeat_check({})
    if result:
        logger.info(f"Heartbeat:\n{result}")
    return result


def run_eod_summary(task):
    """Called by scheduler for end-of-day summary."""
    return generate_eod_summary({})


def _weekly_capability_digest(data: dict) -> str:
    """
    Weekly capability tip — Article 4 (Hot Dog Principle).

    Surfaces one unused skill the user hasn't tried yet.
    Convergence Pipeline Week 2, Item 2A.
    """
    try:
        from familiar.core.progressive import generate_weekly_digest

        # We need the skill_loader — try to get it from the agent
        # If not available, generate a simpler message
        result = generate_weekly_digest(None)
        if result:
            return result
        return "All your available skills are active and well-used!"
    except ImportError:
        return "Weekly digest not available — progressive module not loaded."


def run_weekly_digest(task):
    """Called by scheduler for weekly capability digest."""
    return _weekly_capability_digest({})


# Tool definitions
# ── Inbox monitor tool handlers ───────────────────────────────────────────────


def watch_inbox(data: dict) -> str:
    """Enable the background inbox monitor and create/update its scheduler task."""
    from familiar.core.scheduler import TaskFrequency, TaskType, get_scheduler
    from familiar.skills.email.monitor import get_monitor_config, save_monitor_config

    interval = int(data.get("interval_minutes", 10))
    alert_on = data.get("alert_on", ["urgent"])
    channel = data.get("channel", "")
    chat_id = data.get("chat_id", "")

    config = get_monitor_config()
    config["enabled"] = True
    config["interval_minutes"] = interval
    config["alert_on"] = alert_on

    # Remove the old task if it exists
    scheduler = get_scheduler()
    old_task_id = config.get("task_id")
    if old_task_id:
        scheduler.remove_task(old_task_id)

    # Create a new MINUTELY-class task with custom interval
    task_id = scheduler.add_task(
        name="inbox_watch",
        description=f"Check for priority emails every {interval} min",
        frequency=TaskFrequency.MINUTELY,
        task_type=TaskType.INBOX_WATCH,
        channel=channel or None,
        chat_id=chat_id or None,
        interval_minutes=interval,
        payload={"alert_on": alert_on},
    )
    config["task_id"] = task_id
    save_monitor_config(config)

    alert_desc = " + ".join(alert_on)
    return (
        f"✅ Inbox monitor active — checking every {interval} min, "
        f"alerting on: {alert_desc}.\n"
        f"Use `add_priority_contact` to watch specific senders."
    )


def stop_watching_inbox(data: dict) -> str:
    """Pause the inbox monitor (preserves configuration)."""
    from familiar.core.scheduler import get_scheduler
    from familiar.skills.email.monitor import get_monitor_config, save_monitor_config

    config = get_monitor_config()
    config["enabled"] = False

    task_id = config.get("task_id")
    if task_id:
        scheduler = get_scheduler()
        scheduler.disable_task(task_id)

    save_monitor_config(config)
    return "⏸️ Inbox monitor paused. Run `watch_inbox` to re-enable."


def watch_inbox_status(data: dict) -> str:
    """Return current inbox monitor configuration."""
    from familiar.skills.email.monitor import get_monitor_config

    config = get_monitor_config()
    enabled = config.get("enabled", False)
    interval = config.get("interval_minutes", 10)
    alert_on = config.get("alert_on", ["urgent"])
    senders = config.get("watch_senders", [])
    topics = config.get("watch_topics", [])
    last_check = config.get("last_check_time", "never")
    quiet_start = config.get("quiet_hours_start", "22:00")
    quiet_end = config.get("quiet_hours_end", "07:00")

    status = "🟢 Active" if enabled else "⏸️ Paused"
    lines = [
        f"**Inbox Monitor — {status}**\n",
        f"  Poll interval : every {interval} min",
        f"  Alert on      : {', '.join(alert_on)}",
        f"  Quiet hours   : {quiet_start} – {quiet_end}",
        f"  Last check    : {last_check}",
    ]
    if senders:
        lines.append(f"  Watched senders : {', '.join(senders)}")
    if topics:
        lines.append(f"  Watched topics  : {', '.join(topics)}")
    if not senders and not topics:
        lines.append("  No extra senders or topics watched yet")

    return "\n".join(lines)


def add_priority_contact(data: dict) -> str:
    """Add a sender email/domain to the inbox monitor watch list."""
    from familiar.skills.email.monitor import get_monitor_config, save_monitor_config

    sender = data.get("sender", "").strip().lower()
    if not sender:
        return "❌ Please provide an email address or domain."

    config = get_monitor_config()
    senders = config.setdefault("watch_senders", [])
    if sender in [s.lower() for s in senders]:
        return f"ℹ️ Already watching {sender}."

    senders.append(sender)
    save_monitor_config(config)
    return f"✅ Will now alert on any email from {sender}."


def remove_priority_contact(data: dict) -> str:
    """Remove a sender email/domain from the inbox monitor watch list."""
    from familiar.skills.email.monitor import get_monitor_config, save_monitor_config

    sender = data.get("sender", "").strip().lower()
    if not sender:
        return "❌ Please provide an email address or domain."

    config = get_monitor_config()
    senders = config.get("watch_senders", [])
    before = len(senders)
    config["watch_senders"] = [s for s in senders if s.lower() != sender]
    save_monitor_config(config)

    if len(config["watch_senders"]) < before:
        return f"✅ Removed {sender} from watch list."
    return f"ℹ️ {sender} was not in the watch list."


def snooze_email(data: dict) -> str:
    """Create a follow-up reminder for an email."""
    from familiar.core.scheduler import TaskFrequency, TaskType, get_scheduler

    subject = data.get("subject", "").strip()
    hours = float(data.get("hours", 24))
    if not subject:
        return "❌ Please specify the email subject or triage index to snooze."

    minutes = int(hours * 60)
    message = f"⏰ Follow-up reminder: {subject}"

    scheduler = get_scheduler()
    scheduler.add_task(
        name=f"snooze: {subject[:40]}",
        description=message,
        frequency=TaskFrequency.ONCE,
        task_type=TaskType.REMINDER,
        interval_minutes=minutes,
        payload={"message": message},
    )

    hours_str = f"{int(hours)}h" if hours == int(hours) else f"{hours}h"
    return f"⏰ Reminder set for {hours_str} from now: {subject}"


# ── Pending events & action queue tools ────────────────────────────────────────


def review_pending_events(data: dict) -> str:
    """Show calendar events detected in emails awaiting user review."""
    from familiar.skills.email.pending_events import get_pending_event_store

    store = get_pending_event_store()
    pending = store.get_pending()
    if not pending:
        return "No pending calendar events from email."

    lines = [f"📅 {len(pending)} meeting{'s' if len(pending) != 1 else ''} detected in email:\n"]
    for i, ev in enumerate(pending, 1):
        conflict_str = f" ⚠️ Conflict: {ev['conflict']}" if ev.get("conflict") else ""
        rsvp_str = " (RSVP requested)" if ev.get("has_rsvp") else ""
        location_str = f"\n     📍 {ev['location']}" if ev.get("location") else ""
        lines.append(
            f"  {i}. {ev['title']} — {ev['start']}{rsvp_str}{conflict_str}"
            f"\n     From: {ev['email_from']}"
            f"{location_str}"
        )
    lines.append("\nUse confirm_pending_event or dismiss_pending_event with the event number.")
    return "\n".join(lines)


def confirm_pending_event(data: dict) -> str:
    """Confirm a pending event and add it to your calendar."""
    from familiar.core.confirmations import calendar_create_preview, needs_confirmation
    from familiar.skills.email.pending_events import get_pending_event_store

    store = get_pending_event_store()
    index = data.get("index") or data.get("event_number")
    event_id = data.get("event_id")

    ev = None
    if event_id:
        ev = store.get_event(event_id)
    elif index:
        ev = store.get_by_index(int(index))

    if not ev:
        return "❌ Event not found. Use review_pending_events to see available events."

    cal_data = {
        "title": ev["title"],
        "start": ev["start"],
        "duration": ev["duration"],
        "description": f"From email: {ev['email_from']}\nSubject: {ev['email_subject']}",
        "attendees": [],
        "_pending_event_id": ev["id"],
    }
    if ev.get("location"):
        cal_data["location"] = ev["location"]

    preview = calendar_create_preview(
        cal_data["title"],
        cal_data["start"],
        cal_data["duration"],
        [],
    )
    preview += f"\n  📧 From: {ev['email_from']}"
    if ev.get("location"):
        preview += f"\n  📍 {ev['location']}"
    if ev.get("has_rsvp"):
        preview += "\n  ✉️ RSVP requested"

    # Mark as confirmed in the store
    store.mark_confirmed(ev["id"])

    return needs_confirmation("create_event", cal_data, preview, risk="medium")


def dismiss_pending_event(data: dict) -> str:
    """Dismiss a pending event (don't add to calendar)."""
    from familiar.skills.email.pending_events import get_pending_event_store

    store = get_pending_event_store()
    index = data.get("index") or data.get("event_number")
    event_id = data.get("event_id")

    ev = None
    if event_id:
        ev = store.get_event(event_id)
    elif index:
        ev = store.get_by_index(int(index))

    if not ev:
        return "❌ Event not found. Use review_pending_events to see available events."

    store.mark_dismissed(ev["id"])
    return f"Dismissed: {ev['title']} — {ev['start']}"


def review_action_queue(data: dict) -> str:
    """Show email action items needing follow-up."""
    from familiar.skills.email.action_queue import ACTION_TYPES, get_action_store

    store = get_action_store()
    pending = store.get_pending()
    if not pending:
        return "No pending email actions."

    type_emoji = {
        "thank_you": "💝",
        "rsvp": "✉️",
        "grant_report": "📊",
        "board_prep": "📋",
        "legislative_action": "⚖️",
        "info_request": "📨",
        "delegate": "👥",
        "deadline": "⏰",
        "draft_acknowledgment": "📝",
    }

    lines = [f"✉️ {len(pending)} email action{'s' if len(pending) != 1 else ''} pending:\n"]
    for i, a in enumerate(pending, 1):
        emoji = type_emoji.get(a["action_type"], "📧")
        label = ACTION_TYPES.get(a["action_type"], a["action_type"])
        lines.append(
            f"  {i}. {emoji} {label}"
            f"\n     Subject: {a['email_subject'][:60]}"
            f"\n     From: {a['email_from']}"
        )
        if a.get("suggested_response"):
            lines.append(f"     → {a['suggested_response']}")
        if a.get("deadline"):
            lines.append(f"     ⏰ Deadline: {a['deadline']}")
    lines.append("\nUse complete_action or delegate_action with the action number.")
    return "\n".join(lines)


def complete_action(data: dict) -> str:
    """Mark an email action as completed."""
    from familiar.skills.email.action_queue import get_action_store

    store = get_action_store()
    index = data.get("index") or data.get("action_number")
    action_id = data.get("action_id")

    action = None
    if action_id:
        for a in store.get_pending():
            if a["id"] == action_id:
                action = a
                break
    elif index:
        action = store.get_by_index(int(index))

    if not action:
        return "❌ Action not found. Use review_action_queue to see pending actions."

    store.mark_complete(action["id"])
    return f"✅ Completed: {action['email_subject'][:60]}"


def delegate_action(data: dict) -> str:
    """Delegate an email action to a staff member."""
    from familiar.skills.email.action_queue import get_action_store

    store = get_action_store()
    index = data.get("index") or data.get("action_number")
    action_id = data.get("action_id")
    delegate_to = data.get("delegate_to", "").strip()

    if not delegate_to:
        return "❌ Please specify who to delegate to (name or email)."

    action = None
    if action_id:
        for a in store.get_pending():
            if a["id"] == action_id:
                action = a
                break
    elif index:
        action = store.get_by_index(int(index))

    if not action:
        return "❌ Action not found. Use review_action_queue to see pending actions."

    store.mark_delegated(action["id"], delegate_to)
    return f"👥 Delegated to {delegate_to}: {action['email_subject'][:60]}"


TOOLS = [
    {
        "name": "daily_briefing",
        "description": (
            "Generate a comprehensive daily briefing combining calendar events, tasks, email summary, grant deadlines, and donor follow-ups. "
            "Pulls data from calendar, tasks, triage, and nonprofit skills when available. "
            "Use this for a morning overview or to catch up on the day's agenda. "
            "Returns a formatted summary with all actionable items."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": generate_briefing,
        "category": "proactive",
    },
    {
        "name": "set_briefing_time",
        "description": (
            "Set the time for the automatic daily briefing and enable or disable it. "
            "The briefing is delivered by the scheduler at the configured time each day. "
            "Use HH:MM format (24-hour) for the time parameter. "
            "Set enabled to false to pause briefings without losing the configured time."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "time": {
                    "type": "string",
                    "description": "Time in HH:MM format (e.g., 08:00)",
                    "default": "08:00",
                },
                "enabled": {
                    "type": "boolean",
                    "default": True,
                    "description": "Whether to enable the daily briefing",
                },
            },
        },
        "handler": set_briefing_time,
        "category": "proactive",
    },
    {
        "name": "set_deadline_alerts",
        "description": (
            "Configure how many days before a deadline to trigger alerts for tasks and grants. "
            "The alert window applies to both task due dates and grant application deadlines. "
            "Alerts are surfaced in the daily briefing and heartbeat check-ins."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "days_before": {
                    "type": "integer",
                    "description": "Days before deadline to alert",
                    "default": 3,
                }
            },
        },
        "handler": set_deadline_alerts,
        "category": "proactive",
    },
    {
        "name": "set_quiet_hours",
        "description": (
            "Set quiet hours during which no proactive notifications, briefings, or heartbeat check-ins will be sent. "
            "Supports overnight ranges where start is later than end (e.g. 21:00 to 08:00). "
            "Use HH:MM format (24-hour) for both start and end times."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "start": {
                    "type": "string",
                    "description": "Start of quiet hours (HH:MM)",
                    "default": "21:00",
                },
                "end": {
                    "type": "string",
                    "description": "End of quiet hours (HH:MM)",
                    "default": "08:00",
                },
            },
        },
        "handler": set_quiet_hours,
        "category": "proactive",
    },
    {
        "name": "check_deadlines",
        "description": (
            "Check for approaching deadlines across tasks and grants and generate prioritized alerts. "
            "Flags overdue items, items due today, and items due within the configured alert window. "
            "Use this for an on-demand deadline check outside of scheduled briefings."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": check_deadlines,
        "category": "proactive",
    },
    {
        "name": "proactive_status",
        "description": (
            "Get the current proactive notification settings including briefing time, deadline alert window, and quiet hours. "
            "Shows whether each feature is enabled or disabled. "
            "Use this to review settings before modifying them."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": get_proactive_status,
        "category": "proactive",
    },
    {
        "name": "heartbeat_check",
        "description": (
            "Periodic heartbeat check for overdue tasks, urgent items, and approaching deadlines. "
            "Respects quiet hours and only produces output when there is something actionable to report. "
            "Silence is intentional -- no news means no action needed. "
            "Designed to run every 4-6 hours via the scheduler."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": heartbeat_check,
        "category": "proactive",
    },
    {
        "name": "end_of_day_summary",
        "description": (
            "Generate an end-of-day summary showing tasks completed today, tomorrow's calendar events, and remaining open items. "
            "Reconstructs the day's accomplishments from task completion timestamps. "
            "Use this for a daily wrap-up or to prepare for the next day."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": generate_eod_summary,
        "category": "proactive",
    },
    {
        "name": "weekly_capability_digest",
        "description": (
            "Generate a weekly tip highlighting an unused skill or capability the user has not tried yet. "
            "Follows the Hot Dog Principle (Article 4): surface features naturally rather than overwhelming with options. "
            "Designed to run weekly via the scheduler to gradually introduce new capabilities."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": _weekly_capability_digest,
        "category": "proactive",
    },
    {
        "name": "watch_inbox",
        "description": (
            "Enable the background inbox monitor to receive push alerts when priority emails arrive. "
            "Polls Gmail or IMAP on a configurable interval and notifies you immediately when urgent "
            "emails or emails from watched senders/topics arrive — no need to manually triage. "
            "Set alert_on to ['urgent'] for only urgent emails, or ['urgent', 'action'] for action items too. "
            "Quiet hours are respected automatically. "
            "Use add_priority_contact to add specific senders that always trigger alerts."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "interval_minutes": {
                    "type": "integer",
                    "description": "How often to check for new emails (default: 10)",
                    "default": 10,
                },
                "alert_on": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "Priority levels to alert on: 'urgent', 'action' (default: ['urgent'])",
                },
                "channel": {
                    "type": "string",
                    "description": "Notification channel (telegram, discord, slack, etc.)",
                    "default": "",
                },
                "chat_id": {
                    "type": "string",
                    "description": "Channel-specific chat/user ID for delivery",
                    "default": "",
                },
            },
        },
        "handler": watch_inbox,
        "category": "proactive",
    },
    {
        "name": "stop_watching_inbox",
        "description": (
            "Pause the background inbox monitor without deleting your configuration. "
            "Use watch_inbox to re-enable it."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": stop_watching_inbox,
        "category": "proactive",
    },
    {
        "name": "watch_inbox_status",
        "description": (
            "Show the current inbox monitor configuration: whether it's enabled, "
            "poll interval, alert criteria, watched senders/topics, and when the next check runs."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": watch_inbox_status,
        "category": "proactive",
    },
    {
        "name": "add_priority_contact",
        "description": (
            "Add an email address or domain to the inbox monitor's watch list so you always "
            "receive an immediate alert when they email you, regardless of priority classification. "
            "Examples: 'editor@publisher.com' or 'publisher.com' for any sender from that domain."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "sender": {
                    "type": "string",
                    "description": "Email address or domain to watch (e.g. 'jane@co.com' or 'co.com')",
                }
            },
            "required": ["sender"],
        },
        "handler": add_priority_contact,
        "category": "proactive",
    },
    {
        "name": "remove_priority_contact",
        "description": "Remove an email address or domain from the inbox monitor's watch list.",
        "input_schema": {
            "type": "object",
            "properties": {
                "sender": {
                    "type": "string",
                    "description": "Email address or domain to remove",
                }
            },
            "required": ["sender"],
        },
        "handler": remove_priority_contact,
        "category": "proactive",
    },
    {
        "name": "snooze_email",
        "description": (
            "Set a follow-up reminder for an email you don't have time to handle right now. "
            "The agent will remind you about it after the specified number of hours. "
            "Use the triage index (e.g. '#3') or describe the email subject."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "subject": {
                    "type": "string",
                    "description": "Email subject or triage index (e.g. '#3') to snooze",
                },
                "hours": {
                    "type": "number",
                    "description": "Hours until reminder fires (default: 24)",
                    "default": 24,
                },
            },
            "required": ["subject"],
        },
        "handler": snooze_email,
        "category": "proactive",
    },
    {
        "name": "review_pending_events",
        "description": (
            "Show calendar events automatically detected in your emails that are "
            "awaiting your review. The inbox monitor scans for meeting invitations, "
            "event notices, and scheduling requests. Review them here, then confirm "
            "or dismiss each one."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": review_pending_events,
        "category": "proactive",
    },
    {
        "name": "confirm_pending_event",
        "description": (
            "Confirm a pending event detected in email and add it to your calendar. "
            "Use the event number from review_pending_events."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "index": {
                    "type": "integer",
                    "description": "Event number from the pending events list",
                },
                "event_id": {
                    "type": "string",
                    "description": "Event ID (alternative to index)",
                },
            },
        },
        "handler": confirm_pending_event,
        "category": "proactive",
    },
    {
        "name": "dismiss_pending_event",
        "description": (
            "Dismiss a pending event — don't add it to your calendar. "
            "Use the event number from review_pending_events."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "index": {
                    "type": "integer",
                    "description": "Event number from the pending events list",
                },
                "event_id": {
                    "type": "string",
                    "description": "Event ID (alternative to index)",
                },
            },
        },
        "handler": dismiss_pending_event,
        "category": "proactive",
    },
    {
        "name": "review_action_queue",
        "description": (
            "Show email action items needing follow-up: thank-you letters, RSVPs, "
            "grant report deadlines, board prep, advocacy action alerts, and more. "
            "These are automatically detected from your email triage."
        ),
        "input_schema": {"type": "object", "properties": {}},
        "handler": review_action_queue,
        "category": "proactive",
    },
    {
        "name": "complete_action",
        "description": (
            "Mark an email action as completed. Use the action number from review_action_queue."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "index": {
                    "type": "integer",
                    "description": "Action number from the action queue",
                },
                "action_id": {
                    "type": "string",
                    "description": "Action ID (alternative to index)",
                },
            },
        },
        "handler": complete_action,
        "category": "proactive",
    },
    {
        "name": "delegate_action",
        "description": (
            "Delegate an email action to a staff member. Use the action number "
            "from review_action_queue and specify who to delegate to."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "index": {
                    "type": "integer",
                    "description": "Action number from the action queue",
                },
                "action_id": {
                    "type": "string",
                    "description": "Action ID (alternative to index)",
                },
                "delegate_to": {
                    "type": "string",
                    "description": "Name or email of the staff member to delegate to",
                },
            },
            "required": ["delegate_to"],
        },
        "handler": delegate_action,
        "category": "proactive",
    },
]
