#!/usr/bin/env bash
#
# PreToolUse Hook: Validate Edit paths for RLCR loop
#
# Blocks Claude from editing:
# - Todos files (should use native Task tools instead)
# - Prompt files (read-only, generated by Codex)
# - State files (managed by hooks, not Claude)
# - Wrong round number contract files
# - Goal tracker edits outside the active loop or that alter the immutable section
#

set -euo pipefail

# Load shared functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
source "$SCRIPT_DIR/lib/loop-common.sh"

# ========================================
# Parse Hook Input
# ========================================

HOOK_INPUT=$(cat)
TOOL_NAME=$(echo "$HOOK_INPUT" | jq -r '.tool_name // ""')

if [[ "$TOOL_NAME" != "Edit" ]]; then
    exit 0
fi

FILE_PATH=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // ""')
FILE_PATH_LOWER=$(to_lower "$FILE_PATH")

# Extract session_id from hook input for session-aware loop filtering
HOOK_SESSION_ID=$(extract_session_id "$HOOK_INPUT")

# ========================================
# Block Todos and Prompt Files
# ========================================

if is_round_file_type "$FILE_PATH_LOWER" "todos"; then
    PROJECT_ROOT="$(resolve_project_root)" || exit 0
    LOOP_BASE_DIR="$PROJECT_ROOT/.humanize/rlcr"
    LOOP_DIR=$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")
    if [[ -z "$LOOP_DIR" ]] || ! is_allowlisted_file "$FILE_PATH" "$LOOP_DIR"; then
        todos_blocked_message "Edit" >&2
        exit 2
    fi
fi

if is_round_file_type "$FILE_PATH_LOWER" "prompt"; then
    prompt_write_blocked_message >&2
    exit 2
fi

# ========================================
# Methodology Analysis Phase Edit Restriction
# ========================================
# During methodology analysis, only methodology artifacts can be edited.
# This prevents source code modifications after Codex has signed off.
# This check MUST come before the humanize loop dir early exit below.

PROJECT_ROOT="${PROJECT_ROOT:-$(resolve_project_root 2>/dev/null || true)}"
[[ -z "$PROJECT_ROOT" ]] && exit 0
LOOP_BASE_DIR="${LOOP_BASE_DIR:-$PROJECT_ROOT/.humanize/rlcr}"
# Use only the session-matched loop. Do NOT fall back to an unfiltered search,
# as that would incorrectly restrict unrelated sessions opened in the same repo.
# Limitation: Spawned agents (different session_id) are not restricted by hooks;
# their sanitization is enforced by the analysis prompt.
_MA_LOOP_DIR="${LOOP_DIR:-$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")}"

if [[ -n "$_MA_LOOP_DIR" ]] && [[ -f "$_MA_LOOP_DIR/methodology-analysis-state.md" ]]; then
    # If realpath fails (file doesn't exist yet on BSD/macOS), resolve parent dir
    _ma_real_path=$(realpath "$FILE_PATH" 2>/dev/null || echo "")
    if [[ -z "$_ma_real_path" ]]; then
        _ma_parent=$(realpath "$(dirname "$FILE_PATH")" 2>/dev/null || echo "")
        [[ -n "$_ma_parent" ]] && _ma_real_path="$_ma_parent/$(basename "$FILE_PATH")"
    fi
    _ma_real_loop=$(realpath "$_MA_LOOP_DIR" 2>/dev/null || echo "")
    # Fallback to raw paths when realpath is unavailable (older macOS/BSD)
    # Ensure paths are absolute and reject ".." to prevent traversal bypasses.
    if [[ -z "$_ma_real_path" ]]; then
        if [[ "$FILE_PATH" == *".."* ]]; then
            echo "# Edit Blocked During Methodology Analysis

Path contains traversal segments that cannot be resolved without realpath." >&2
            exit 2
        fi
        # Fail closed if the leaf is a symlink we cannot resolve; the raw
        # path would satisfy the loop-dir prefix check while pointing at a
        # target outside the loop, letting the basename allowlist approve
        # edits to arbitrary files during methodology-analysis mode.
        if [[ -L "$FILE_PATH" ]]; then
            echo "# Edit Blocked During Methodology Analysis

Path is a symlink that cannot be resolved without realpath." >&2
            exit 2
        fi
        if [[ "$FILE_PATH" == /* ]]; then
            _ma_real_path="$FILE_PATH"
        else
            _ma_real_path="$PROJECT_ROOT/$FILE_PATH"
        fi
    fi
    if [[ -z "$_ma_real_loop" ]]; then
        if [[ "$_MA_LOOP_DIR" == /* ]]; then
            _ma_real_loop="$_MA_LOOP_DIR"
        else
            _ma_real_loop="$PROJECT_ROOT/$_MA_LOOP_DIR"
        fi
    fi
    if [[ "$_ma_real_path" == "$_ma_real_loop/"* ]]; then
        _ma_basename=$(basename "$_ma_real_path")
        case "$_ma_basename" in
            methodology-analysis-report.md|methodology-analysis-done.md)
                exit 0
                ;;
        esac
    fi
    echo "# Edit Blocked During Methodology Analysis

During the methodology analysis phase, only methodology artifacts can be edited.
Allowed: methodology-analysis-report.md, methodology-analysis-done.md" >&2
    exit 2
fi

# ========================================
# Check if File is in .humanize/rlcr
# ========================================

if ! is_in_humanize_loop_dir "$FILE_PATH"; then
    exit 0
fi

# ========================================
# Find Active Loop and Current Round
# ========================================

PROJECT_ROOT="${PROJECT_ROOT:-$(resolve_project_root 2>/dev/null || true)}"
[[ -z "$PROJECT_ROOT" ]] && exit 0
LOOP_BASE_DIR="${LOOP_BASE_DIR:-$PROJECT_ROOT/.humanize/rlcr}"
ACTIVE_LOOP_DIR="${LOOP_DIR:-$(find_active_loop "$LOOP_BASE_DIR" "$HOOK_SESSION_ID")}"

if [[ -z "$ACTIVE_LOOP_DIR" ]]; then
    exit 0
fi

# Detect if we're in Finalize Phase (finalize-state.md exists)
STATE_FILE_TO_PARSE=$(resolve_active_state_file "$ACTIVE_LOOP_DIR")
IS_FINALIZE_PHASE=false
if [[ "$STATE_FILE_TO_PARSE" == *"/finalize-state.md" ]]; then
    IS_FINALIZE_PHASE=true
fi

# Parse state file using strict validation (fail closed on malformed state)
if ! parse_state_file_strict "$STATE_FILE_TO_PARSE" 2>/dev/null; then
    echo "Error: Malformed state file, blocking operation for safety" >&2
    exit 1
fi
CURRENT_ROUND="$STATE_CURRENT_ROUND"

# ========================================
# Block State File Edits (state.md, finalize-state.md, methodology-analysis-state.md)
# ========================================
# NOTE: Check most specific patterns first because is_state_file_path matches any *state.md

if is_methodology_analysis_state_file_path "$FILE_PATH_LOWER"; then
    methodology_analysis_state_file_blocked_message >&2
    exit 2
fi

if is_finalize_state_file_path "$FILE_PATH_LOWER"; then
    finalize_state_file_blocked_message >&2
    exit 2
fi

if is_state_file_path "$FILE_PATH_LOWER"; then
    state_file_blocked_message >&2
    exit 2
fi

if [[ "$IS_FINALIZE_PHASE" == "true" ]] && is_round_file_type "$FILE_PATH_LOWER" "contract"; then
    finalize_contract_blocked_message "edit" >&2
    exit 2
fi

# ========================================
# Block Plan Backup Edits
# ========================================

FILENAME=$(basename "$FILE_PATH")
if [[ "$FILENAME" == "plan.md" ]]; then
    if [[ "$FILE_PATH" == *"/.humanize/rlcr/"* ]]; then
        FALLBACK="Editing plan.md backup is not allowed during RLCR loop."
        REASON=$(load_and_render_safe "$TEMPLATE_DIR" "block/plan-backup-protected.md" "$FALLBACK")
        echo "$REASON" >&2
        exit 2
    fi
fi

# ========================================
# Validate Goal Tracker Edits
# ========================================

if is_goal_tracker_path "$FILE_PATH_LOWER"; then
    GOAL_TRACKER_PATH="$ACTIVE_LOOP_DIR/goal-tracker.md"
    NORMALIZED_FILE_PATH=$(_normalize_path "$FILE_PATH")
    NORMALIZED_GOAL_TRACKER_PATH=$(_normalize_path "$GOAL_TRACKER_PATH")

    if [[ "$NORMALIZED_FILE_PATH" != "$NORMALIZED_GOAL_TRACKER_PATH" ]]; then
        goal_tracker_blocked_message "$CURRENT_ROUND" "$GOAL_TRACKER_PATH" >&2
        exit 2
    fi

    if [[ "$CURRENT_ROUND" -gt 0 ]]; then
        if ! echo "$HOOK_INPUT" | jq -e '.tool_input | has("old_string") and has("new_string")' >/dev/null 2>&1; then
            echo "Error: Missing required field: tool_input.old_string or tool_input.new_string" >&2
            exit 1
        fi
        OLD_STRING=$(echo "$HOOK_INPUT" | jq -r '.tool_input.old_string // ""')
        if [[ -z "$OLD_STRING" ]]; then
            echo "Error: Missing required field: tool_input.old_string" >&2
            exit 1
        fi

        NEW_STRING=$(echo "$HOOK_INPUT" | jq -r '.tool_input.new_string // ""')
        REPLACE_ALL=$(echo "$HOOK_INPUT" | jq -r '.tool_input.replace_all // false')

        if ! UPDATED_CONTENT=$(preview_edit_result "$GOAL_TRACKER_PATH" "$OLD_STRING" "$NEW_STRING" "$REPLACE_ALL" 2>/dev/null); then
            goal_tracker_blocked_message "$CURRENT_ROUND" "$GOAL_TRACKER_PATH" >&2
            exit 2
        fi

        if ! goal_tracker_mutable_update_allowed "$GOAL_TRACKER_PATH" "$UPDATED_CONTENT"; then
            goal_tracker_blocked_message "$CURRENT_ROUND" "$GOAL_TRACKER_PATH" >&2
            exit 2
        fi
    fi

    exit 0
fi

# ========================================
# Validate Summary/Contract File Round Number
# ========================================

if is_round_file_type "$FILE_PATH_LOWER" "summary" || is_round_file_type "$FILE_PATH_LOWER" "contract"; then
    # Extract filename from path (portable - works in bash and zsh)
    CLAUDE_FILENAME=$(echo "$FILE_PATH" | sed -n 's|.*\.humanize/rlcr/[^/]*/\(.*\)$|\1|p')
    if [[ -z "$CLAUDE_FILENAME" ]]; then
        CLAUDE_FILENAME=$(echo "$FILE_PATH" | sed -n 's|.*\.humanize/rlcr/\(.*\)$|\1|p')
    fi

    if [[ -n "$CLAUDE_FILENAME" ]]; then
        CLAUDE_ROUND=$(extract_round_number "$CLAUDE_FILENAME")
        FILE_TYPE=$([[ "$FILE_PATH_LOWER" == *"-contract.md" ]] && echo "contract" || echo "summary")

        if [[ -n "$CLAUDE_ROUND" ]] && [[ "$CLAUDE_ROUND" != "$CURRENT_ROUND" ]] && ! is_allowlisted_file "$FILE_PATH" "$ACTIVE_LOOP_DIR"; then
            CORRECT_PATH="$ACTIVE_LOOP_DIR/round-${CURRENT_ROUND}-${FILE_TYPE}.md"
            FALLBACK="# Wrong Round Number

You tried to {{ACTION}} round-{{CLAUDE_ROUND}}-{{FILE_TYPE}}.md but current round is **{{CURRENT_ROUND}}**.

Edit: {{CORRECT_PATH}}"
            load_and_render_safe "$TEMPLATE_DIR" "block/wrong-round-number.md" "$FALLBACK" \
                "ACTION=edit" \
                "CLAUDE_ROUND=$CLAUDE_ROUND" \
                "FILE_TYPE=$FILE_TYPE" \
                "CURRENT_ROUND=$CURRENT_ROUND" \
                "CORRECT_PATH=$CORRECT_PATH" >&2
            exit 2
        fi
    fi
fi

exit 0
