#!/usr/bin/env bash
# Launch a MiniMax M3 implementation sub-session in an isolated git worktree,
# with the prompt assembled from a planner work item. See specs/mm-worktree-task.spec.md.
set -euo pipefail

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info()  { echo -e "${BLUE}==>${NC} $*"; }
ok()    { echo -e "${GREEN}✓${NC} $*"; }
warn()  { echo -e "${YELLOW}!${NC} $*" >&2; }
err()   { echo -e "${RED}✗${NC} $*" >&2; }

DEFAULT_ALLOWLIST="Read,Write,Edit,Glob,Grep,Bash(pytest *),Bash(python3 *),Bash(ruff *),Bash(grep *),Bash(ls *),Bash(git add *),Bash(git commit *),Bash(git status*),Bash(git diff*),Bash(git rev-parse*),Bash(git log*)"

usage() {
  cat <<'EOF'
Usage: mm-worktree-task <item-uuid> [OPTIONS]

Launch a MiniMax M3 implementation sub-session (via ask-minimax-run) in an
isolated git worktree of the current repo, with the prompt assembled from a
planner work item.

Arguments:
  <item-uuid>            Full 36-char planner work-item UUID. Required unless
                          --brief is given together with --slug.

Options:
  --slug <slug>           Worktree/branch suffix (default: first 8 chars of uuid)
  --base <ref>            Branch/commit the worktree is created from and that
                          --collect diffs against (default: main)
  --focus "<line>"        One-line focus appended to the task frame
  --brief <file>          Use this file as the task body instead of/in addition
                          to the fetched item
  --docs all|recent|none  Which item documents to bundle (default: recent)
  --tools "<list>"        Extra --allowedTools entries (comma-separated)
  --timeout <secs>        Sub-session timeout (default: 1800)
  --context <name>        cp-cli planner context (default: the ~/.cpconfig context
                          whose project_id equals the repo name, else cp-cli's own
                          current_context)
  --upload                After --collect, upload the report + mark impl-complete
  --collect               Don't launch; print status for an already-run slug
  --dryrun, -n            Assemble everything, print it, create nothing
  --help, -h              Show this help

Environment:
  MM_WORKTREE_TASK_CP_CLI_DIR=<dir>       checkout holding lib/cp_cli (default: the
                                          current repo if it has one, else
                                          ~/dev/claude/projects/agent-runtimes)
  MM_WORKTREE_TASK_CP_CONTEXT=<name>      same as --context
  MM_WORKTREE_TASK_CPCONFIG=<file>        cpconfig to resolve the context from
                                          (default: ~/.cpconfig)
Env overrides (for testing):
  MM_WORKTREE_TASK_FAKE_ITEM=<file>       fake `planner items get --json` output
  MM_WORKTREE_TASK_FAKE_DOCS_DIR=<dir>    fake docs: <dir>/list.json + <dir>/<name>
EOF
}

UUID=""
SLUG=""
FOCUS=""
BRIEF=""
BASE="main"
DOCS="recent"
EXTRA_TOOLS=""
TIMEOUT=1800
UPLOAD=0
COLLECT=0
DRYRUN=0
CP_CONTEXT="${MM_WORKTREE_TASK_CP_CONTEXT:-}"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --slug) SLUG="$2"; shift 2 ;;
    --focus) FOCUS="$2"; shift 2 ;;
    --brief) BRIEF="$2"; shift 2 ;;
    --base) BASE="$2"; shift 2 ;;
    --docs) DOCS="$2"; shift 2 ;;
    --tools) EXTRA_TOOLS="$2"; shift 2 ;;
    --timeout) TIMEOUT="$2"; shift 2 ;;
    --context) CP_CONTEXT="$2"; shift 2 ;;
    --upload) UPLOAD=1; shift ;;
    --collect) COLLECT=1; shift ;;
    --dryrun|-n) DRYRUN=1; shift ;;
    --help|-h) usage; exit 0 ;;
    -*) err "unknown flag: $1"; usage; exit 1 ;;
    *)
      if [[ -z "$UUID" ]]; then UUID="$1"; else err "unexpected argument: $1"; exit 1; fi
      shift ;;
  esac
done

case "$DOCS" in all|recent|none) ;; *) err "--docs must be all|recent|none"; exit 1 ;; esac

if [[ -z "$UUID" ]]; then
  if [[ -z "$BRIEF" || -z "$SLUG" ]]; then
    err "item-uuid is required unless --brief and --slug are both given"
    exit 1
  fi
fi

if [[ -n "$UUID" && -z "$SLUG" ]]; then
  SLUG="${UUID:0:8}"
fi

if [[ $UPLOAD -eq 1 && $COLLECT -eq 0 ]]; then
  err "--upload requires --collect (launch is detached; upload happens after the sub-session has produced a report)"
  exit 1
fi

# --- repo root ---
if ! REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"; then
  err "not inside a git repository"
  exit 1
fi
REPO_NAME="$(basename "$REPO_ROOT")"

# --- planner CLI: where it runs from, and which context it talks to ---
# The script is used from any planner-backed project; cp_cli itself lives in
# agent-runtimes. Resolve the checkout once and the context once, then every
# planner call goes through cp_cli().
resolve_cp_cli_dir() {
  local d
  for d in "${MM_WORKTREE_TASK_CP_CLI_DIR:-}" "$REPO_ROOT" "$HOME/dev/claude/projects/agent-runtimes"; do
    [[ -n "$d" && ( -f "$d/lib/cp_cli.py" || -d "$d/lib/cp_cli" ) ]] && { echo "$d"; return 0; }
  done
  return 1
}
CP_CLI_DIR="$(resolve_cp_cli_dir || true)"

if [[ -z "$CP_CONTEXT" ]]; then
  CPCONFIG="${MM_WORKTREE_TASK_CPCONFIG:-$HOME/.cpconfig}"
  if [[ -f "$CPCONFIG" ]]; then
    CP_CONTEXT="$(python3 - "$CPCONFIG" "$REPO_NAME" <<'PY' 2>/dev/null || true
import sys
try:
    import yaml
    cfg = yaml.safe_load(open(sys.argv[1])) or {}
except Exception:
    sys.exit(0)
for ctx in cfg.get("contexts") or []:
    if ctx.get("project_id") == sys.argv[2]:
        print(ctx.get("name", ""))
        break
PY
)"
  fi
fi

cp_cli() {
  if [[ -z "$CP_CLI_DIR" ]]; then
    err "cannot find lib/cp_cli.py (set MM_WORKTREE_TASK_CP_CLI_DIR or check out agent-runtimes)"
    return 1
  fi
  if [[ -n "$CP_CONTEXT" ]]; then
    ( cd "$CP_CLI_DIR" && python3 -m lib.cp_cli --context "$CP_CONTEXT" "$@" )
  else
    ( cd "$CP_CLI_DIR" && python3 -m lib.cp_cli "$@" )
  fi
}

REPORT_DIR="${XDG_RUNTIME_DIR:-/tmp}/mm-worktree-task/${REPO_NAME}"
WORKTREE="${REPO_ROOT}/.claude/worktrees/mm-${SLUG}"
BRANCH="mm/${SLUG}"
BUNDLE_FILE="${REPORT_DIR}/${SLUG}-bundle.md"
PROMPT_FILE="${REPORT_DIR}/${SLUG}-prompt.txt"
STDOUT_FILE="${REPORT_DIR}/${SLUG}-stdout.txt"
RESULT_FILE="${REPORT_DIR}/${SLUG}-result.md"
LAUNCH_LOG="${REPORT_DIR}/launch.log"

ALLOWLIST="$DEFAULT_ALLOWLIST"
[[ -n "$EXTRA_TOOLS" ]] && ALLOWLIST="${ALLOWLIST},${EXTRA_TOOLS}"

# --- --collect mode ---
if [[ $COLLECT -eq 1 ]]; then
  if [[ ! -d "$WORKTREE" ]]; then
    err "no worktree found for slug '${SLUG}' (${WORKTREE})"
    exit 1
  fi
  info "git log --oneline ${BASE}..HEAD"
  git -C "$WORKTREE" log --oneline "${BASE}..HEAD" || true
  echo
  info "git diff --stat ${BASE}...HEAD"
  git -C "$WORKTREE" diff --stat "${BASE}...HEAD" || true
  echo
  if [[ -f "$RESULT_FILE" ]]; then
    info "report: $RESULT_FILE"
    cat "$RESULT_FILE"
  else
    warn "no report found at $RESULT_FILE"
  fi

  if [[ $UPLOAD -eq 1 ]]; then
    if [[ -z "$UUID" ]]; then
      err "--upload requires an item-uuid"
      exit 1
    fi
    if [[ ! -f "$RESULT_FILE" ]]; then
      err "cannot upload: no report at $RESULT_FILE"
      exit 1
    fi
    DATE="$(date +%Y-%m-%d)"
    DOC_NAME="mm-${SLUG}-${DATE}.md"
    info "uploading ${DOC_NAME} to item ${UUID}"
    cp_cli planner items documents put "$UUID" "$DOC_NAME" --file "$RESULT_FILE"
    cp_cli planner items patch "$UUID" --flow-state impl-complete \
        --description "mm-worktree-task: ${SLUG} committed on mm/${SLUG}" 
    ok "uploaded and marked impl-complete"
  fi
  exit 0
fi

# --- gather item + docs ---
ITEM_JSON=""
TITLE=""
DOC_NAMES=()
DOC_BUNDLED=()
declare -A DOC_BODY

fetch_item() {
  if [[ -n "$UUID" ]]; then
    if [[ -n "${MM_WORKTREE_TASK_FAKE_ITEM:-}" ]]; then
      ITEM_JSON="$(cat "$MM_WORKTREE_TASK_FAKE_ITEM")"
    else
      ITEM_JSON="$(cp_cli planner items get "$UUID" --json)"
    fi
    TITLE="$(echo "$ITEM_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin).get("title",""))')"
  fi
}

fetch_docs() {
  [[ "$DOCS" == "none" ]] && return 0
  local list_json
  if [[ -n "${MM_WORKTREE_TASK_FAKE_DOCS_DIR:-}" ]]; then
    [[ -f "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/list.json" ]] || return 0
    list_json="$(cat "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/list.json")"
  elif [[ -n "$UUID" ]]; then
    list_json="$(cp_cli planner items documents list "$UUID" --json 2>/dev/null || echo '{"documents":[]}')"
  else
    return 0
  fi

  while IFS=$'\t' read -r name updated; do
    [[ -z "$name" ]] && continue
    DOC_NAMES+=("$name")
    local bundle=0
    if [[ "$DOCS" == "all" ]]; then
      bundle=1
    elif [[ "$DOCS" == "recent" ]]; then
      if [[ -n "$updated" && "$updated" != "None" ]]; then
        local cutoff now updated_epoch
        now="$(date +%s)"
        updated_epoch="$(date -d "$updated" +%s 2>/dev/null || echo "$now")"
        cutoff=$(( now - 14*86400 ))
        [[ "$updated_epoch" -ge "$cutoff" ]] && bundle=1
      else
        # no update dates available -> fall back to "all"
        bundle=1
      fi
    fi
    if [[ $bundle -eq 1 ]]; then
      DOC_BUNDLED+=("$name")
      if [[ -n "${MM_WORKTREE_TASK_FAKE_DOCS_DIR:-}" ]]; then
        DOC_BODY["$name"]="$(cat "${MM_WORKTREE_TASK_FAKE_DOCS_DIR}/${name}" 2>/dev/null || echo "")"
      else
        DOC_BODY["$name"]="$(cp_cli planner items documents get "$UUID" "$name" 2>/dev/null || echo "")"
      fi
    fi
  done < <(echo "$list_json" | python3 -c '
import json, sys
data = json.load(sys.stdin)
for d in data.get("documents", []):
    name = d.get("name", "")
    updated = d.get("updated_at", "")
    print(name + "\t" + str(updated))
')
}

fetch_item
fetch_docs

PARENT_ID=""
PARENT_JSON=""
if [[ -n "$ITEM_JSON" ]]; then
  PARENT_ID="$(echo "$ITEM_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin).get("parent_id") or "")')"
fi
if [[ -n "$PARENT_ID" && -z "${MM_WORKTREE_TASK_FAKE_ITEM:-}" ]]; then
  PARENT_JSON="$(cp_cli planner items get "$PARENT_ID" --json 2>/dev/null || echo "")"
fi

# --- build bundle content ---
build_bundle() {
  echo "# Bundle for ${SLUG}"
  echo
  if [[ -n "$ITEM_JSON" ]]; then
    echo "## Item"
    echo '```json'
    echo "$ITEM_JSON"
    echo '```'
    echo
  fi
  if [[ -n "$PARENT_JSON" ]]; then
    echo "## Parent"
    echo '```json'
    echo "$PARENT_JSON"
    echo '```'
    echo
  fi
  echo "## Documents"
  if [[ ${#DOC_NAMES[@]} -eq 0 ]]; then
    echo "(none)"
  else
    for n in "${DOC_NAMES[@]}"; do
      local marker="available only"
      for b in "${DOC_BUNDLED[@]:-}"; do [[ "$b" == "$n" ]] && marker="bundled below"; done
      echo "- ${n} (${marker})"
    done
  fi
  echo
  for n in "${DOC_BUNDLED[@]:-}"; do
    [[ -z "$n" ]] && continue
    echo "## Document: ${n}"
    echo '```'
    echo "${DOC_BODY[$n]:-}"
    echo '```'
    echo
  done
}

BUNDLE_CONTENT="$(build_bundle)"

# --- build prompt ---
DOC_NAME_LIST="$(IFS=', '; echo "${DOC_NAMES[*]:-none}")"

build_prompt() {
  cat <<EOF
You are running non-interactively (-p) as an implementation subagent inside a git worktree of the ${REPO_NAME} repo.

WORKTREE: ${WORKTREE}   BRANCH: ${BRANCH}
Your current working directory IS the worktree. Never cd elsewhere. Never touch \`git config\`. Never \`git push\`. Never \`git stash\`.

Project rules that apply to you:
- Spec-driven: if you change behaviour, update the matching spec/*.md requirement in the same commit (add a new numbered requirement ID following the file's existing scheme; keep the rationale line).
- Python 3.12, type hints, Pydantic v2. Async tests use ONLY \`@pytest.mark.asyncio\` + \`async def\`.
- Do NOT run the full test suite. Run ONLY the test files you touched or created.
- Do not get stuck fixing test infrastructure. If a fixture is missing, mock it.
- Read the spec and existing tests around the code before editing. Match existing names exactly.
- Keep the diff minimal and focused on this task.

When done:
1. \`git add -A && git commit -m "<type>: <summary> (${UUID:-brief})"\` on branch ${BRANCH}. First verify: \`git rev-parse --abbrev-ref HEAD\` must print ${BRANCH}, otherwise STOP and report.
2. Write your report to: ${RESULT_FILE} with sections: Summary, Files changed, Tests run, Decisions, Open questions.
3. One-line stdout acknowledgement only.

## Task — ${UUID:-brief only}: ${TITLE}

Read the bundle at ${BUNDLE_FILE} fully before editing. The item body is the requirement; documents are supporting context. Documents available on the item (not all bundled): ${DOC_NAME_LIST}.
EOF
  [[ -n "$FOCUS" ]] && echo -e "\nFocus: ${FOCUS}"
  if [[ -n "$BRIEF" ]]; then
    echo
    echo "## Brief"
    cat "$BRIEF"
  fi
}

PROMPT_CONTENT="$(build_prompt)"

CMD="cd ${WORKTREE} && setsid nohup timeout ${TIMEOUT} ask-minimax-run --model \"MiniMax-M3[1m]\" --allowedTools \"${ALLOWLIST}\" --permission-mode acceptEdits --add-dir ${REPORT_DIR} -p \"\$(cat ${PROMPT_FILE})\" > ${STDOUT_FILE} 2>&1"

if [[ $DRYRUN -eq 1 ]]; then
  echo "[dryrun] mm-worktree-task — no changes will be made"
  echo "  Repo:         ${REPO_ROOT}"
  echo "  cp-cli from:  ${CP_CLI_DIR:-<not found>}"
  echo "  cp-cli ctx:   ${CP_CONTEXT:-<cp-cli default>}"
  echo "  Slug:         ${SLUG}"
  echo "  Base:         ${BASE}"
  echo "  UUID:         ${UUID:-(none, --brief only)}"
  echo "  Worktree:     ${WORKTREE}"
  echo "  Branch:       ${BRANCH}"
  echo "  Report dir:   ${REPORT_DIR}"
  echo "  Bundle path:  ${BUNDLE_FILE}"
  echo "  Prompt path:  ${PROMPT_FILE}"
  echo "  Timeout:      ${TIMEOUT}s"
  echo "  Allowlist:    ${ALLOWLIST}"
  echo
  echo "--- Bundle manifest ---"
  echo "$BUNDLE_CONTENT"
  echo
  echo "--- Prompt ---"
  echo "$PROMPT_CONTENT"
  echo
  echo "--- Command ---"
  echo "$CMD"
  exit 0
fi

# --- real run ---
mkdir -p "$REPORT_DIR"
echo "$BUNDLE_CONTENT" > "$BUNDLE_FILE"
echo "$PROMPT_CONTENT" > "$PROMPT_FILE"

if [[ -d "$WORKTREE" ]] || git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/${BRANCH}"; then
  err "worktree or branch already exists for slug '${SLUG}' (${WORKTREE} / ${BRANCH})"
  exit 1
fi

info "creating worktree ${WORKTREE} on branch ${BRANCH}"
git -C "$REPO_ROOT" worktree add "$WORKTREE" -b "$BRANCH" "$BASE"

info "launching ask-minimax-run (detached, timeout ${TIMEOUT}s)"
(
  cd "$WORKTREE"
  start=$(date +%s)
  set +e
  setsid nohup timeout "$TIMEOUT" ask-minimax-run --model "MiniMax-M3[1m]" \
    --allowedTools "$ALLOWLIST" --permission-mode acceptEdits \
    --add-dir "$REPORT_DIR" -p "$(cat "$PROMPT_FILE")" \
    > "$STDOUT_FILE" 2>&1
  rc=$?
  set -e
  echo "${SLUG} rc=${rc} elapsed=$(( $(date +%s) - start ))s" >> "$LAUNCH_LOG"
) &
disown

ok "launched"
echo "  Report dir:  ${REPORT_DIR}"
echo "  Prompt file: ${PROMPT_FILE}"
echo "  Stdout file: ${STDOUT_FILE}"
echo "  Launch log:  ${LAUNCH_LOG}"
echo "  poll with: tail -f ${STDOUT_FILE}"
