# tools/vault_graph_tools.py — Vault knowledge graph (neighbors, mentions, suggestions, subgraph).
from __future__ import annotations

import json

from langchain_core.tools import tool
from pydantic import BaseModel, Field

from tools._logging import _debug_log, _emit_input_status


class VaultGraphNeighborsInput(BaseModel):
    note_id: str = Field(description="Vault capture / note UUID.")
    kinds: str = Field(
        default="",
        description="Optional comma-separated link kinds to include (e.g. wikilink,suggested_similarity). Empty = all.",
    )
    limit: int = Field(default=40, ge=1, le=200)
    project_id: str = Field(default="", description="Optional project filter; empty uses the note's project.")


@tool(args_schema=VaultGraphNeighborsInput)
def vault_graph_neighbors(note_id: str, kinds: str = "", limit: int = 40, project_id: str = "") -> str:
    """List related Vault notes: wikilinks plus embedding-suggested edges (same data as the app Related panel)."""
    _debug_log("[TOOL] vault_graph_neighbors", "TOOL")
    _emit_input_status("Loading graph neighbors…")
    try:
        from chat_storage import list_graph_neighbors

        klist = [x.strip() for x in kinds.split(",") if x.strip()] if kinds else None
        rows = list_graph_neighbors(
            note_id.strip(),
            kinds=klist,
            limit=limit,
            project_id=project_id.strip() or None,
        )
        if not rows:
            return "No graph neighbors found for this note."
        return json.dumps(rows, ensure_ascii=False, indent=2)
    finally:
        _emit_input_status("")


class VaultGraphMentionsInput(BaseModel):
    note_id: str = Field(description="Target note UUID (find inbound mentions of this note's title).")
    limit: int = Field(default=30, ge=1, le=200)
    include_unlinked: bool = Field(default=False, description="If true, include plain-text title hits without a wikilink edge.")


@tool(args_schema=VaultGraphMentionsInput)
def vault_graph_linked_mentions(note_id: str, limit: int = 30, include_unlinked: bool = False) -> str:
    """Find other notes that mention this note (wikilink or optional plain-title matches) with short snippets."""
    _debug_log("[TOOL] vault_graph_linked_mentions", "TOOL")
    _emit_input_status("Scanning linked mentions…")
    try:
        from chat_storage import list_vault_graph_mentions

        result = list_vault_graph_mentions(
            note_id.strip(),
            limit=limit,
            scan_limit=400,
            include_unlinked=bool(include_unlinked),
        )
        if not result.get("ok"):
            return json.dumps(result, ensure_ascii=False)
        mentions = result.get("mentions") or []
        if not mentions:
            return f"No mentions found for «{result.get('target_title') or note_id}»."
        return json.dumps(result, ensure_ascii=False, indent=2)
    finally:
        _emit_input_status("")


class VaultGraphSuggestionsInput(BaseModel):
    project_id: str = Field(default="", description="Optional project scope.")
    limit: int = Field(default=50, ge=1, le=300)


@tool(args_schema=VaultGraphSuggestionsInput)
def vault_graph_list_suggestions(project_id: str = "", limit: int = 50) -> str:
    """List pending suggested similarity edges between Vault notes (awaiting confirm/reject)."""
    _debug_log("[TOOL] vault_graph_list_suggestions", "TOOL")
    _emit_input_status("Listing graph suggestions…")
    try:
        from chat_storage import list_vault_graph_suggestions

        rows = list_vault_graph_suggestions(
            project_id=project_id.strip() or None,
            limit=limit,
            offset=0,
        )
        if not rows:
            return "No pending vault graph suggestions."
        return json.dumps(rows, ensure_ascii=False, indent=2)
    finally:
        _emit_input_status("")


class VaultGraphRefreshInput(BaseModel):
    project_id: str = Field(default="", description="Optional project scope for proposal.")
    limit_notes: int = Field(default=60, ge=2, le=120, description="Max notes with embeddings to compare.")
    min_similarity: float = Field(default=0.68, ge=0.0, le=1.0)


@tool(args_schema=VaultGraphRefreshInput)
def vault_graph_refresh_suggestions(project_id: str = "", limit_notes: int = 60, min_similarity: float = 0.68) -> str:
    """Propose new similarity edges between Vault notes (embedding-based). May be rate-limited server-side."""
    _debug_log("[TOOL] vault_graph_refresh_suggestions", "TOOL")
    _emit_input_status("Refreshing similarity suggestions…")
    try:
        from chat_storage import propose_vault_similarity_edges

        result = propose_vault_similarity_edges(
            project_id=project_id.strip() or None,
            limit_notes=limit_notes,
            min_similarity=min_similarity,
            max_new_edges=200,
        )
        return json.dumps(result, ensure_ascii=False)
    finally:
        _emit_input_status("")


class VaultGraphEdgeIdInput(BaseModel):
    edge_id: str = Field(description="UUID of a vault_graph_edges row.")


@tool(args_schema=VaultGraphEdgeIdInput)
def vault_graph_confirm_edge(edge_id: str) -> str:
    """Confirm a suggested vault graph edge (promote to active)."""
    _debug_log("[TOOL] vault_graph_confirm_edge", "TOOL")
    _emit_input_status("Confirming edge…")
    try:
        from chat_storage import confirm_vault_graph_edge

        row = confirm_vault_graph_edge(edge_id.strip())
        if not row:
            return "Edge not found or not in suggested state."
        return json.dumps(row, ensure_ascii=False)
    finally:
        _emit_input_status("")


@tool(args_schema=VaultGraphEdgeIdInput)
def vault_graph_reject_edge(edge_id: str) -> str:
    """Reject a vault graph suggestion (tombstone; will not be re-proposed with same canonical pair)."""
    _debug_log("[TOOL] vault_graph_reject_edge", "TOOL")
    _emit_input_status("Rejecting edge…")
    try:
        from chat_storage import reject_vault_graph_edge

        row = reject_vault_graph_edge(edge_id.strip())
        if not row:
            return "Edge not found."
        return json.dumps(row, ensure_ascii=False)
    finally:
        _emit_input_status("")


class VaultSubgraphInput(BaseModel):
    note_id: str = Field(description="Focus note UUID.")
    depth: int = Field(default=2, ge=0, le=4)
    include_suggested: bool = Field(default=True)
    include_wikilinks: bool = Field(default=True, description="Include wikilink edges from note_links.")
    include_similarity: bool = Field(
        default=True,
        description="Include vault_graph_edges rows (similarity / suggested).",
    )


@tool(args_schema=VaultSubgraphInput)
def vault_graph_subgraph(
    note_id: str,
    depth: int = 2,
    include_suggested: bool = True,
    include_wikilinks: bool = True,
    include_similarity: bool = True,
) -> str:
    """Export nodes and edges around a note for visualization or analysis (wikilinks + graph edges)."""
    _debug_log("[TOOL] vault_graph_subgraph", "TOOL")
    _emit_input_status("Building subgraph…")
    try:
        from chat_storage import export_vault_subgraph

        result = export_vault_subgraph(
            note_id.strip(),
            depth=depth,
            project_id=None,
            include_suggested=bool(include_suggested),
            include_wikilinks=bool(include_wikilinks),
            include_similarity_edges=bool(include_similarity),
            max_nodes=150,
        )
        return json.dumps(result, ensure_ascii=False, indent=2)
    finally:
        _emit_input_status("")


class VaultInsightsInput(BaseModel):
    note_id: str = Field(description="Focus note UUID.")
    mode: str = Field(
        default="gaps",
        description="One of: gaps, trends, topics, concepts, topic_mix (embedding cluster mix over subgraph).",
    )
    depth: int = Field(default=2, ge=0, le=4)
    with_focus_context: bool = Field(
        default=False,
        description="If true, include focus note title + excerpt in the JSON (for grounded summaries).",
    )


@tool(args_schema=VaultInsightsInput)
def vault_graph_insights(note_id: str, mode: str = "gaps", depth: int = 2, with_focus_context: bool = False) -> str:
    """Lightweight structural insights on the Vault graph near a note (orphans, high-degree nodes, etc.)."""
    _debug_log("[TOOL] vault_graph_insights", "TOOL")
    _emit_input_status("Analyzing vault graph…")
    try:
        from chat_storage import vault_graph_insights as _insights

        result = _insights(
            note_id.strip(),
            mode=mode.strip().lower(),
            depth=depth,
            project_id=None,
            max_nodes=120,
            with_focus_context=bool(with_focus_context),
        )
        return json.dumps(result, ensure_ascii=False, indent=2)
    finally:
        _emit_input_status("")
