#!/usr/bin/env python3
"""
Diff Summary -- Plain-English summaries of git changes.

Generates human-readable summaries of what changed in a git diff.
Designed for handoff documents, session logs, and non-developer readers.

Usage:
    diff-summary                          # summarize uncommitted changes
    diff-summary HEAD~3                   # summarize last 3 commits
    diff-summary main..develop            # summarize branch differences
    diff-summary --staged                 # summarize staged changes only
    diff-summary --files                  # just list changed files with stats
    diff-summary --since "2 hours ago"    # changes in time range
"""

import sys
import os
import re
import subprocess
from pathlib import Path
from collections import defaultdict

__version__ = "1.0.0"

# ANSI colors
class C:
    GREEN = "\033[92m"
    YELLOW = "\033[93m"
    RED = "\033[91m"
    BLUE = "\033[94m"
    BOLD = "\033[1m"
    DIM = "\033[2m"
    RESET = "\033[0m"

    @staticmethod
    def disable():
        C.GREEN = C.YELLOW = C.RED = C.BLUE = C.BOLD = C.DIM = C.RESET = ""


def run_git(args):
    """Run a git command and return output."""
    try:
        result = subprocess.run(
            ["git"] + args,
            capture_output=True, text=True, timeout=30
        )
        return result.stdout.strip() if result.returncode == 0 else ""
    except Exception:
        return ""


def get_diff_stat(ref=None, staged=False):
    """Get diff --stat output."""
    cmd = ["diff", "--stat"]
    if staged:
        cmd.append("--staged")
    elif ref:
        cmd.append(ref)
    return run_git(cmd)


def get_diff_numstat(ref=None, staged=False):
    """Get per-file additions/deletions."""
    cmd = ["diff", "--numstat"]
    if staged:
        cmd.append("--staged")
    elif ref:
        cmd.append(ref)
    output = run_git(cmd)
    if not output:
        return []

    files = []
    for line in output.split("\n"):
        parts = line.split("\t")
        if len(parts) == 3:
            added = int(parts[0]) if parts[0] != '-' else 0
            removed = int(parts[1]) if parts[1] != '-' else 0
            files.append({
                "path": parts[2],
                "added": added,
                "removed": removed,
                "net": added - removed,
            })
    return files


def get_changed_files(ref=None, staged=False):
    """Get list of changed files with change type."""
    cmd = ["diff", "--name-status"]
    if staged:
        cmd.append("--staged")
    elif ref:
        cmd.append(ref)
    output = run_git(cmd)
    if not output:
        return []

    files = []
    for line in output.split("\n"):
        if not line:
            continue
        parts = line.split("\t")
        if len(parts) >= 2:
            status = parts[0][0]
            path = parts[-1]
            status_map = {"A": "added", "M": "modified", "D": "deleted", "R": "renamed"}
            files.append({
                "path": path,
                "status": status_map.get(status, status),
            })
    return files


def get_commit_log(ref=None, since=None):
    """Get commit log."""
    cmd = ["log", "--oneline", "--no-merges"]
    if since:
        cmd.append(f"--since={since}")
    elif ref:
        cmd.append(ref)
    else:
        cmd.append("-10")
    return run_git(cmd)


def categorize_file(path):
    """Categorize a file by its role in the project."""
    p = path.lower()

    if '.claude/agents/' in p: return 'agent definitions'
    if '.claude/skills/' in p: return 'skills'
    if '.claude/hooks/' in p: return 'hooks'
    if 'agent-learnings/' in p: return 'learning system'
    if 'handoffs/' in p: return 'handoff documents'
    if 'session-logs/' in p: return 'session logs'
    if 'docs/specs/' in p: return 'specifications'
    if 'docs/plans/' in p: return 'plans'
    if 'docs/guides/' in p: return 'guides'
    if 'docs/reference/' in p: return 'reference docs'
    if 'docs/research/' in p: return 'research'
    if 'docs/design/' in p: return 'design docs'
    if 'docs/status/' in p: return 'status'
    if 'databases/' in p: return 'database files'
    if 'scrapers/' in p: return 'scrapers'
    if 'utilities/' in p: return 'utilities'
    if 'components/' in p: return 'UI components'
    if 'stores/' in p: return 'state management'
    if 'lib/' in p: return 'shared libraries'
    if 'app/' in p: return 'app pages'
    if 'types/' in p: return 'type definitions'
    if p.endswith('.test.ts') or p.endswith('.test.tsx') or p.endswith('.spec.ts'): return 'tests'
    if p.endswith('.json') and 'package' in p: return 'package config'
    if p == 'claude.md': return 'project config'
    if p.endswith('.md'): return 'documentation'
    if p.endswith('.py'): return 'scripts'
    if p.endswith('.sh'): return 'shell scripts'
    if p.endswith('.ts') or p.endswith('.tsx'): return 'source code'
    if p.endswith('.js') or p.endswith('.jsx'): return 'source code'
    if p.endswith('.css') or p.endswith('.scss'): return 'styles'

    return 'other'


def generate_summary(files_changed, files_numstat, commits=None, ref_label=None):
    """Generate a plain-English summary."""
    lines = []

    if ref_label:
        lines.append(f"{C.BOLD}Changes: {ref_label}{C.RESET}")
    else:
        lines.append(f"{C.BOLD}Changes Summary{C.RESET}")
    lines.append("")

    if not files_changed:
        lines.append("No changes found.")
        return "\n".join(lines)

    # Group by category
    by_category = defaultdict(list)
    numstat_map = {f["path"]: f for f in files_numstat}

    for f in files_changed:
        cat = categorize_file(f["path"])
        stat = numstat_map.get(f["path"], {})
        by_category[cat].append({
            **f,
            "added": stat.get("added", 0),
            "removed": stat.get("removed", 0),
        })

    # Overall stats
    total_added = sum(f.get("added", 0) for fs in by_category.values() for f in fs)
    total_removed = sum(f.get("removed", 0) for fs in by_category.values() for f in fs)
    total_files = len(files_changed)

    lines.append(f"  {C.GREEN}+{total_added}{C.RESET} {C.RED}-{total_removed}{C.RESET} across {total_files} files")
    lines.append("")

    # By category
    for category in sorted(by_category.keys()):
        files = by_category[category]
        cat_added = sum(f.get("added", 0) for f in files)
        cat_removed = sum(f.get("removed", 0) for f in files)

        lines.append(f"  {C.BLUE}{category}{C.RESET} ({len(files)} files, {C.GREEN}+{cat_added}{C.RESET}/{C.RED}-{cat_removed}{C.RESET})")

        for f in sorted(files, key=lambda x: x["path"]):
            status_icon = {"added": "+", "modified": "~", "deleted": "-", "renamed": ">"}.get(f["status"], "?")
            status_color = {"added": C.GREEN, "modified": C.YELLOW, "deleted": C.RED}.get(f["status"], "")
            name = Path(f["path"]).name
            lines.append(f"    {status_color}{status_icon}{C.RESET} {name}")

        lines.append("")

    # Plain-English summary
    lines.append(f"{C.BOLD}What changed:{C.RESET}")
    summaries = []
    for category, files in sorted(by_category.items()):
        count = len(files)
        statuses = set(f["status"] for f in files)
        if "added" in statuses and count == len([f for f in files if f["status"] == "added"]):
            summaries.append(f"  - Added {count} new {category}")
        elif "deleted" in statuses and count == len([f for f in files if f["status"] == "deleted"]):
            summaries.append(f"  - Removed {count} {category}")
        else:
            summaries.append(f"  - Updated {count} {category}")

    lines.extend(summaries)

    # Commits if available
    if commits:
        lines.append("")
        lines.append(f"{C.BOLD}Commits:{C.RESET}")
        for commit_line in commits.split("\n")[:10]:
            lines.append(f"  {C.DIM}{commit_line}{C.RESET}")

    return "\n".join(lines)


def main():
    args = sys.argv[1:]

    if "--version" in args:
        print(f"diff-summary {__version__}")
        sys.exit(0)

    if "--help" in args:
        print(__doc__)
        sys.exit(0)

    if not sys.stdout.isatty():
        C.disable()

    staged = "--staged" in args
    files_only = "--files" in args
    since = None
    ref = None

    if "--since" in args:
        idx = args.index("--since")
        if idx + 1 < len(args):
            since = args[idx + 1]

    # Get ref (first non-flag argument)
    for a in args:
        if not a.startswith("--"):
            ref = a
            break

    # Get data
    if since:
        ref = f"HEAD@{{{since}}}"
        ref_label = f"since {since}"
    elif staged:
        ref_label = "staged changes"
    elif ref:
        ref_label = ref
    else:
        ref_label = "uncommitted changes"

    files_changed = get_changed_files(ref, staged)
    files_numstat = get_diff_numstat(ref, staged)
    commits = get_commit_log(ref, since) if (ref or since) else None

    if files_only:
        for f in files_changed:
            stat = next((s for s in files_numstat if s["path"] == f["path"]), {})
            added = stat.get("added", 0)
            removed = stat.get("removed", 0)
            print(f"  {f['status']:8s} {C.GREEN}+{added:<4d}{C.RESET} {C.RED}-{removed:<4d}{C.RESET} {f['path']}")
    else:
        print(generate_summary(files_changed, files_numstat, commits, ref_label))


if __name__ == "__main__":
    main()
