#!/usr/bin/env python3
"""Record session outcomes in the context graph.

Called during session wrap to create a session node with edges to issues
worked on and decisions produced. Supports cross-session continuity by
building the temporal layer that graph_context.py traverses.

Usage:
    python3 .claude/skills/flydocs-workflow/scripts/graph_session.py \
        --summary "Completed Phase 1, started Phase 2" \
        [--issue FLY-56] [--issue FLY-174] \
        [--decision 006] \
        [--date 2026-02-17] \
        [--root PATH]

Multiple --issue and --decision flags can be provided.
"""

import argparse
import re
import sys
from datetime import date
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
from graph_utils import (
    find_project_root,
    find_workspace_root,
    load_graph,
    save_graph,
    output_json,
    fail,
)


# Session nodes older than this get reduced weight in graph_context.py
DEFAULT_RETENTION_DAYS = 30


#: `2026-08-29` or `2026-08-29-2`. The shape of a session id, in the module
#: that creates the nodes — `session.py` imports it rather than re-declaring
#: it, because a second copy is how the record and the node start disagreeing.
SESSION_ID_PATTERN = re.compile(r"^(\d{4}-\d{2}-\d{2})(?:-(\d+))?$")


def resolve_session_node_id(graph, session_date, requested=None):
    """The node id to create: the caller's, or the next free one for the date.

    `--session-id` exists so the session *record* and the session *node* name
    the same session (FLY-1411). The record is composed before this script
    runs, so the id has to travel rather than be derived twice. A requested id
    that is somehow already taken falls back to the search below, and the
    caller is told which id was actually used.
    """
    if requested:
        if not SESSION_ID_PATTERN.match(requested):
            fail(
                f"--session-id must look like YYYY-MM-DD or YYYY-MM-DD-N, "
                f"got {requested!r}. The id is a node key and a record key at "
                "once; a free-form one silently splits the two."
            )
        candidate = f"session:{requested}"
        if candidate not in graph.get("nodes", {}):
            return candidate
    return find_next_session_id(graph, session_date)


def find_next_session_id(graph, session_date):
    """Find the next available session ID for a given date."""
    nodes = graph.get("nodes", {})
    base = f"session:{session_date}"

    # Check if base ID exists, if so try a, b, c...
    if base not in nodes:
        return base

    for suffix in "abcdefghijklmnopqrstuvwxyz":
        candidate = f"{base}-{suffix}"
        if candidate not in nodes:
            return candidate

    # Fallback
    return f"{base}-z2"


def main():
    parser = argparse.ArgumentParser(
        description="Record session outcomes in the context graph"
    )
    parser.add_argument(
        "--summary", required=True,
        help="Brief session summary (1-2 sentences)"
    )
    parser.add_argument(
        "--issue", action="append", dest="issues", default=[],
        help="Issue identifier worked on (can specify multiple)"
    )
    parser.add_argument(
        "--decision", action="append", dest="decisions", default=[],
        help="ADR number produced/referenced (can specify multiple)"
    )
    parser.add_argument(
        "--date", type=str, default=None,
        help="Session date (YYYY-MM-DD, default: today)"
    )
    parser.add_argument(
        "--root", type=str, default=None,
        help="Project root (default: auto-detect)"
    )
    parser.add_argument(
        "--workspace", action="store_true",
        help="Record session in the workspace-level graph"
    )
    parser.add_argument(
        "--session-id", type=str, default=None, dest="session_id",
        help="Node id to use, without the `session:` prefix "
             "(default: next free id for the date)"
    )
    args = parser.parse_args()

    if args.workspace:
        root = find_workspace_root()
        if not root:
            fail("No workspace found (no .flydocs-workspace.json)")
    else:
        root = Path(args.root) if args.root else find_project_root()
        if not root:
            fail("Could not find project root (no .flydocs/ directory found)")

    graph = load_graph(root)

    session_date = args.date or date.today().isoformat()
    session_id = resolve_session_node_id(graph, session_date, args.session_id)

    # Create session node
    graph["nodes"][session_id] = {
        "type": "session",
        "label": args.summary,
        "date": session_date,
        "manual": True,
    }

    edges_added = []

    # Create WORKED_ON edges to issues
    for issue_ref in args.issues:
        issue_node = f"issue:{issue_ref}"

        # Create issue node if it doesn't exist
        if issue_node not in graph["nodes"]:
            graph["nodes"][issue_node] = {
                "type": "issue",
                "label": issue_ref,
                "manual": True,
            }

        edge = {
            "from": session_id,
            "to": issue_node,
            "rel": "WORKED_ON",
            "weight": 1.0,
            "manual": True,
        }
        graph["edges"].append(edge)
        edges_added.append(f"{session_id} --WORKED_ON--> {issue_node}")

    # Create PRODUCED edges to decisions
    for decision_num in args.decisions:
        decision_node = f"decision:{decision_num.zfill(3)}"

        edge = {
            "from": session_id,
            "to": decision_node,
            "rel": "PRODUCED",
            "weight": 1.0,
            "manual": True,
        }

        # Only add if target exists
        if decision_node in graph["nodes"]:
            graph["edges"].append(edge)
            edges_added.append(f"{session_id} --PRODUCED--> {decision_node}")

    save_graph(root, graph)

    output_json({
        "success": True,
        "sessionId": session_id,
        "date": session_date,
        "summary": args.summary,
        "edges": edges_added,
    })


if __name__ == "__main__":
    main()
