"""중앙 락과 task-key 단위 mutex. 다른 okstra_ctl 모듈에 의존하지 않는다."""
from __future__ import annotations

import contextlib
import fcntl

from .ids import _escape_segment_for_join, _safe_fs_segment


@contextlib.contextmanager
def central_lock(home):
    """~/.okstra/.lock 위에 fcntl LOCK_EX.

    중앙 인덱스(active.jsonl·recent.jsonl)를 쓰는 모든 경로가 이 한 파일을
    거치므로 record_start·reconcile·reserve 가 서로 직렬화된다.
    """
    from pathlib import Path as _Path
    home_p = _Path(home)
    home_p.mkdir(parents=True, exist_ok=True)
    lockfile = home_p / ".lock"
    if not lockfile.exists():
        lockfile.touch()
    f = lockfile.open("r+")
    try:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX)
        yield
    finally:
        f.close()


@contextlib.contextmanager
def worktree_provision_mutex(home, project_id: str, task_group: str,
                             task_id: str):
    """task-key 단위 worktree 프로비저닝 임계구역
    (`<home>/.locks/worktree-provision/<task-key>.lock` 위 fcntl LOCK_EX).

    사전검사(registry lookup / 경로·브랜치 존재)·stage 선택·`git worktree add`·
    registry reserve 가 락 없이 인터리브되면: 동시 `--stage auto` run 둘이 같은
    stage 를 선택해 후발이 "reused" 경로로 같은 worktree 에 들어가고, 같은
    경로·브랜치를 둔 git 경쟁이 비결정적으로 실패하며, 한쪽 rollback
    (`git worktree remove --force`)이 다른 쪽 worktree 를 지울 수 있다.
    stage worktree 도 같은 task-key 락을 공유한다 — 브랜치 네임스페이스와
    common-anchor 계산이 task 단위로 묶여 있기 때문.

    flock 은 재진입 불가: 이 락을 쥔 채 다시 acquire 하면 self-deadlock 이므로
    provision 함수 내부가 아니라 호출자(run.py orchestration)가 1회 감싼다.
    """
    from pathlib import Path as _Path
    locks_dir = _Path(home) / ".locks" / "worktree-provision"
    locks_dir.mkdir(parents=True, exist_ok=True)
    parts = [_escape_segment_for_join(_safe_fs_segment(s))
             for s in (project_id, task_group, task_id)]
    lockfile = locks_dir / ("-".join(parts) + ".lock")
    lockfile.touch(exist_ok=True)
    with lockfile.open("r+") as f:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX)
        try:
            yield
        finally:
            fcntl.flock(f.fileno(), fcntl.LOCK_UN)
