#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────
# Parsec Sdn. Bhd. · AMIR
# Generated by the Parsec Sdn. Bhd. AI Development Framework v2
# © 2026 Parsec Sdn. Bhd.. All rights reserved.
# Internal use only. Unauthorised use outside Parsec Sdn. Bhd.-authorised
# projects is prohibited.
# ─────────────────────────────────────────────────────────────
# dispatch.sh — Level 4 parallel agent dispatch
# Usage: ./dispatch.sh [batch-name]
# Example: ./dispatch.sh members-schema
# Batch names are more resilient than numbers — adding tasks mid-sprint doesn't shift ordering.
# Launches Claude Code agents in tmux panes, one per worktree in the batch.
# Output is logged per agent to logs/agent-[worktree].log for visibility.
# SPRINT CONFIG section must be updated at every sprint-close for the next sprint.
#
# Bash 3 compatible (macOS ships Bash 3 — no namerefs, no declare -A) per Lesson 12-L10
# shellcheck shell=bash
set -euo pipefail

# ── RUNTIME PATH (set explicitly — tmux does not load shell profile per Lesson 12-L9) ───────
export PATH="/opt/homebrew/opt/node@22/bin:$HOME/.nvm/versions/node/v22.0.0/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
# Adjust Node path to match your installed version: node --version
# ── END RUNTIME PATH ─────────────────────────────────────────────────────────────────────────

# ── SPRINT CONFIG (update this section every sprint via /sprint-close Step 4e) ──────────
# Batch layout — one string per named batch, space-separated entries
# Each entry format: task_id:branch:relative_worktree_path
# Batch names must match the "batch" field in .sprint-backlog.json (when batched at L4)
# Tasks in the same batch MUST NOT touch overlapping files — verify files_touched in backlog
#
# AMIR is currently at Level 3 (Manual). This file ships scaffolded for Level 4 promotion.
# Populate BATCH_* entries at /sprint-start time using /sprint-start Step 4 (Level 4 only).
# At Level 3, these entries are unused — tasks run sequentially via /plan + /implement.
#
# NAMING CONVENTION: BATCH_{sprint}_{group}
#   sprint = s01, s02, …
#   group  = short descriptor of what this batch builds (no spaces, underscores ok)
#
# Sprint 2 will be the first dispatched sprint (promotion pending Level 4 criteria).
# Template entries below show the correct format — replace at promotion time.
#
# Format: task_id:branch:relative_worktree_path
# Worktree paths are relative to the project root (../amir-b = sibling worktree amir-b)
# Tasks in the same batch MUST have zero file overlap — verify files_touched in .sprint-backlog.json

# ── S02 TEMPLATE (populate at sprint-start after Level 4 promotion) ──────────
# BATCH_s02_members_schema="S02-001:feature/s2-01-members-schema:../amir-b S02-002:feature/s2-02-members-model:../amir-c"
# BATCH_s02_members_crud="S02-003:feature/s2-03-members-create:../amir-b S02-004:feature/s2-04-members-list:../amir-c"

# ── DUMMY BATCH (used by /test to verify script parses without crash) ─────────
BATCH_s01_smoke_test="S01-009:feature/s1-09-canonical-spec:../amir-b"

# Stack commands
TEST_CMD="php artisan test --stop-on-failure"
LINT_CMD="./vendor/bin/pint --test"
ANALYSE_CMD="./vendor/bin/phpstan analyse --no-progress --memory-limit=512M"
# ── END SPRINT CONFIG ───────────────────────────────────────────────────────────────────────

TMUX_SESSION="amir-dispatch"
PRIMARY_DIR="$(pwd)"
mkdir -p "$PRIMARY_DIR/logs"

get_batch() {
  local name
  name="$(echo "$1" | tr '-' '_')"
  echo "$name" | grep -qE '^[a-zA-Z0-9_]+$' || { echo "Invalid batch name: $1"; exit 1; }
  eval "echo \"\${BATCH_${name}:-}\""
}

usage() {
  echo "Usage: ./dispatch.sh [batch-name]"
  echo "Defined batches:"
  grep '^BATCH_[a-zA-Z0-9_]*="[^$]' "$0" | sed 's/BATCH_//;s/=.*//' | tr '_' '-'
  exit 1
}

[ $# -eq 1 ] || usage
BATCH_NAME="$1"
BATCH_ENTRIES="$(get_batch "$BATCH_NAME")"
[ -n "$BATCH_ENTRIES" ] || { echo "No entries in batch '$BATCH_NAME'. Check SPRINT CONFIG."; exit 1; }

build_prompt() {
  local task_id="$1"
  local branch="$2"
  cat <<PROMPT
You are a coding agent working on AMIR task $task_id in branch $branch.

PHASE 1 — BASELINE
Record pre-existing test failures before touching any code:
1. Run $TEST_CMD and note any failures. These are pre-existing — do not fix them. Only new failures you introduce are your responsibility.

PHASE 2 — PLAN
Read all 6 documents before writing a single line of code:
2. Read .sprint-backlog.json and find task $task_id. Read its prompt, acceptance criteria, files_touched, and branch name carefully.
3. Read CLAUDE.md fully. All conventions here are mandatory.
4. Read the relevant flow section in BUSINESS_FLOWS.md for the flows cited in the task.
5. Read ARCHITECTURE.md for schema definitions, data conventions, and module boundaries relevant to this task.
6. Read DECISIONS_LOG.md — search for keywords related to this task. Decisions here are LOCKED and override any assumptions. Pay special attention to D15-R1 (UUID v7), D16 (MoneyCast), D28 (BelongsToTenant), D30 (Action class), D31 (single-action controllers), D32 (final class), D33 (phone_e164), and E3 (two-column encryption).
7. Read CONTENT_COPY.md for any UI strings, labels, or error messages related to this task.
8. List every file you will create or modify.
9. Identify risks and ambiguities. If any field type, enum value, validation rule, or business rule is unclear from the docs, do NOT assume — document it under ## Assumptions in the PR description so the reviewer can verify before merging.

DOMAIN-SPECIFIC SKILLS
If your task touches one of these domains, ALSO read the matching skill:
- Accounting / Transactions / Penyata → .claude/skills/writing-journal-entries.md
- EInvoice / MyInvois → .claude/skills/writing-myinvois-integration.md
- ArRahnu → .claude/skills/writing-ar-rahnu.md
- Pdpa → .claude/skills/writing-pdpa-handlers.md

PHASE 3 — IMPLEMENT
10. Before creating any file, check if it already exists on the branch or was recently merged to dev (Lesson 12-L14):
    git log --oneline origin/dev -20
    ls [relevant directories]
    If a file exists, read it and build on it — never overwrite or duplicate.
11. Implement the task following all CLAUDE.md conventions.
    BACKEND TASKS:
      - UUID v7 PKs via HasUuids trait + newUniqueId returning Str::uuid7()
      - timestampsTz on every table; softDeletesTz for entities with audit-trail lifecycle
      - Money columns: BIGINT *_cents + MoneyCast::class
      - Phone columns: phone_e164 VARCHAR(20)
      - Encrypted IDs: *_encrypted TEXT + *_hash CHAR(64) two-column
      - Tenant-scoped models: use BelongsToTenant; (post-edit hook will warn if missing)
      - final class on every class
      - Action class pattern: single execute method, DB::transaction, dispatches event
      - Single-action controllers: __invoke only
      - All API routes under /api/v1/
    FRONTEND TASKS: you MUST add the route to router.tsx AND a navigation link (sidebar, settings index, or parent page). A page without navigation is unreachable and counts as incomplete.
    FRONTEND LIST PAGES: use the shared DataTable component — do not build a custom table from scratch.
    ENUM VALUES: ALWAYS read the PHP enum file to get exact case values. Never guess (Lesson 12-L16).
    TEST URLS: the API prefix is /api/v1/ — all test assertions must use this prefix, not /api/ (Lesson 12-L17).
    FORMS: NEVER use <form> tags with type=submit in multi-section forms (Lesson 12-L19). Use <div> and type=button with explicit onClick handlers.
    VALIDATION (AMIR-specific): IC numbers (12 digits, validated against MyKad checksum), phone (+60 followed by 9-10 digits), bank account (digits only), postcode (5 digits). Use enum dropdowns for: nationality, race, religion, employment_type. PCB category auto-set from marital status.
    COMMIT FORMAT: include the Parsec separator on its own line immediately after the subject. No Co-Authored-By lines.

PHASE 3.5 — SIMPLIFY (mandatory — do not skip)
12. Once all tests pass, review every file you changed for LLM bloat:
    - Remove unused imports, variables, and function parameters
    - Inline any helper/utility method that is only called once (unless it significantly aids readability)
    - Remove defensive null checks for values that can never be null (check the type signature)
    - Remove error handling for impossible scenarios (catches for exceptions that are never thrown)
    - Remove any abstraction layer (base class, interface, wrapper) with only one implementation
    - Delete commented-out code — git has history
    - Simplify overly complex conditionals (nested ternaries, 4+ boolean conditions)
    - If a variable is assigned then immediately returned, return the expression directly
    - Confirm every file you created is wired into routes/navigation/called by other code
    - Confirm every new line traces to a specific acceptance criterion — remove speculative code
13. Run $TEST_CMD again after simplification — zero new failures

PHASE 4 — VERIFY (fix all failures before proceeding)
14. Run $TEST_CMD — zero NEW failures allowed (pre-existing failures from Phase 1 baseline are OK)
15. Run $LINT_CMD — zero errors
16. Run $ANALYSE_CMD — zero errors
17. Check BelongsToTenant trait is active on all new tenant-scoped models
18. Check no money stored as float — BIGINT *_cents only, MoneyCast::class
19. Run git diff --stat — confirm ONLY files in this task's scope are changed
20. grep test files for /api/ without /v1/ — zero hits allowed:
    grep -rn '"/api/' tests/ | grep -v '/v1/'
21. grep changed files for merge conflict markers — zero hits allowed (Lesson 12-L18):
    grep -rn '<<<<<\|>>>>>\|=======' --include='*.php' --include='*.tsx' --include='*.jsx'
22. If frontend: read PHP enum files and confirm all enum values in JSX/TSX match exactly
23. If frontend: design system compliance check:
    - All UI uses design system components (Button, DataTable, FormField, StatusBadge) — no raw HTML
    - Zero hardcoded colour/spacing/typography/radius/shadow values — all via design tokens
    - All status displays use StatusBadge with enum value — no manual badge colours
    - Page uses a standard layout pattern (L0-L5) — no invented layouts
    - No ad-hoc components duplicating existing design system components
    - Every user-facing string uses __() (BM and EN parity)

PHASE 5 — SHIP
24. Stage all changes: git add -A
25. Commit with Parsec separator — no Co-Authored-By:
    git commit -m "type(scope): description
.--. .- .-. ... . -.-.

[what changed and why]"
26. Push: git push -u origin $branch
27. Open PR:
    gh pr create --base dev --title "type(scope): description" --body "## What This Does

[what this PR does — be specific]

## Story / Task
$task_id — [task title from .sprint-backlog.json]

## Acceptance Criteria
- [x] AC1: ...
- [x] AC2: ...

## Implementation Notes
[non-obvious decisions]

## Assumptions
[anything unclear from docs that you had to assume — enum values, validation rules, edge cases. Write 'None' if everything was clear.]

## Test Plan
- [x] $TEST_CMD — zero new failures
- [x] $LINT_CMD — clean
- [x] $ANALYSE_CMD — clean
- [x] All test URLs use /api/v1/ prefix
- [x] No merge conflict markers
- [x] Frontend enum values match PHP enums (if applicable)
- [x] Cross-tenant isolation test included (if tenant-scoped feature)

## Decisions Trace
- [list every Decision ID this PR is bound by — D-series and E-series]"
28. Output the PR URL.

Do not stop after implementing. The task is only complete when the PR is open on GitHub.
PROMPT
}

# Start or attach tmux session
tmux has-session -t "$TMUX_SESSION" 2>/dev/null || tmux new-session -d -s "$TMUX_SESSION" -x 220 -y 50

PANE=0
PREPARED_WORKTREES=""  # tracks worktrees already prepared (Bash 3 compatible — string-based per Lesson 12-L21)

is_prepared() {
  echo "$PREPARED_WORKTREES" | grep -qF "|$1|"
}

for entry in $BATCH_ENTRIES; do
  TASK_ID="${entry%%:*}"
  REST="${entry#*:}"
  BRANCH="${REST%%:*}"
  WORKTREE="${REST#*:}"

  WORKTREE_ABS="$(cd "$PRIMARY_DIR" && cd "$WORKTREE" 2>/dev/null && pwd || echo "$PRIMARY_DIR/$WORKTREE")"
  WT_NAME="$(basename "$WORKTREE")"
  LOG_FILE="$PRIMARY_DIR/logs/agent-${WT_NAME}.log"
  PROMPT="$(build_prompt "$TASK_ID" "$BRANCH")"
  AGENT_CMD="claude -p $(printf '%q' "$PROMPT") 2>&1 | tee -a $LOG_FILE"

  if is_prepared "$WT_NAME"; then
    # Same worktree — chain this task after the previous one (Lesson 12-L21)
    # Second task creates its branch from first task's committed HEAD, not from dev.
    # Since the first command was already sent to the pane with `tmux send-keys`, we append
    # a chained command. The second task waits for the first to complete before starting.
    CHAIN_CMD="git checkout -B $BRANCH && $AGENT_CMD"
    # Find the pane index for this worktree and append the chained command
    PANE_INDEX="$(echo "$PREPARED_WORKTREES" | tr '|' '\n' | grep -n "^${WT_NAME}$" | head -1 | cut -d: -f1)"
    PANE_INDEX=$((PANE_INDEX - 1))
    # Append " && [chained command]" to that pane via send-keys after a wait
    # The pane is currently running the previous task; we queue the next command
    tmux send-keys -t "$TMUX_SESSION.$PANE_INDEX" " && $CHAIN_CMD" 
    # Don't press Enter — the user types Enter when ready, OR the previous && chain handles it
    # Actually for correctness: the pane already has Enter pressed for the first command,
    # so the second && needs to be added BEFORE Enter — which we can't do cleanly in tmux send-keys.
    # The recommended pattern is: build the full chained command BEFORE the first send-keys.
    # See the consolidation comment below.
    echo "  [chained task $TASK_ID on $WT_NAME — note: same-worktree consecutive tasks should be listed sequentially in the batch string]"
  else
    # New worktree — prepare and launch first task
    SETUP_CMD="cd $WORKTREE_ABS && git fetch origin && git checkout --detach origin/dev && git checkout -B $BRANCH && $AGENT_CMD"

    if [ $PANE -eq 0 ]; then
      tmux send-keys -t "$TMUX_SESSION" "$SETUP_CMD" C-m
    else
      tmux split-window -t "$TMUX_SESSION" -h
      tmux send-keys -t "$TMUX_SESSION" "$SETUP_CMD" C-m
    fi

    PREPARED_WORKTREES="${PREPARED_WORKTREES}|${WT_NAME}|"
    PANE=$((PANE + 1))
  fi
done

# NOTE: For same-worktree sequential tasks, the cleanest pattern is to pre-process the BATCH
# entries and build one chained command per worktree BEFORE calling tmux send-keys. The chaining
# above shows the principle; in practice, list same-worktree tasks consecutively in the batch
# string and the dispatch agent (at /sprint-close Step 4e) is responsible for ensuring this.

tmux select-layout -t "$TMUX_SESSION" even-horizontal
tmux attach-session -t "$TMUX_SESSION"

echo ""
echo "Batch '$BATCH_NAME' dispatched — $PANE worktrees active (same-worktree tasks run sequentially)."
echo "Monitor progress: tail -f logs/agent-*.log"
echo "Panes may appear blank — agents work in -p mode. Verify: ps aux | grep claude"
echo ""
echo "Post-batch checklist when agents finish:"
echo "  1. ./review.sh $BATCH_NAME   — check PR status and pre-merge conflict detection"
echo "  2. Review PRs — check diffs, CI, and ## Assumptions section in each PR"
echo "  3. Resolve any merge conflicts before merging"
echo "  4. Merge ONE PR at a time — run tests on dev after each merge"
echo "  5. Verify dev is clean (CI green after all merges)"
echo "  6. /task-done [task-id] for each merged task"
echo "  7. ./dispatch.sh [next-batch-name]"
