"""Read-only graph queries and formatting — supplements store.py."""

from __future__ import annotations

import asyncpg
from redis.asyncio import Redis

from loom.graph import cache, store
from loom.graph.task import Task, TaskStatus


def tasks_to_graph_list(tasks: list[Task]) -> list[dict]:
    """Convert Task objects to lightweight dicts for graph display."""
    return [
        {
            "id": t.id, "title": t.title, "status": t.status.value,
            "priority": t.priority.value, "assignee": t.assignee,
            "parent_id": t.parent_id, "depends_on": t.depends_on,
        }
        for t in tasks
    ]


def format_mermaid(task_list: list[dict]) -> str:
    """Format a task graph list as a Mermaid diagram."""
    lines = ["graph TD"]
    for t in task_list:
        label = f"{t['id']}[{t['title']}]"
        lines.append(f"    {label}")
        for dep in t["depends_on"]:
            lines.append(f"    {dep} --> {t['id']}")
    return "\n".join(lines)


async def trace_blockers(
    pool: asyncpg.Pool, task_id: str, *, max_depth: int = 10
) -> dict:
    """Trace the dependency chain to find what's blocking a task.

    Returns a dict with the target task info and a tree of unsatisfied
    dependencies, each annotated with status and their own blockers.
    """
    task = await store.get_task(pool, task_id)

    async def _trace(tid: str, depth: int, visited: set[str]) -> dict | None:
        if tid in visited or depth >= max_depth:
            return {"id": tid, "cycle_or_depth_limit": True}
        visited.add(tid)
        t = await store.get_task(pool, tid)
        blockers = []
        for dep_id in t.depends_on:
            dep = await store.get_task(pool, dep_id)
            if dep.status == TaskStatus.DONE:
                continue
            node: dict = {
                "id": dep.id,
                "title": dep.title,
                "status": dep.status.value,
                "assignee": dep.assignee,
            }
            if dep.depends_on:
                sub = await _trace(dep.id, depth + 1, visited)
                if sub and sub.get("blockers"):
                    node["blockers"] = sub["blockers"]
            blockers.append(node)
        return {"id": tid, "title": t.title, "status": t.status.value, "blockers": blockers}

    result = await _trace(task_id, 0, set())
    if not result:
        return {"id": task_id, "title": task.title, "status": task.status.value, "blockers": []}
    return result


async def aggregate_progress(
    pool: asyncpg.Pool, redis: Redis, project_id: str
) -> dict:
    """Aggregate progress across all claimed tasks in a project.

    Returns per-task progress and overall summary stats.
    """
    claimed = await store.get_tasks_by_status(pool, project_id, "claimed", limit=1000)
    tasks = []
    total_percent = 0
    with_percent = 0
    for t in claimed:
        progress = await cache.get_task_progress(redis, project_id, t.id)
        entry: dict = {
            "id": t.id,
            "title": t.title,
            "assignee": t.assignee,
            "priority": t.priority.value,
        }
        if progress:
            entry["progress"] = progress
            if progress.get("percent") is not None:
                total_percent += progress["percent"]
                with_percent += 1
        tasks.append(entry)
    avg_percent = round(total_percent / with_percent) if with_percent else None
    return {
        "claimed_count": len(claimed),
        "reporting_progress": with_percent,
        "average_percent": avg_percent,
        "tasks": tasks,
    }


def build_conflict_map(claimed_tasks: list[Task]) -> dict[str, list[str]]:
    """Build a mapping of file -> [task_ids] for claimed tasks."""
    claimed_files: dict[str, list[str]] = {}
    for ct in claimed_tasks:
        for f in (ct.context.get("files") or []) if isinstance(ct.context, dict) else []:
            claimed_files.setdefault(f, []).append(ct.id)
    return claimed_files


def annotate_conflicts(dumps: list[dict], claimed_files: dict[str, list[str]]) -> None:
    """Add file_conflicts annotations to task dicts that overlap with claimed tasks."""
    for d in dumps:
        ctx_data = d.get("context") or {}
        files = ctx_data.get("files") or [] if isinstance(ctx_data, dict) else []
        overlaps = {f: claimed_files[f] for f in files if f in claimed_files}
        if overlaps:
            d["file_conflicts"] = overlaps


async def list_projects_with_status(pool: asyncpg.Pool) -> list[dict]:
    """List all projects with task count summaries."""
    from loom.graph.project import get_project_status, list_projects
    projects = await list_projects(pool)
    result = []
    for p in projects:
        pid = str(p["id"])
        try:
            status = await get_project_status(pool, pid)
            result.append({
                "id": pid, "name": p["name"],
                "description": p.get("description", ""),
                "tags": p.get("tags", []),
                "total": status.total, "done": status.done,
                "pending": status.pending, "failed": status.failed,
                "issue": status.issue,
            })
        except Exception:
            result.append({"id": pid, "name": p["name"], "tags": p.get("tags", []), "total": 0})
    return result


def format_summary(task_list: list[dict]) -> str:
    """Format a task graph list as a text summary."""
    by_status: dict[str, int] = {}
    for t in task_list:
        by_status[t["status"]] = by_status.get(t["status"], 0) + 1
    total = len(task_list)
    parts = [f"{total} tasks total"]
    for s, c in sorted(by_status.items()):
        parts.append(f"  {s}: {c}")
    return "\n".join(parts)
