# 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

"""
Advocacy & Legislative Tracking Skill.

Built for nonprofits fighting ballot initiatives and tracking legislation.
Tracks bills, hearings, testimony, coalition partners, and action alerts.

Stores:
  ~/.familiar/data/advocacy.json
  ~/.familiar/data/advocacy_supporters.json
"""

import csv
import io
import json
import logging
import os
import secrets
import threading
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional

logger = logging.getLogger(__name__)

_lock = threading.Lock()


def _data_dir() -> Path:
    try:
        from familiar.core.paths import DATA_DIR

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


# ── Constants ─────────────────────────────────────────────────────────

BILL_STATUSES = [
    "introduced",
    "committee",
    "floor_vote",
    "passed_one",
    "passed_both",
    "signed",
    "vetoed",
    "dead",
]

POSITIONS = ["support", "oppose", "watch", "neutral"]

PRIORITIES = ["critical", "high", "medium", "low"]

JURISDICTIONS = ["WA", "federal", "local", "OR", "CA", "other"]

ALERT_TYPES = ["call_to_action", "hearing_notice", "vote_alert", "victory_report"]


# ── BillStore ─────────────────────────────────────────────────────────


class BillStore:
    """Thread-safe JSON store for tracked bills."""

    def __init__(self):
        self._path = _data_dir() / "advocacy.json"
        self._data: dict = {"bills": []}
        self.load()

    def load(self):
        with _lock:
            if self._path.exists():
                try:
                    self._data = json.loads(self._path.read_text())
                except (json.JSONDecodeError, OSError) as e:
                    logger.warning("Failed to load advocacy data: %s", e)
                    self._data = {"bills": []}
            else:
                self._data = {"bills": []}

    def save(self):
        with _lock:
            self._path.parent.mkdir(parents=True, exist_ok=True)
            tmp = self._path.with_suffix(".tmp")
            tmp.write_text(json.dumps(self._data, indent=2))
            tmp.replace(self._path)
            try:
                os.chmod(self._path, 0o600)
            except OSError:
                pass

    def track_bill(
        self,
        bill_number: str,
        title: str,
        jurisdiction: str = "WA",
        session: str = "",
        status: str = "introduced",
        our_position: str = "watch",
        priority: str = "medium",
        sponsor: str = "",
        committee: str = "",
        summary: str = "",
        tags: Optional[list] = None,
    ) -> Optional[str]:
        """Track a new bill. Returns bill_id or None if duplicate."""
        # Prevent duplicate bill numbers within same session
        for b in self._data["bills"]:
            if b["bill_number"] == bill_number and b.get("session") == session:
                return None

        bill_id = f"bill_{secrets.token_hex(6)}"
        now = datetime.now(timezone.utc).isoformat()

        self._data["bills"].append(
            {
                "id": bill_id,
                "bill_number": bill_number,
                "title": title,
                "summary": summary,
                "jurisdiction": jurisdiction if jurisdiction in JURISDICTIONS else "other",
                "session": session or str(datetime.now().year),
                "status": status if status in BILL_STATUSES else "introduced",
                "our_position": our_position if our_position in POSITIONS else "watch",
                "priority": priority if priority in PRIORITIES else "medium",
                "sponsor": sponsor,
                "committee": committee,
                "hearing_dates": [],
                "vote_history": [],
                "talking_points": [],
                "coalition_partners": [],
                "action_alerts_sent": [],
                "testimony_submitted": [],
                "related_bills": [],
                "tags": tags or [],
                "notes": "",
                "last_updated": now,
                "created_at": now,
            }
        )
        self.save()
        return bill_id

    def get_bill(self, bill_id: str) -> Optional[dict]:
        self.load()
        for b in self._data["bills"]:
            if b["id"] == bill_id:
                return b
        return None

    def delete_bill(self, bill_id: str) -> bool:
        """Remove a tracked bill. Returns True if found and deleted."""
        self.load()
        before = len(self._data["bills"])
        self._data["bills"] = [b for b in self._data["bills"] if b["id"] != bill_id]
        if len(self._data["bills"]) < before:
            self.save()
            return True
        return False

    def list_bills(
        self,
        position: Optional[str] = None,
        priority: Optional[str] = None,
        jurisdiction: Optional[str] = None,
        status: Optional[str] = None,
    ) -> list[dict]:
        self.load()
        bills = self._data["bills"]
        if position:
            bills = [b for b in bills if b.get("our_position") == position]
        if priority:
            bills = [b for b in bills if b.get("priority") == priority]
        if jurisdiction:
            bills = [b for b in bills if b.get("jurisdiction") == jurisdiction]
        if status:
            bills = [b for b in bills if b.get("status") == status]
        return bills

    def update_bill(self, bill_id: str, updates: dict) -> Optional[dict]:
        for b in self._data["bills"]:
            if b["id"] == bill_id:
                for k, v in updates.items():
                    if k not in ("id", "created_at"):
                        b[k] = v
                b["last_updated"] = datetime.now(timezone.utc).isoformat()
                self.save()
                return b
        return None

    def remove_bill(self, bill_id: str) -> bool:
        before = len(self._data["bills"])
        self._data["bills"] = [b for b in self._data["bills"] if b["id"] != bill_id]
        if len(self._data["bills"]) < before:
            self.save()
            return True
        return False

    def add_hearing(
        self,
        bill_id: str,
        date: str,
        time: str = "",
        location: str = "",
        hearing_type: str = "public",
    ) -> bool:
        for b in self._data["bills"]:
            if b["id"] == bill_id:
                b["hearing_dates"].append(
                    {
                        "date": date,
                        "time": time,
                        "location": location,
                        "type": hearing_type,
                    }
                )
                b["last_updated"] = datetime.now(timezone.utc).isoformat()
                self.save()
                return True
        return False

    def add_testimony(
        self,
        bill_id: str,
        testifier: str,
        position: str = "",
        format_type: str = "written",
        notes: str = "",
    ) -> bool:
        for b in self._data["bills"]:
            if b["id"] == bill_id:
                b["testimony_submitted"].append(
                    {
                        "date": datetime.now(timezone.utc).isoformat()[:10],
                        "testifier": testifier,
                        "position": position or b.get("our_position", ""),
                        "format": format_type,
                        "notes": notes,
                    }
                )
                b["last_updated"] = datetime.now(timezone.utc).isoformat()
                self.save()
                return True
        return False

    def add_coalition_partner(self, bill_id: str, org_name: str) -> bool:
        for b in self._data["bills"]:
            if b["id"] == bill_id:
                if org_name not in b["coalition_partners"]:
                    b["coalition_partners"].append(org_name)
                    self.save()
                return True
        return False

    def list_upcoming_hearings(self, days: int = 30) -> list[dict]:
        """Get hearings across all bills within N days."""
        self.load()
        today = datetime.now(timezone.utc).date()
        cutoff = today + timedelta(days=days)
        results = []
        for b in self._data["bills"]:
            if b.get("status") in ("signed", "vetoed", "dead"):
                continue
            for h in b.get("hearing_dates", []):
                try:
                    hdate = datetime.fromisoformat(h["date"]).date()
                    if today <= hdate <= cutoff:
                        results.append(
                            {
                                "bill_id": b["id"],
                                "bill_number": b["bill_number"],
                                "title": b["title"],
                                "our_position": b.get("our_position", ""),
                                "hearing_date": h["date"],
                                "hearing_time": h.get("time", ""),
                                "location": h.get("location", ""),
                                "hearing_type": h.get("type", ""),
                            }
                        )
                except (ValueError, KeyError):
                    continue
        results.sort(key=lambda h: h["hearing_date"])
        return results

    def record_action_alert(self, bill_id: str, alert_type: str, recipients_count: int = 0) -> bool:
        for b in self._data["bills"]:
            if b["id"] == bill_id:
                b["action_alerts_sent"].append(
                    {
                        "date": datetime.now(timezone.utc).isoformat()[:10],
                        "type": alert_type,
                        "recipients_count": recipients_count,
                    }
                )
                self.save()
                return True
        return False

    def count_active(self) -> int:
        self.load()
        return sum(
            1 for b in self._data["bills"] if b.get("status") not in ("signed", "vetoed", "dead")
        )

    def count_hearings(self, days: int = 14) -> int:
        return len(self.list_upcoming_hearings(days=days))


def get_bill_store() -> BillStore:
    return BillStore()


# ── SupporterStore ────────────────────────────────────────────────────


class SupporterStore:
    """Thread-safe JSON store for advocacy supporters."""

    def __init__(self):
        self._path = _data_dir() / "advocacy_supporters.json"
        self._data: dict = {"supporters": []}
        self.load()

    def load(self):
        with _lock:
            if self._path.exists():
                try:
                    self._data = json.loads(self._path.read_text())
                except (json.JSONDecodeError, OSError) as e:
                    logger.warning("Failed to load supporters: %s", e)
                    self._data = {"supporters": []}
            else:
                self._data = {"supporters": []}

    def save(self):
        with _lock:
            self._path.parent.mkdir(parents=True, exist_ok=True)
            tmp = self._path.with_suffix(".tmp")
            tmp.write_text(json.dumps(self._data, indent=2))
            tmp.replace(self._path)
            try:
                os.chmod(self._path, 0o600)
            except OSError:
                pass

    def add_supporter(
        self,
        name: str,
        email: str = "",
        phone: str = "",
        zip_code: str = "",
        legislative_district: str = "",
        tags: Optional[list] = None,
    ) -> Optional[str]:
        """Add a supporter. Returns ID or None if duplicate email."""
        if email:
            for s in self._data["supporters"]:
                if s.get("email", "").lower() == email.lower():
                    return None

        sid = f"sup_{secrets.token_hex(6)}"
        self._data["supporters"].append(
            {
                "id": sid,
                "name": name,
                "email": email,
                "phone": phone,
                "zip_code": zip_code,
                "legislative_district": legislative_district,
                "tags": tags or [],
                "active": True,
                "added_at": datetime.now(timezone.utc).isoformat(),
            }
        )
        self.save()
        return sid

    def import_csv(self, csv_text: str) -> tuple[int, list[str]]:
        """Import supporters from CSV text. Returns (count, errors)."""
        reader = csv.DictReader(io.StringIO(csv_text))
        imported = 0
        errors = []
        for i, row in enumerate(reader):
            name = (row.get("name") or row.get("Name") or "").strip()
            if not name:
                errors.append(f"Row {i + 1}: missing name")
                continue
            email = (row.get("email") or row.get("Email") or "").strip()
            result = self.add_supporter(
                name=name,
                email=email,
                phone=(row.get("phone") or row.get("Phone") or "").strip(),
                zip_code=(row.get("zip") or row.get("zip_code") or "").strip(),
                legislative_district=(
                    row.get("district") or row.get("legislative_district") or ""
                ).strip(),
            )
            if result:
                imported += 1
            elif email:
                errors.append(f"Row {i + 1}: duplicate email {email}")
        return imported, errors

    def list_supporters(self, active_only: bool = True) -> list[dict]:
        self.load()
        if active_only:
            return [s for s in self._data["supporters"] if s.get("active", True)]
        return self._data["supporters"]

    def remove_supporter(self, supporter_id: str) -> bool:
        for s in self._data["supporters"]:
            if s["id"] == supporter_id:
                s["active"] = False
                self.save()
                return True
        return False

    def count_active(self) -> int:
        self.load()
        return sum(1 for s in self._data["supporters"] if s.get("active", True))


def get_supporter_store() -> SupporterStore:
    return SupporterStore()


# ── Action Alert Drafting ─────────────────────────────────────────────


def draft_action_alert(bill: dict, alert_type: str) -> dict:
    """Generate an action alert for a bill. Returns {subject, body}."""
    num = bill.get("bill_number", "")
    title = bill.get("title", "")
    position = bill.get("our_position", "watch")
    pos_verb = "support" if position == "support" else "oppose"

    if alert_type == "call_to_action":
        subject = f"ACTION NEEDED: {pos_verb.title()} {num} — {title}"
        body = (
            f"We need your voice! {num} ({title}) is moving through the legislature "
            f"and we need you to contact your representatives to {pos_verb} this bill.\n\n"
        )
        if bill.get("talking_points"):
            body += "Key talking points:\n"
            for tp in bill["talking_points"]:
                body += f"- {tp}\n"
        body += f"\nOur position: {position.upper()}"

    elif alert_type == "hearing_notice":
        hearings = bill.get("hearing_dates", [])
        next_hearing = hearings[-1] if hearings else {}
        subject = f"HEARING: {num} — {next_hearing.get('date', 'TBD')}"
        body = (
            f"{num} ({title}) has a hearing scheduled.\n\n"
            f"Date: {next_hearing.get('date', 'TBD')}\n"
            f"Time: {next_hearing.get('time', 'TBD')}\n"
            f"Location: {next_hearing.get('location', 'TBD')}\n\n"
            f"We {pos_verb} this bill. Please consider submitting testimony."
        )

    elif alert_type == "vote_alert":
        subject = f"VOTE ALERT: {num} — Contact your representative NOW"
        body = (
            f"{num} ({title}) is coming to a vote. "
            f"Contact your representative NOW to {pos_verb} this bill.\n\n"
            f"Our position: {position.upper()}"
        )

    elif alert_type == "victory_report":
        subject = f"UPDATE: {num} — {title}"
        body = (
            f"Thank you for your advocacy! Here's an update on {num} ({title}).\n\n"
            f"Current status: {bill.get('status', 'unknown')}\n"
        )
        if bill.get("vote_history"):
            last_vote = bill["vote_history"][-1]
            body += (
                f"Last vote: {last_vote.get('result', '')} ({last_vote.get('vote_count', '')})\n"
            )

    else:
        subject = f"Update: {num} — {title}"
        body = f"An update regarding {num} ({title})."

    return {"subject": subject, "body": body}


# ── Tool Handlers ─────────────────────────────────────────────────────


def track_bill(params: dict) -> str:
    """Track a new bill."""
    bill_number = params.get("bill_number", "").strip()
    title = params.get("title", "").strip()
    if not bill_number or not title:
        return "Error: bill_number and title are required."

    store = get_bill_store()
    bill_id = store.track_bill(
        bill_number=bill_number,
        title=title,
        jurisdiction=params.get("jurisdiction", "WA"),
        session=params.get("session", ""),
        status=params.get("status", "introduced"),
        our_position=params.get("our_position", "watch"),
        priority=params.get("priority", "medium"),
        sponsor=params.get("sponsor", ""),
        committee=params.get("committee", ""),
        summary=params.get("summary", ""),
        tags=params.get("tags"),
    )
    if bill_id:
        return f"Now tracking {bill_number} — '{title}' ({bill_id})."
    return f"Bill {bill_number} is already being tracked."


def list_bills(params: dict) -> str:
    """List tracked bills with optional filters."""
    store = get_bill_store()
    bills = store.list_bills(
        position=params.get("position"),
        priority=params.get("priority"),
        jurisdiction=params.get("jurisdiction"),
        status=params.get("status"),
    )
    if not bills:
        return "No bills tracked."

    lines = [f"**{len(bills)} Tracked Bills:**\n"]
    for b in bills:
        hearings = len(b.get("hearing_dates", []))
        testimony = len(b.get("testimony_submitted", []))
        lines.append(
            f"- **{b['bill_number']}** {b['title']}"
            f" | {b['status']} | position: {b['our_position']}"
            f" | priority: {b['priority']}"
            f" | hearings: {hearings} | testimony: {testimony}"
        )
    return "\n".join(lines)


def bill_details(params: dict) -> str:
    """Show detailed bill info."""
    bill_id = params.get("bill_id", "")
    store = get_bill_store()
    b = store.get_bill(bill_id)
    if not b:
        return f"Bill '{bill_id}' not found."

    lines = [
        f"# {b['bill_number']} — {b['title']}",
        f"**Status:** {b['status']} | **Position:** {b['our_position']}"
        f" | **Priority:** {b['priority']}",
        f"**Jurisdiction:** {b['jurisdiction']} | **Session:** {b['session']}",
        f"**Sponsor:** {b.get('sponsor', 'Unknown')}"
        f" | **Committee:** {b.get('committee', 'Unknown')}",
    ]

    if b.get("summary"):
        lines.append(f"\n{b['summary']}")

    if b.get("talking_points"):
        lines.append("\n## Talking Points")
        for tp in b["talking_points"]:
            lines.append(f"- {tp}")

    hearings = b.get("hearing_dates", [])
    if hearings:
        lines.append(f"\n## Hearings ({len(hearings)})")
        for h in hearings:
            lines.append(
                f"- {h['date']} {h.get('time', '')} at {h.get('location', 'TBD')}"
                f" ({h.get('type', 'public')})"
            )

    testimony = b.get("testimony_submitted", [])
    if testimony:
        lines.append(f"\n## Testimony ({len(testimony)})")
        for t in testimony:
            lines.append(f"- {t['date']}: {t['testifier']} ({t['format']}) — {t['position']}")

    coalition = b.get("coalition_partners", [])
    if coalition:
        lines.append(f"\n## Coalition ({len(coalition)} orgs)")
        lines.append(", ".join(coalition))

    alerts = b.get("action_alerts_sent", [])
    if alerts:
        lines.append(f"\n## Action Alerts Sent ({len(alerts)})")
        for a in alerts:
            lines.append(f"- {a['date']}: {a['type']} to {a['recipients_count']} people")

    return "\n".join(lines)


def update_bill(params: dict) -> str:
    """Update a bill's fields."""
    bill_id = params.get("bill_id", "")
    if not bill_id:
        return "Error: bill_id is required."

    store = get_bill_store()
    updates = {k: v for k, v in params.items() if k != "bill_id"}
    result = store.update_bill(bill_id, updates)
    if result:
        return f"Updated {result['bill_number']}."
    return f"Bill '{bill_id}' not found."


def remove_bill(params: dict) -> str:
    """Stop tracking a bill."""
    bill_id = params.get("bill_id", "")
    store = get_bill_store()
    if store.remove_bill(bill_id):
        return f"Removed bill {bill_id}."
    return f"Bill '{bill_id}' not found."


def add_hearing(params: dict) -> str:
    """Add a hearing date to a bill."""
    bill_id = params.get("bill_id", "")
    date = params.get("date", "")
    if not bill_id or not date:
        return "Error: bill_id and date are required."

    store = get_bill_store()
    if store.add_hearing(
        bill_id,
        date,
        time=params.get("time", ""),
        location=params.get("location", ""),
        hearing_type=params.get("hearing_type", "public"),
    ):
        return f"Added hearing on {date} to bill {bill_id}."
    return f"Bill '{bill_id}' not found."


def add_testimony(params: dict) -> str:
    """Record testimony submitted for a bill."""
    bill_id = params.get("bill_id", "")
    testifier = params.get("testifier", "")
    if not bill_id or not testifier:
        return "Error: bill_id and testifier are required."

    store = get_bill_store()
    if store.add_testimony(
        bill_id,
        testifier,
        position=params.get("position", ""),
        format_type=params.get("format", "written"),
        notes=params.get("notes", ""),
    ):
        return f"Recorded testimony from {testifier} for bill {bill_id}."
    return f"Bill '{bill_id}' not found."


def add_coalition_partner(params: dict) -> str:
    """Add a coalition partner to a bill."""
    bill_id = params.get("bill_id", "")
    org_name = params.get("org_name", "").strip()
    if not bill_id or not org_name:
        return "Error: bill_id and org_name are required."

    store = get_bill_store()
    if store.add_coalition_partner(bill_id, org_name):
        return f"Added {org_name} as coalition partner on bill {bill_id}."
    return f"Bill '{bill_id}' not found."


def upcoming_hearings(params: dict) -> str:
    """List upcoming hearings across all tracked bills."""
    days = int(params.get("days", 30))
    store = get_bill_store()
    hearings = store.list_upcoming_hearings(days=days)
    if not hearings:
        return f"No hearings in the next {days} days."

    lines = [f"**{len(hearings)} Upcoming Hearings:**\n"]
    for h in hearings:
        lines.append(
            f"- **{h['hearing_date']}** {h.get('hearing_time', '')}"
            f" — {h['bill_number']}: {h['title']}"
            f" | position: {h['our_position']}"
            f" | location: {h.get('location', 'TBD')}"
        )
    return "\n".join(lines)


def draft_alert(params: dict) -> str:
    """Draft an action alert for a bill."""
    bill_id = params.get("bill_id", "")
    alert_type = params.get("alert_type", "call_to_action")

    store = get_bill_store()
    bill = store.get_bill(bill_id)
    if not bill:
        return f"Bill '{bill_id}' not found."

    if alert_type not in ALERT_TYPES:
        return f"Invalid alert type. Choose from: {', '.join(ALERT_TYPES)}"

    alert = draft_action_alert(bill, alert_type)
    return f"**Subject:** {alert['subject']}\n\n{alert['body']}"


def add_supporter(params: dict) -> str:
    """Add a supporter to the advocacy list."""
    name = params.get("name", "").strip()
    if not name:
        return "Error: name is required."

    store = get_supporter_store()
    sid = store.add_supporter(
        name=name,
        email=params.get("email", ""),
        phone=params.get("phone", ""),
        zip_code=params.get("zip_code", ""),
        legislative_district=params.get("legislative_district", ""),
        tags=params.get("tags"),
    )
    if sid:
        return f"Added supporter '{name}' ({sid})."
    return f"Supporter with email '{params.get('email', '')}' already exists."


def import_supporters(params: dict) -> str:
    """Import supporters from CSV data."""
    csv_text = params.get("csv_data", "")
    if not csv_text:
        return "Error: csv_data is required."

    store = get_supporter_store()
    imported, errors = store.import_csv(csv_text)
    result = f"Imported {imported} supporters."
    if errors:
        result += f" {len(errors)} errors: " + "; ".join(errors[:5])
    return result


def list_supporters_tool(params: dict) -> str:
    """List advocacy supporters."""
    store = get_supporter_store()
    supporters = store.list_supporters()
    if not supporters:
        return "No supporters in the list."

    lines = [f"**{len(supporters)} Active Supporters:**\n"]
    for s in supporters[:20]:  # Limit display
        district = f" (LD {s['legislative_district']})" if s.get("legislative_district") else ""
        lines.append(f"- {s['name']}{district} — {s.get('email', 'no email')}")
    if len(supporters) > 20:
        lines.append(f"... and {len(supporters) - 20} more")
    return "\n".join(lines)


def advocacy_dashboard(params: dict) -> str:
    """Overview of active legislative tracking."""
    bill_store = get_bill_store()
    supporter_store = get_supporter_store()

    bills = bill_store.list_bills()
    active = [b for b in bills if b.get("status") not in ("signed", "vetoed", "dead")]
    hearings = bill_store.list_upcoming_hearings(days=14)
    critical = [b for b in active if b.get("priority") == "critical"]
    supporters = supporter_store.count_active()

    lines = [
        "# Advocacy Dashboard\n",
        f"**Active Bills:** {len(active)} ({len(critical)} critical)",
        f"**Upcoming Hearings (14d):** {len(hearings)}",
        f"**Active Supporters:** {supporters}",
    ]

    if critical:
        lines.append("\n## Critical Bills")
        for b in critical:
            lines.append(
                f"- **{b['bill_number']}** {b['title']} — {b['status']} | {b['our_position']}"
            )

    if hearings:
        lines.append("\n## Next Hearings")
        for h in hearings[:5]:
            lines.append(f"- {h['hearing_date']}: {h['bill_number']} at {h.get('location', 'TBD')}")

    return "\n".join(lines)


# ── Tool Definitions ──────────────────────────────────────────────────

TOOLS = [
    {
        "name": "track_bill",
        "description": "Start tracking a legislative bill.",
        "input_schema": {
            "type": "object",
            "properties": {
                "bill_number": {"type": "string", "description": "e.g. HB 1234"},
                "title": {"type": "string"},
                "jurisdiction": {"type": "string", "enum": JURISDICTIONS},
                "status": {"type": "string", "enum": BILL_STATUSES},
                "our_position": {"type": "string", "enum": POSITIONS},
                "priority": {"type": "string", "enum": PRIORITIES},
                "sponsor": {"type": "string"},
                "committee": {"type": "string"},
                "summary": {"type": "string"},
            },
            "required": ["bill_number", "title"],
        },
        "handler": track_bill,
    },
    {
        "name": "list_bills",
        "description": "List tracked bills with optional filters.",
        "input_schema": {
            "type": "object",
            "properties": {
                "position": {"type": "string", "enum": POSITIONS},
                "priority": {"type": "string", "enum": PRIORITIES},
                "jurisdiction": {"type": "string", "enum": JURISDICTIONS},
                "status": {"type": "string", "enum": BILL_STATUSES},
            },
        },
        "handler": list_bills,
    },
    {
        "name": "bill_details",
        "description": "Show detailed info for a tracked bill.",
        "input_schema": {
            "type": "object",
            "properties": {"bill_id": {"type": "string"}},
            "required": ["bill_id"],
        },
        "handler": bill_details,
    },
    {
        "name": "update_bill",
        "description": "Update a bill's status, position, priority, or other fields.",
        "input_schema": {
            "type": "object",
            "properties": {
                "bill_id": {"type": "string"},
                "status": {"type": "string", "enum": BILL_STATUSES},
                "our_position": {"type": "string", "enum": POSITIONS},
                "priority": {"type": "string", "enum": PRIORITIES},
                "talking_points": {"type": "array", "items": {"type": "string"}},
                "notes": {"type": "string"},
            },
            "required": ["bill_id"],
        },
        "handler": update_bill,
    },
    {
        "name": "remove_bill",
        "description": "Stop tracking a bill.",
        "input_schema": {
            "type": "object",
            "properties": {"bill_id": {"type": "string"}},
            "required": ["bill_id"],
        },
        "handler": remove_bill,
    },
    {
        "name": "add_hearing",
        "description": "Add a hearing date to a tracked bill.",
        "input_schema": {
            "type": "object",
            "properties": {
                "bill_id": {"type": "string"},
                "date": {"type": "string", "description": "YYYY-MM-DD"},
                "time": {"type": "string"},
                "location": {"type": "string"},
                "hearing_type": {"type": "string"},
            },
            "required": ["bill_id", "date"],
        },
        "handler": add_hearing,
    },
    {
        "name": "add_testimony",
        "description": "Record testimony submitted for a bill.",
        "input_schema": {
            "type": "object",
            "properties": {
                "bill_id": {"type": "string"},
                "testifier": {"type": "string"},
                "position": {"type": "string", "enum": POSITIONS},
                "format": {"type": "string", "enum": ["written", "oral", "video"]},
                "notes": {"type": "string"},
            },
            "required": ["bill_id", "testifier"],
        },
        "handler": add_testimony,
    },
    {
        "name": "add_coalition_partner",
        "description": "Add a coalition partner org to a bill.",
        "input_schema": {
            "type": "object",
            "properties": {
                "bill_id": {"type": "string"},
                "org_name": {"type": "string"},
            },
            "required": ["bill_id", "org_name"],
        },
        "handler": add_coalition_partner,
    },
    {
        "name": "upcoming_hearings",
        "description": "List upcoming hearings across all tracked bills.",
        "input_schema": {
            "type": "object",
            "properties": {
                "days": {"type": "integer", "description": "Look-ahead days (default 30)"},
            },
        },
        "handler": upcoming_hearings,
    },
    {
        "name": "draft_action_alert",
        "description": (
            "Draft an action alert for supporters about a bill."
            " Types: call_to_action, hearing_notice, vote_alert, victory_report."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "bill_id": {"type": "string"},
                "alert_type": {"type": "string", "enum": ALERT_TYPES},
            },
            "required": ["bill_id"],
        },
        "handler": draft_alert,
    },
    {
        "name": "add_supporter",
        "description": "Add a supporter to the advocacy contact list.",
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "email": {"type": "string"},
                "phone": {"type": "string"},
                "zip_code": {"type": "string"},
                "legislative_district": {"type": "string"},
            },
            "required": ["name"],
        },
        "handler": add_supporter,
    },
    {
        "name": "import_supporters",
        "description": "Import supporters from CSV (columns: name, email, phone, zip, district).",
        "input_schema": {
            "type": "object",
            "properties": {
                "csv_data": {"type": "string", "description": "CSV text with header row"},
            },
            "required": ["csv_data"],
        },
        "handler": import_supporters,
    },
    {
        "name": "list_supporters",
        "description": "List advocacy supporters.",
        "input_schema": {"type": "object", "properties": {}},
        "handler": list_supporters_tool,
    },
    {
        "name": "advocacy_dashboard",
        "description": "Overview of legislative tracking — active bills, hearings, supporters.",
        "input_schema": {"type": "object", "properties": {}},
        "handler": advocacy_dashboard,
    },
]
