#!/usr/bin/env python3
"""sc writes [--since 1h] [--in <folder>]: what Space Command wrote lately.
sc answers [--in <folder>]: answers the user sent this session from Space Command.

Space Command logs each write it makes for the user (a ticked box, a new
task, a squash, an undo) app-side, one JSON line each, in `writes.jsonl`
beside its index (docs/design/claude.md › The user's edits, question 70).
This prints the recent ones for files in the current git folder, so a
Claude Code session knows which uncommitted changes are the user's. It
prints nothing when there are none, so the SessionStart hook costs nothing
then. It never writes.

`sc answers` prints what the user sent with Send to Session on a questions
copy (docs/design/claude.md, question 90): a JSON file each in `answers/` beside
the index. It takes the files for this git folder and this session, naming the
session from the hook's input, and deletes each one it prints, so the app can
tell it's been read.
"""
import argparse
import json
import os
import re
import subprocess
import sys
from datetime import datetime, timedelta, timezone

LOGS = [
    "~/Library/Application Support/Space Command/writes.jsonl",
    "~/Library/Containers/ca.space-command.SpaceCommand.store/Data/Library/Application Support/Space Command/writes.jsonl",
]
ANSWERS = [os.path.join(os.path.dirname(path), "answers") for path in LOGS]
SPAN = re.compile(r"^(\d+)([mhd])$")


def span(text):
    m = SPAN.match(text.strip())
    if not m:
        raise argparse.ArgumentTypeError(f"{text!r}: give minutes, hours or days, such as 30m, 1h or 2d")
    n, unit = int(m.group(1)), m.group(2)
    return timedelta(minutes=n) if unit == "m" else timedelta(hours=n) if unit == "h" else timedelta(days=n)


def folder(start):
    try:
        top = subprocess.run(["git", "-C", start, "rev-parse", "--show-toplevel"],
                             capture_output=True, text=True, timeout=5).stdout.strip()
    except Exception:
        top = ""
    return os.path.realpath(top or start)


def entries(paths):
    for path in paths:
        try:
            with open(os.path.expanduser(path), encoding="utf-8") as f:
                for line in f:
                    try:
                        entry = json.loads(line)
                        entry["at"] = datetime.fromisoformat(entry["at"].replace("Z", "+00:00"))
                        yield entry
                    except (ValueError, KeyError, TypeError):
                        continue
        except OSError:
            continue


def ago(delta):
    minutes = int(delta.total_seconds() // 60)
    if minutes < 1:
        return "just now"
    if minutes < 60:
        return f"{minutes} min ago"
    hours = minutes // 60
    if hours < 24:
        return f"{hours} h ago"
    return f"{hours // 24} d ago"


def writes(args):
    now = datetime.now(timezone.utc)
    top = folder(args.folder)
    # The home folder holds every project: too wide to say anything useful.
    if top == os.path.realpath(os.path.expanduser("~")):
        return
    lines = []
    for entry in sorted(entries(LOGS), key=lambda e: e["at"]):
        if now - entry["at"] > args.since:
            continue
        files = []
        for f in entry.get("files", []):
            path = os.path.realpath(f.get("path", ""))
            if path == top or path.startswith(top + os.sep):
                where = os.path.relpath(path, top)
                nums = f.get("lines") or []
                files.append(where + (f" (line{'s' if len(nums) > 1 else ''} {', '.join(map(str, nums))})" if nums else ""))
        if files:
            lines.append(f"- {ago(now - entry['at'])}: {entry.get('label', 'A write')} in {'; '.join(files)}")
    if lines:
        print("The user made these edits from Space Command's panel, so these changes are theirs, "
              "not another session's:")
        print("\n".join(lines))


def session_name(transcript):
    """The session's name in its transcript, as copy-questions heads its copies."""
    name = None
    try:
        with open(transcript, encoding="utf-8") as f:
            for line in f:
                if '"agent-name"' not in line and '"custom-title"' not in line:
                    continue
                try:
                    d = json.loads(line)
                except ValueError:
                    continue
                if d.get("type") in ("agent-name", "custom-title"):
                    name = d.get("agentName") or d.get("customTitle") or name
    except (OSError, TypeError):
        pass
    return name


def hook_input():
    if sys.stdin is None or sys.stdin.isatty():
        return {}
    try:
        return json.load(sys.stdin)
    except ValueError:
        return {}


def answers(args):
    top = folder(args.folder)
    if top == os.path.realpath(os.path.expanduser("~")):
        return
    # A session without a name is headed by its folder's name.
    name = session_name(hook_input().get("transcript_path")) or os.path.basename(top)
    found = []
    for directory in ANSWERS:
        try:
            files = os.listdir(os.path.expanduser(directory))
        except OSError:
            continue
        for file in files:
            if not file.endswith(".json"):
                continue
            path = os.path.join(os.path.expanduser(directory), file)
            try:
                with open(path, encoding="utf-8") as f:
                    drop = json.load(f)
                at = datetime.fromisoformat(drop["at"].replace("Z", "+00:00"))
                text = drop["answers"]
                where = drop["folder"]
            except (OSError, ValueError, KeyError, TypeError, AttributeError):
                continue
            if os.path.realpath(where) != top or (drop.get("session") not in (None, name)):
                continue
            # Removing it claims it: two sessions never both get one.
            try:
                os.remove(path)
            except OSError:
                continue
            found.append((at, drop.get("caption"), text))
    now = datetime.now(timezone.utc)
    for at, caption, text in sorted(found, key=lambda f: f[0]):
        print(f"The user answered your questions from Space Command's panel, {ago(now - at)}. "
              "These are their answers, by question number:")
        if caption:
            print(f"For: {caption}")
        print(text)


def main():
    parser = argparse.ArgumentParser(prog="sc", description="Space Command from the command line.")
    sub = parser.add_subparsers(dest="command", required=True)
    w = sub.add_parser("writes", help="what Space Command wrote lately in this folder")
    w.add_argument("--since", type=span, default=timedelta(hours=1), help="how far back: 30m, 1h (the default), 2d")
    w.add_argument("--in", dest="folder", default=os.getcwd(), help="the folder (its git folder); the current one by default")
    a = sub.add_parser("answers", help="answers the user sent this session; prints each once, then deletes it")
    a.add_argument("--in", dest="folder", default=os.getcwd(), help="the folder (its git folder); the current one by default")
    args = parser.parse_args()
    if args.command == "writes":
        writes(args)
    elif args.command == "answers":
        answers(args)


if __name__ == "__main__":
    try:
        main()
    except BrokenPipeError:
        sys.exit(0)
