"""Task ID parsing utilities (V2).

Supports unified parsing of task IDs across files, branches, and CLI args.

V2 pattern:
    task-<digits>(_<phase>)?(_<parallel>)?(+<retry>)?
    - phase:    \\d+\\.\\d+   (e.g. _1.2)
    - parallel: [a-z]         (e.g. _a)
    - retry:    \\d+           (e.g. +1)

Examples
--------
- ``task-2469``                 -> base only
- ``task-2469+1``               -> retry suffix preserved
- ``task-2469_1.2``             -> phase
- ``task-2469_1.2_a``           -> phase + parallel
- ``task-2469_1.2_a+3``         -> phase + parallel + retry
"""

from __future__ import annotations

import re
from typing import Optional

# Full V2 pattern (anchored). Captures: base id, phase, parallel, retry.
TASK_ID_V2_PATTERN = re.compile(
    r"^task-(?P<num>\d+)(?:_(?P<phase>\d+\.\d+))?(?:_(?P<parallel>[a-z]))?(?:\+(?P<retry>\d+))?$"
)

# Legacy dot-phase 호환 패턴 (V2 패턴은 무수정, 본 패턴은 SSOT 인접 추가)
# 회귀 evidence: PR #49/#50/#51 Gemini high - task-1234.5 거부 회귀 픽스용
_LEGACY_DOTPHASE_PATTERN = re.compile(
    r"^task-\d+(?:\.\d+)*(?:\.dev\d+)?(?:\.done)?$"
)

# Loose pattern used when scanning filenames/branches (no anchors).
_TASK_ID_LOOSE_PATTERN = re.compile(
    r"task-(?P<num>\d+)(?:_(?P<phase>\d+\.\d+))?(?:_(?P<parallel>[a-z]))?(?:\+(?P<retry>\d+))?"
)


def parse_task_id_v2(s: str) -> dict:
    """Parse a V2 task id string into structural components.

    Returns a dict with keys ``base``, ``phase``, ``parallel``, ``retry``.
    For inputs that don't match, returns dict with all values ``None`` and
    ``base`` left as the original string for caller diagnostics.

    Examples
    --------
    >>> parse_task_id_v2("task-2469+1")
    {'base': 'task-2469', 'phase': None, 'parallel': None, 'retry': '+1'}
    >>> parse_task_id_v2("task-2469_1.2_a+3")
    {'base': 'task-2469', 'phase': '_1.2', 'parallel': '_a', 'retry': '+3'}
    """
    m = TASK_ID_V2_PATTERN.match(s.strip())
    if not m:
        return {"base": None, "phase": None, "parallel": None, "retry": None}

    base = f"task-{m.group('num')}"
    phase = f"_{m.group('phase')}" if m.group("phase") else None
    parallel = f"_{m.group('parallel')}" if m.group("parallel") else None
    retry = f"+{m.group('retry')}" if m.group("retry") else None

    return {"base": base, "phase": phase, "parallel": parallel, "retry": retry}


def is_valid_task_id(s: str) -> bool:
    """Return True iff ``s`` matches the V2 pattern exactly."""
    return TASK_ID_V2_PATTERN.match(s.strip()) is not None


def is_valid_task_id_with_legacy(s: object) -> bool:
    """V2 task id 또는 legacy dot-phase task id를 PASS.

    legacy 예시: task-9.1, task-1234.5, task-648.1.dev1, task-648.1.dev1.done
    V2 예시:    task-2485+1, task-2487+1, task-2469_1.2_a+3
    """
    if not isinstance(s, str):
        return False
    s = s.strip()
    return bool(TASK_ID_V2_PATTERN.match(s)) or bool(_LEGACY_DOTPHASE_PATTERN.match(s))


def _reassemble_from_match(m: re.Match) -> str:
    parts = [f"task-{m.group('num')}"]
    if m.group("phase"):
        parts.append(f"_{m.group('phase')}")
    if m.group("parallel"):
        parts.append(f"_{m.group('parallel')}")
    if m.group("retry"):
        parts.append(f"+{m.group('retry')}")
    return "".join(parts)


def extract_task_id_from_filename(filename: str) -> Optional[str]:
    """Extract a task id from a filename or path.

    Examples
    --------
    >>> extract_task_id_from_filename("memory/tasks/task-2469+1.md")
    'task-2469+1'
    >>> extract_task_id_from_filename("task-2469_1.2_a+3.md")
    'task-2469_1.2_a+3'
    """
    if not isinstance(filename, str) or not filename:
        return None

    # Strip directory + extension to maximize chance of clean match.
    basename = filename.rsplit("/", 1)[-1]
    stem = basename.rsplit(".", 1)[0] if "." in basename else basename

    m = TASK_ID_V2_PATTERN.match(stem)
    if m:
        return _reassemble_from_match(m)

    # Fallback: search inside the original (helps for prefixes like "memo_task-2469.md").
    m = _TASK_ID_LOOSE_PATTERN.search(filename)
    if m:
        return _reassemble_from_match(m)

    return None


def extract_task_id_from_branch(branch: str) -> Optional[str]:
    """Extract a task id from a branch name.

    Examples
    --------
    >>> extract_task_id_from_branch("task/task-2467+3-dev6")
    'task-2467+3'
    >>> extract_task_id_from_branch("task/task-2469_1.2-dev2")
    'task-2469_1.2'
    """
    if not isinstance(branch, str) or not branch:
        return None

    m = _TASK_ID_LOOSE_PATTERN.search(branch)
    if not m:
        return None

    return _reassemble_from_match(m)
