#!/usr/bin/env bash
set -euo pipefail

# ─────────────────────────────────────────────────────────────────────────────
# 01-stash-manager.sh — Smart Stash Management for SecureGit
#
# Interactive menu-driven stash manager with named stashes, search,
# patch export, snapshot integration, and safe drop with confirmation.
#
# Usage:
#   ./01-stash-manager.sh              # Launch interactive menu
#   ./01-stash-manager.sh list         # List stashes (non-interactive)
#   ./01-stash-manager.sh save [msg]   # Quick save with optional message
#   ./01-stash-manager.sh pop [n]      # Quick pop by index
# ─────────────────────────────────────────────────────────────────────────────

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/lib/securegit-common.sh"

sg_require_repo

# ─── Constants ───────────────────────────────────────────────────────────────
_REPO_ROOT="$(git rev-parse --show-toplevel)"
_PATCH_DIR="$_REPO_ROOT/.securegit/patches"

# ─── Helpers ─────────────────────────────────────────────────────────────────

# Return the number of stash entries
_stash_count() {
    git stash list 2>/dev/null | wc -l | tr -d ' '
}

# Print a formatted stash list with branch, age, and message
_stash_list_detailed() {
    local count
    count="$(_stash_count)"

    if (( count == 0 )); then
        sg_info "No stashes found."
        return 1
    fi

    printf "  ${_C_BOLD}%-6s %-20s %-14s %s${_C_RESET}\n" "INDEX" "BRANCH" "AGE" "MESSAGE"
    sg_divider

    local i=0
    while IFS= read -r line; do
        local ref branch msg age_epoch age_human
        ref="stash@{$i}"

        # Extract branch name from stash message (format: "WIP on branch: hash msg" or "On branch: hash msg")
        branch="$(echo "$line" | sed -E 's/^stash@\{[0-9]+\}: (WIP on |On )([^:]+):.*/\2/')"
        msg="$(echo "$line" | sed -E 's/^stash@\{[0-9]+\}: [^:]+: [a-f0-9]+ //')"

        # Truncate long messages
        if (( ${#msg} > 50 )); then
            msg="${msg:0:47}..."
        fi

        # Get age
        age_epoch="$(git log -1 --format='%cr' "$ref" 2>/dev/null || echo "unknown")"

        printf "  ${_C_CYAN}%-6s${_C_RESET} %-20s ${_C_DIM}%-14s${_C_RESET} %s\n" \
            "[$i]" "$branch" "$age_epoch" "$msg"

        (( i++ )) || true
    done < <(git stash list 2>/dev/null)

    echo
    sg_info "Total: $count stash(es)"
}

# Show detailed diff stats for a single stash
_stash_show_detail() {
    local idx="${1:-0}"
    local ref="stash@{$idx}"

    if ! git rev-parse --verify "$ref" &>/dev/null; then
        sg_error "Stash [$idx] does not exist."
        return 1
    fi

    sg_header "Stash [$idx] Details"

    local entry
    entry="$(git stash list | sed -n "$((idx + 1))p")"
    sg_info "Entry: $entry"
    echo

    sg_info "Files changed:"
    git stash show "$ref" 2>/dev/null || sg_warn "Could not show stash diff stats."
    echo

    if sg_confirm "Show full diff?"; then
        git stash show -p "$ref" 2>/dev/null | head -200
        local total_lines
        total_lines="$(git stash show -p "$ref" 2>/dev/null | wc -l)"
        if (( total_lines > 200 )); then
            sg_info "... ($((total_lines - 200)) more lines truncated)"
        fi
    fi
}

# ─── Operations ──────────────────────────────────────────────────────────────

op_list() {
    sg_header "Stash List"
    _stash_list_detailed
}

op_save() {
    local msg="${1:-}"

    if [[ -z "$(git status --porcelain 2>/dev/null)" ]]; then
        sg_warn "Working tree is clean. Nothing to stash."
        return 0
    fi

    if [[ -z "$msg" ]]; then
        msg="$(sg_prompt "Stash message (optional)")"
    fi

    local branch
    branch="$(sg_current_branch)"

    sg_info "Current branch: $branch"
    sg_info "Files to stash:"
    git status --short
    echo

    local include_untracked="--include-untracked"
    if ! sg_confirm "Include untracked files?"; then
        include_untracked=""
    fi

    if [[ -n "$msg" ]]; then
        _sg_cmd stash save "$msg" $include_untracked 2>/dev/null \
            || git stash push -m "$msg" $include_untracked
    else
        _sg_cmd stash save $include_untracked 2>/dev/null \
            || git stash push $include_untracked
    fi

    sg_success "Changes stashed successfully."
    sg_undo_available
}

op_apply() {
    local count
    count="$(_stash_count)"
    if (( count == 0 )); then
        sg_warn "No stashes available."
        return 0
    fi

    _stash_list_detailed || return 0
    echo

    local idx
    idx="$(sg_prompt "Stash index to apply" "0")"

    if ! [[ "$idx" =~ ^[0-9]+$ ]] || (( idx >= count )); then
        sg_error "Invalid stash index: $idx"
        return 1
    fi

    local pop_it="n"
    if sg_confirm "Remove stash after applying (pop)?"; then
        pop_it="y"
    fi

    local ref="stash@{$idx}"

    if [[ "$pop_it" == "y" ]]; then
        _sg_cmd stash pop "$idx" 2>/dev/null \
            || git stash pop "$ref"
        sg_success "Stash [$idx] popped and applied."
    else
        git stash apply "$ref"
        sg_success "Stash [$idx] applied (still in stash list)."
    fi

    sg_undo_available
}

op_search() {
    local count
    count="$(_stash_count)"
    if (( count == 0 )); then
        sg_warn "No stashes to search."
        return 0
    fi

    local pattern
    pattern="$(sg_prompt "Search pattern (regex)")"

    if [[ -z "$pattern" ]]; then
        sg_error "Pattern cannot be empty."
        return 1
    fi

    sg_header "Searching stashes for: $pattern"

    local found=0
    local i=0
    while (( i < count )); do
        local ref="stash@{$i}"
        local matches
        matches="$(git stash show -p "$ref" 2>/dev/null | grep -n -E "$pattern" 2>/dev/null || true)"
        if [[ -n "$matches" ]]; then
            local entry
            entry="$(git stash list | sed -n "$((i + 1))p")"
            printf "\n  ${_C_GREEN}Stash [$i]${_C_RESET}: %s\n" "$entry"
            echo "$matches" | head -20 | while IFS= read -r mline; do
                printf "    ${_C_DIM}%s${_C_RESET}\n" "$mline"
            done
            local match_count
            match_count="$(echo "$matches" | wc -l)"
            if (( match_count > 20 )); then
                sg_info "    ... and $((match_count - 20)) more matches"
            fi
            (( found++ )) || true
        fi
        (( i++ )) || true
    done

    echo
    if (( found == 0 )); then
        sg_warn "No matches found for '$pattern' in any stash."
    else
        sg_success "Found matches in $found stash(es)."
    fi
}

op_export() {
    local count
    count="$(_stash_count)"
    if (( count == 0 )); then
        sg_warn "No stashes to export."
        return 0
    fi

    _stash_list_detailed || return 0
    echo

    local idx
    idx="$(sg_prompt "Stash index to export" "0")"

    if ! [[ "$idx" =~ ^[0-9]+$ ]] || (( idx >= count )); then
        sg_error "Invalid stash index: $idx"
        return 1
    fi

    local ref="stash@{$idx}"

    mkdir -p "$_PATCH_DIR"

    # Generate a filename from the stash message
    local entry branch msg safe_name
    entry="$(git stash list | sed -n "$((idx + 1))p")"
    branch="$(echo "$entry" | sed -E 's/^stash@\{[0-9]+\}: (WIP on |On )([^:]+):.*/\2/')"
    msg="$(echo "$entry" | sed -E 's/^stash@\{[0-9]+\}: [^:]+: [a-f0-9]+ //')"

    # Create a filesystem-safe name
    safe_name="$(echo "${branch}-${msg}" | tr -cs '[:alnum:]-_' '-' | head -c 60)"
    safe_name="${safe_name%-}"  # Remove trailing dash

    local timestamp
    timestamp="$(date +%Y%m%d-%H%M%S)"
    local patch_file="$_PATCH_DIR/stash-${idx}-${safe_name}-${timestamp}.patch"

    git stash show -p "$ref" > "$patch_file"

    sg_success "Exported stash [$idx] to:"
    sg_info "  $patch_file"
    sg_info "Apply later with: git apply \"$patch_file\""
}

op_drop() {
    local count
    count="$(_stash_count)"
    if (( count == 0 )); then
        sg_warn "No stashes to drop."
        return 0
    fi

    _stash_list_detailed || return 0
    echo

    local idx
    idx="$(sg_prompt "Stash index to drop" "0")"

    if ! [[ "$idx" =~ ^[0-9]+$ ]] || (( idx >= count )); then
        sg_error "Invalid stash index: $idx"
        return 1
    fi

    local ref="stash@{$idx}"
    local entry
    entry="$(git stash list | sed -n "$((idx + 1))p")"

    sg_warn "About to drop: $entry"
    echo

    # Show what will be lost
    sg_info "Changes that will be discarded:"
    git stash show "$ref" 2>/dev/null || true
    echo

    if sg_confirm "Drop stash [$idx]? This cannot be undone." "n"; then
        # Offer to export first
        if sg_confirm "Export to patch file before dropping?"; then
            op_export <<< "$idx"
        fi

        git stash drop "$ref"
        sg_success "Stash [$idx] dropped."
    else
        sg_info "Drop cancelled."
    fi
}

op_snapshot() {
    sg_header "Create SecureGit Snapshot"

    if [[ -z "$(git status --porcelain 2>/dev/null)" ]]; then
        sg_warn "Working tree is clean. Nothing to snapshot."
        return 0
    fi

    local msg
    msg="$(sg_prompt "Snapshot description" "manual snapshot")"

    sg_info "Current changes:"
    git status --short
    echo

    # Create a snapshot using securegit
    # A snapshot preserves changes in securegit's history without modifying the working tree
    if command -v securegit &>/dev/null; then
        sg_info "Creating securegit snapshot..."

        # Stage everything, create snapshot commit, then restore state
        local current_branch
        current_branch="$(sg_current_branch)"

        # Use securegit's snapshot approach: stash, commit ref, restore
        _sg_cmd snapshot "$msg" 2>/dev/null && {
            sg_success "SecureGit snapshot created: $msg"
            sg_info "Restore with: securegit undo"
            return 0
        }

        # Fallback: manual snapshot via stash + ref
        sg_warn "Snapshot command not available, using stash-based snapshot..."
    fi

    # Fallback: create a named stash that acts as a snapshot
    local timestamp
    timestamp="$(date +%Y%m%d-%H%M%S)"
    local snapshot_msg="snapshot/${timestamp}: ${msg}"

    git stash push -m "$snapshot_msg" --include-untracked
    # Immediately restore the stash to keep working tree intact
    git stash apply "stash@{0}" 2>/dev/null || true

    sg_success "Snapshot saved as stash: $snapshot_msg"
    sg_info "Working tree unchanged. Snapshot preserved in stash."
    sg_undo_available
}

op_stash_to_snapshot() {
    sg_header "Convert Stash to Snapshot"

    local count
    count="$(_stash_count)"
    if (( count == 0 )); then
        sg_warn "No stashes to convert."
        return 0
    fi

    _stash_list_detailed || return 0
    echo

    local idx
    idx="$(sg_prompt "Stash index to convert to snapshot" "0")"

    if ! [[ "$idx" =~ ^[0-9]+$ ]] || (( idx >= count )); then
        sg_error "Invalid stash index: $idx"
        return 1
    fi

    local ref="stash@{$idx}"
    local entry
    entry="$(git stash list | sed -n "$((idx + 1))p")"
    local msg
    msg="$(echo "$entry" | sed -E 's/^stash@\{[0-9]+\}: [^:]+: [a-f0-9]+ //')"

    sg_info "Converting stash [$idx]: $msg"
    echo

    # Export the stash as a patch for safety
    mkdir -p "$_PATCH_DIR"
    local timestamp
    timestamp="$(date +%Y%m%d-%H%M%S)"
    local patch_file="$_PATCH_DIR/snapshot-${timestamp}.patch"
    git stash show -p "$ref" > "$patch_file"
    sg_info "Backup patch saved: $patch_file"

    # Rename the stash to snapshot convention
    # Git doesn't support renaming stashes, so we recreate with new message
    local snapshot_msg="snapshot/${timestamp}: ${msg}"

    # Apply, drop, re-stash with new name
    local had_changes="false"
    if [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then
        had_changes="true"
        git stash push -m "temp-preserve-working-tree" --include-untracked 2>/dev/null
    fi

    git stash apply "$ref" 2>/dev/null
    git stash drop "$ref" 2>/dev/null
    git stash push -m "$snapshot_msg" --include-untracked
    # Restore snapshot to working tree (snapshot = stash that leaves tree intact)
    git stash apply "stash@{0}" 2>/dev/null || true

    if [[ "$had_changes" == "true" ]]; then
        # Clean snapshot-applied changes, restore original working state
        git checkout -- . 2>/dev/null || true
        git clean -fd 2>/dev/null || true
        # Find and pop the temp stash
        local temp_idx
        temp_idx="$(git stash list | grep -n 'temp-preserve-working-tree' | head -1 | cut -d: -f1)"
        if [[ -n "$temp_idx" ]]; then
            git stash pop "stash@{$((temp_idx - 1))}" 2>/dev/null || true
        fi
    fi

    sg_success "Stash converted to snapshot: $snapshot_msg"
    sg_undo_available
}

op_detail() {
    local count
    count="$(_stash_count)"
    if (( count == 0 )); then
        sg_warn "No stashes available."
        return 0
    fi

    _stash_list_detailed || return 0
    echo

    local idx
    idx="$(sg_prompt "Stash index to inspect" "0")"

    if ! [[ "$idx" =~ ^[0-9]+$ ]] || (( idx >= count )); then
        sg_error "Invalid stash index: $idx"
        return 1
    fi

    _stash_show_detail "$idx"
}

# ─── CLI Shortcut Handling ───────────────────────────────────────────────────

if [[ "${1:-}" == "list" ]]; then
    op_list
    exit 0
elif [[ "${1:-}" == "save" ]]; then
    shift
    op_save "${*:-}"
    exit 0
elif [[ "${1:-}" == "pop" ]]; then
    shift
    idx="${1:-0}"
    ref="stash@{$idx}"
    _sg_cmd stash pop "$idx" 2>/dev/null || git stash pop "$ref"
    sg_success "Stash [$idx] popped."
    exit 0
fi

# ─── Interactive Menu Loop ───────────────────────────────────────────────────

while true; do
    choice="$(sg_menu "SecureGit Stash Manager" \
        "List stashes with details" \
        "Create named stash" \
        "Apply / pop stash" \
        "Inspect stash contents" \
        "Search stash contents" \
        "Export stash to patch" \
        "Drop stash" \
        "Create snapshot (preserves working tree)" \
        "Convert stash to snapshot" \
        "Quit"
    )"

    case "$choice" in
        1)  op_list ;;
        2)  op_save ;;
        3)  op_apply ;;
        4)  op_detail ;;
        5)  op_search ;;
        6)  op_export ;;
        7)  op_drop ;;
        8)  op_snapshot ;;
        9)  op_stash_to_snapshot ;;
        10) sg_info "Goodbye."; exit 0 ;;
    esac

    echo
    sg_divider
done
