#!/usr/bin/env bash
# status.sh — multi-agent-mux-status 의 부속 스크립트 (READ-ONLY)
# 한 번 호출로 현재 agent 세션 상태표를 출력. 부수효과 없음.
# reconcile.sh --dry-run 을 재사용해 drift 를 계산하고 (P1-E), YAML/디스크에서
# 보강한 표를 그린다. YAML 을 절대 수정하지 않는다.
#
# Usage: bash status.sh [--json]
set -euo pipefail

source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"

RECONCILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/multi-agent-mux-monitor/scripts/reconcile.sh"

JSON=0
[ "${1:-}" = "--json" ] && JSON=1

[ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found. Run multi-agent-mux-create first." >&2; exit 1; }

# read-only drift snapshot — reconcile.sh --dry-run (no side effects)
DRIFT_JSON="$(bash "$RECONCILE" --once --emit-diff --dry-run)"

# Project root (parent of .agents/) holds the multi-agent-mux-delegate-job .mam registry.
# Resolved relative to this script — no hardcoded absolute path (review item 6).
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../../" && pwd)"

if [ "$JSON" = "1" ]; then
  # D8: --json historically only carried reconcile.sh's drift subset (timestamp/
  # yaml_path/herdr_sessions_alive/herdr_confirmed/drifts/actions), not the enriched
  # per-row fields (RESUME/JOB_ID/JOB_STATUS/CMD/attach_command/pane.cwd/...) that
  # only this script's text-mode block computed. Fixed additively below via a
  # 'sessions_detail' key — every existing key is passed through untouched, so
  # any consumer of the pre-fix schema keeps working unmodified.
  MAM_STATE_JSON="$(load_state_json)" DRIFT_JSON="$DRIFT_JSON" env_python "$AGENT_SESSIONS_YAML" PROJECT_ROOT="$PROJECT_ROOT" <<'PYEOF'
import os, json, glob

home = os.environ['HOME_DIR']
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
drift = json.loads(os.environ['DRIFT_JSON'])

try:
    d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
except Exception:
    d = {}

alive = set(drift.get('herdr_sessions_alive', []))
drift_by_name = {}
for dr in drift.get('drifts', []):
    drift_by_name.setdefault(dr['name'], []).append(dr['class'])


def resume_on_disk(s):
    # workspace-SCOPED check only — per-row own id, never a global identity (P0-C)
    name = s.get('name', '')
    cwd = (s.get('pane') or {}).get('cwd', '')
    agent = None
    for a in ('claude', 'agy', 'hermes', 'grok', 'opencode'):
        if any(name.endswith(f'-{r}-{a}') for r in ('creator', 'planner', 'reviewer')) or name.endswith(f'-{a}'):
            agent = a
            break
    if not agent:
        for a in ('claude', 'agy', 'hermes', 'grok', 'opencode'):
            if f"-{a}" in name or f"_{a}" in name:
                agent = a
                break

    if agent == 'claude':
        u = s.get('claude_session_id_own')
        if u:
            key = cwd.replace('/', '-').replace('_', '-')
            return 'yes' if os.path.exists(f"{claude_project_dir}/{key}/{u}.jsonl") else 'MISSING'
        key = cwd.replace('/', '-').replace('_', '-')
        return 'scan' if glob.glob(f"{claude_project_dir}/{key}/*.jsonl") else 'no'
    if agent == 'agy':
        u = s.get('agy_conversation_id_own')
        if u:
            return 'yes' if os.path.exists(f"{home}/.gemini/antigravity-cli/conversations/{u}.db") else 'MISSING'
        return 'no'
    if agent == 'hermes':
        u = s.get('hermes_conversation_id_own')
        if u:
            hdb = f"{home}/.hermes/state.db"
            if not os.path.exists(hdb):
                return 'MISSING'
            try:
                import sqlite3
                conn = sqlite3.connect(hdb)
                r = conn.execute("SELECT 1 FROM sessions WHERE id=?", (u,)).fetchone()
                conn.close()
                return 'yes' if r else 'MISSING'
            except Exception:
                return 'MISSING'
        return 'no'
    if agent == 'grok':
        u = s.get('grok_session_id_own')
        if u:
            import urllib.parse
            ws_slug = urllib.parse.quote(cwd, safe='')
            return 'yes' if os.path.exists(f"{home}/.grok/sessions/{ws_slug}/{u}/chat_history.jsonl") else 'MISSING'
        return 'no'
    if agent == 'opencode':
        u = s.get('opencode_session_id_own')
        if u:
            base_dir = f"{home}/.local/share/opencode"
            odb = None
            for cand in [f"{base_dir}/opencode.db", f"{base_dir}/storage.db"]:
                if os.path.exists(cand):
                    odb = cand
                    break
            if not odb and os.path.isdir(f"{base_dir}/project"):
                for root, _, files in os.walk(f"{base_dir}/project"):
                    for f in files:
                        if f.endswith(".db"):
                            odb = os.path.join(root, f)
                            break
                    if odb:
                        break
            if not odb or not os.path.exists(odb):
                return 'MISSING'
            try:
                import sqlite3
                conn = sqlite3.connect(odb)
                r = conn.execute("SELECT 1 FROM session WHERE id=?", (u,)).fetchone()
                conn.close()
                return 'yes' if r else 'MISSING'
            except Exception:
                return 'MISSING'
        return 'no'
    return '?'


def get_job_status(s):
    jid = s.get('delegate_job_id')
    if not jid:
        return ('-', '-')

    project_root = os.environ.get('PROJECT_ROOT', '.')
    candidates = [
        os.path.join('.mam', 'jobs', f"{jid}.json"),
        os.path.join(project_root, '.mam', 'jobs', f"{jid}.json"),
        os.path.join(project_root, '.mam', 'delegate_job_logs', jid, 'status.json'),
    ]
    for path in candidates:
        if os.path.exists(path):
            try:
                with open(path) as jf:
                    job_data = json.load(jf)
                return (jid, job_data.get('status', 'unknown'))
            except Exception:
                pass

    return (jid, 'unknown')


def _slug(path):
    if not path:
        return ''
    import re
    a = os.path.abspath(path)
    parent = os.path.basename(os.path.dirname(a)) or 'workspace'
    work = os.path.basename(a) or 'root'
    if parent in ('/', '.'): parent = 'workspace'
    if work in ('/', '.'): work = 'root'
    s = f'{parent}-{work}'.lower().replace('_', '-')
    return re.sub(r'[^a-zA-Z0-9-]', '', s).lstrip('-')


sessions_detail = []
from lib_py.agents.sanitize import sanitize_herdr_agent_name as _sanitize

def is_alive(name, server):
    return f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive

for s in d.get('herdr_sessions', []):
    name = s.get('name', '?')
    # herdr_workspace 는 워크스페이스 *라벨* 이지 소켓 이름이 아니다.
    server = s.get('herdr_session') or s.get('herdr_server') or 'default'
    jid, jstatus = get_job_status(s)
    pane = s.get('pane') or {}
    wslabel = s.get('herdr_workspace') or _slug(pane.get('cwd', '')) or None
    sessions_detail.append({
        # Fields named/typed to match the reviewed D8 contract
        # (.mam/jobs/40bdce88/claude-reports/report-final.md §3.1) exactly —
        # mam_core maps this straight onto its Session/Pane/Drift models.
        'name': name,
        'server': server,
        'herdr_workspace': wslabel,
        'status': s.get('status', '?'),
        'herdr_alive': is_alive(name, server),
        'cmd': pane.get('cmd'),
        'role': s.get('role'),
        'resume_state': resume_on_disk(s),
        'job_id': jid,
        'job_status': jstatus,
        'pane_cwd': pane.get('cwd'),
        'attach_command': s.get('attach_command'),
        'drift_classes': drift_by_name.get(name, []),
        # Additive beyond the D8 example — needed by the M1 Detail Pane
        # (pane pid / full launch command / start command / last banner text).
        'pane_pid': pane.get('pid'),
        'cmd_full': pane.get('cmd_full'),
        'start_command': s.get('start_command'),
        'last_visible_status': s.get('last_visible_status'),
    })

drift['sessions_detail'] = sessions_detail
print(json.dumps(drift, ensure_ascii=False))
PYEOF
  exit 0
fi

MAM_STATE_JSON="$(load_state_json)" DRIFT_JSON="$DRIFT_JSON" env_python "$AGENT_SESSIONS_YAML" PROJECT_ROOT="$PROJECT_ROOT" <<'PYEOF'
import os, sys, json, glob

home = os.environ['HOME_DIR']
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
drift = json.loads(os.environ['DRIFT_JSON'])

try:
    d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
except Exception:
    d = {}

alive = set(drift.get('herdr_sessions_alive', []))
drift_by_name = {}
for dr in drift.get('drifts', []):
    drift_by_name.setdefault(dr['name'], []).append(dr['class'])


def resume_on_disk(s):
    # workspace-SCOPED check only — per-row own id, never a global identity (P0-C)
    name = s.get('name', '')
    cwd = (s.get('pane') or {}).get('cwd', '')
    agent = None
    for a in ('claude', 'agy', 'hermes', 'grok', 'opencode'):
        if any(name.endswith(f'-{r}-{a}') for r in ('creator', 'planner', 'reviewer')) or name.endswith(f'-{a}'):
            agent = a
            break
    if not agent:
        for a in ('claude', 'agy', 'hermes', 'grok', 'opencode'):
            if f"-{a}" in name or f"_{a}" in name:
                agent = a
                break

    if agent == 'claude':
        u = s.get('claude_session_id_own')
        if u:
            key = cwd.replace('/', '-').replace('_', '-')
            return 'yes' if os.path.exists(f"{claude_project_dir}/{key}/{u}.jsonl") else 'MISSING'
        key = cwd.replace('/', '-').replace('_', '-')
        return 'scan' if glob.glob(f"{claude_project_dir}/{key}/*.jsonl") else 'no'
    if agent == 'agy':
        u = s.get('agy_conversation_id_own')
        if u:
            return 'yes' if os.path.exists(f"{home}/.gemini/antigravity-cli/conversations/{u}.db") else 'MISSING'
        return 'no'
    if agent == 'hermes':
        u = s.get('hermes_conversation_id_own')
        if u:
            hdb = f"{home}/.hermes/state.db"
            if not os.path.exists(hdb):
                return 'MISSING'
            try:
                import sqlite3
                conn = sqlite3.connect(hdb)
                r = conn.execute("SELECT 1 FROM sessions WHERE id=?", (u,)).fetchone()
                conn.close()
                return 'yes' if r else 'MISSING'
            except Exception:
                return 'MISSING'
        return 'no'
    if agent == 'grok':
        u = s.get('grok_session_id_own')
        if u:
            import urllib.parse
            ws_slug = urllib.parse.quote(cwd, safe='')
            return 'yes' if os.path.exists(f"{home}/.grok/sessions/{ws_slug}/{u}/chat_history.jsonl") else 'MISSING'
        return 'no'
    if agent == 'opencode':
        u = s.get('opencode_session_id_own')
        if u:
            base_dir = f"{home}/.local/share/opencode"
            odb = None
            for cand in [f"{base_dir}/opencode.db", f"{base_dir}/storage.db"]:
                if os.path.exists(cand):
                    odb = cand
                    break
            if not odb and os.path.isdir(f"{base_dir}/project"):
                for root, _, files in os.walk(f"{base_dir}/project"):
                    for f in files:
                        if f.endswith(".db"):
                            odb = os.path.join(root, f)
                            break
                    if odb:
                        break
            if not odb or not os.path.exists(odb):
                return 'MISSING'
            try:
                import sqlite3
                conn = sqlite3.connect(odb)
                r = conn.execute("SELECT 1 FROM session WHERE id=?", (u,)).fetchone()
                conn.close()
                return 'yes' if r else 'MISSING'
            except Exception:
                return 'MISSING'
        return 'no'
    return '?'


def get_job_status(s):
    jid = s.get('delegate_job_id')
    if not jid:
        return ('-', '-')

    project_root = os.environ.get('PROJECT_ROOT', '.')
    # Candidate locations (review item 6: project-root-relative, no hardcoded abs paths):
    #   1) cwd-relative registry  2) project-root registry  3) project-root audit log
    candidates = [
        os.path.join('.mam', 'jobs', f"{jid}.json"),
        os.path.join(project_root, '.mam', 'jobs', f"{jid}.json"),
        os.path.join(project_root, '.mam', 'delegate_job_logs', jid, 'status.json'),
    ]
    for path in candidates:
        if os.path.exists(path):
            try:
                with open(path) as jf:
                    job_data = json.load(jf)
                return (jid, job_data.get('status', 'unknown'))
            except Exception:
                pass

    return (jid, 'unknown')


def _slug(path):
    if not path:
        return ''
    import re
    a = os.path.abspath(path)
    parent = os.path.basename(os.path.dirname(a)) or 'workspace'
    work = os.path.basename(a) or 'root'
    if parent in ('/', '.'): parent = 'workspace'
    if work in ('/', '.'): work = 'root'
    s = f'{parent}-{work}'.lower().replace('_', '-')
    return re.sub(r'[^a-zA-Z0-9-]', '', s).lstrip('-')


from lib_py.agents.sanitize import sanitize_herdr_agent_name as _sanitize

sessions = d.get('herdr_sessions', [])
print(f"agent-sessions status — {drift['timestamp']}  (herdr_confirmed={drift['herdr_confirmed']})")
print("=" * 150)
print(f"{'NAME':<44} {'SOCKET':<12} {'WORKSPACE':<14} {'YAML':<10} {'HERDR':<6} {'CMD':<6} {'RESUME':<8} {'JOB_ID':<10} {'JOB_STATUS':<12} DRIFT")
print("-" * 150)
if not sessions:
    print("(no sessions registered)")
def is_alive(name, server):
    return f"{name}|{server}" in alive or f"{_sanitize(name)}|{server}" in alive

for s in sessions:
    name = s.get('name', '?')
    # herdr_workspace 는 워크스페이스 *라벨* 이지 소켓 이름이 아니다.
    server = s.get('herdr_session') or s.get('herdr_server') or 'default'
    wslabel = s.get('herdr_workspace') or _slug((s.get('pane') or {}).get('cwd', '')) or '-'
    status = s.get('status', '?')
    herdr = 'alive' if is_alive(name, server) else 'dead'
    cmd = (s.get('pane') or {}).get('cmd', '?')
    res = resume_on_disk(s)
    jid, jstatus = get_job_status(s)
    drs = ','.join(drift_by_name.get(name, [])) or '-'
    print(f"{name:<44} {server:<12} {wslabel:<14} {status:<10} {herdr:<6} {cmd:<6} {res:<8} {jid:<10} {jstatus:<12} {drs}")
# drifts not tied to a registered row (e.g. class B unregistered, class D cache)
known = {s.get('name') for s in sessions}
extra = [dr for dr in drift.get('drifts', []) if dr['name'] not in known]
if extra:
    print("-" * 136)
    for dr in extra:
        print(f"  [{dr['class']}] {dr['msg']}")
print("=" * 136)
print(f"alive herdr: {sorted(alive)}")
PYEOF
