"""
Shared repo context resolution for hooks in sibling-repos topology.

In sibling-repos (type 4), hooks run from the workspace parent but need to
read config and session files from the active child repo. This module provides
detection and path resolution.

Usage in hooks:
    from repo_context import resolve_repo_dir
    repo_dir = resolve_repo_dir()          # from active-repo pointer
    repo_dir = resolve_repo_dir(file_path) # from file being edited
    config = Path(repo_dir) / '.flydocs' / 'config.json'
"""

import json
import os
from pathlib import Path

WORKSPACE_FILENAME = '.flydocs-workspace.json'
ACTIVE_REPO_POINTER = '.flydocs/session/active-repo'


def _read_workspace() -> dict | None:
    """Read .flydocs-workspace.json from CWD if it exists."""
    ws_path = Path(WORKSPACE_FILENAME)
    if not ws_path.exists():
        return None
    try:
        return json.loads(ws_path.read_text())
    except (json.JSONDecodeError, OSError):
        return None


def _repo_name_from_path(file_path: str, workspace: dict) -> str | None:
    """Determine which child repo a file path belongs to.

    Resolves file_path to realpath (resolving symlinks), then checks if it
    falls under any repo directory listed in the workspace file.
    """
    try:
        real_path = os.path.realpath(file_path)
    except (ValueError, OSError):
        return None

    cwd = os.path.realpath(os.getcwd())
    for name, entry in workspace.get('repos', {}).items():
        repo_real = os.path.realpath(os.path.join(cwd, entry.get('path', '')))
        if real_path.startswith(repo_real + os.sep) or real_path == repo_real:
            return name
    return None


def _read_active_repo() -> str | None:
    """Read the repo name from the active-repo pointer file."""
    pointer = Path(ACTIVE_REPO_POINTER)
    if not pointer.exists():
        return None
    try:
        name = pointer.read_text().strip()
        return name if name else None
    except (OSError, IOError):
        return None


def _write_active_repo(repo_name: str) -> None:
    """Write the repo name to the active-repo pointer file."""
    pointer = Path(ACTIVE_REPO_POINTER)
    try:
        pointer.parent.mkdir(parents=True, exist_ok=True)
        pointer.write_text(repo_name + '\n')
    except (OSError, IOError):
        pass


def _repo_dir_from_name(repo_name: str, workspace: dict) -> str | None:
    """Resolve a repo name to its absolute directory path (symlinks resolved)."""
    entry = workspace.get('repos', {}).get(repo_name)
    if not entry:
        return None
    return os.path.realpath(os.path.join(os.getcwd(), entry.get('path', '')))


def list_repo_dirs() -> list[tuple[str, str]]:
    """List all child repo directories in the workspace.

    Returns a list of (name, absolute_path) tuples.
    Returns empty list if not in a sibling-repos workspace.
    """
    workspace = _read_workspace()
    if not workspace:
        return []

    result = []
    for name, entry in workspace.get('repos', {}).items():
        repo_dir = os.path.realpath(os.path.join(os.getcwd(), entry.get('path', '')))
        if os.path.isdir(repo_dir):
            result.append((name, repo_dir))
    return result


def is_workspace() -> bool:
    """Check if CWD is a sibling-repos workspace root."""
    return _read_workspace() is not None


def resolve_session_dir(repo_dir: str | None = None) -> Path:
    """Resolve the workspace-scoped session directory.

    Session files (focus.md, status, acceptance-criteria.md, last-summary.json)
    are scoped by workspaceId so that multiple workspaces sharing the same
    repo directory maintain isolated session state. (FLY-674)

    Returns:
        Path to `.flydocs/session/<workspaceId>/` if workspaceId is available,
        `.flydocs/session/default/` otherwise (local tier).

    Note: The `active-repo` pointer remains at `.flydocs/session/active-repo`
    (flat, unscoped) because it's a workspace-level concern resolved before
    config is available.
    """
    base = Path(repo_dir) if repo_dir else Path('.')
    config_file = base / '.flydocs' / 'config.json'

    workspace_id: str | None = None
    if config_file.exists():
        try:
            config = json.loads(config_file.read_text())
            workspace_id = config.get('workspaceId')
        except (json.JSONDecodeError, OSError):
            pass

    scope = workspace_id if workspace_id else 'default'
    return base / '.flydocs' / 'session' / scope


def resolve_repo_dir(file_path: str | None = None) -> str:
    """Resolve the directory to read .flydocs/ files from.

    In sibling-repos topology (workspace file exists at CWD):
    1. If file_path provided, detect repo from the path and update pointer
    2. Fall back to active-repo pointer
    3. Fall back to CWD (workspace root)

    In single-repo topology, returns CWD unchanged.

    Returns an absolute path.
    """
    workspace = _read_workspace()
    if not workspace:
        return os.getcwd()

    # Option A: detect from file path
    if file_path:
        repo_name = _repo_name_from_path(file_path, workspace)
        if repo_name:
            _write_active_repo(repo_name)
            repo_dir = _repo_dir_from_name(repo_name, workspace)
            if repo_dir:
                return repo_dir

    # Option B: read pointer
    active_name = _read_active_repo()
    if active_name:
        repo_dir = _repo_dir_from_name(active_name, workspace)
        if repo_dir:
            return repo_dir

    return os.getcwd()
