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

# Colours
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BOLD='\033[1m'
RESET='\033[0m'

usage() {
    cat <<'USAGE'
Usage: validate-skill [OPTIONS] <PATH>

Validate Claude Code SKILL.md files against known restrictions.

Arguments:
  PATH    A SKILL.md file or directory to scan recursively (default: .)

Options:
  -n, --dryrun   List files that would be checked without validating
  -h, --help     Show this help message

Checks for:
  - Frontmatter: missing delimiters, name/description fields, invalid name format
  - Bang-commands: ${VAR} syntax, $() substitution, ~/ and ../ paths
  - allowed-tools: uncovered command binaries, overly broad patterns
USAGE
}

DRYRUN=false
TARGET="."

while [[ $# -gt 0 ]]; do
    case "$1" in
        -n|--dryrun) DRYRUN=true; shift ;;
        -h|--help) usage; exit 0 ;;
        -*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
        *) TARGET="$1"; shift ;;
    esac
done

# Discovery
files=()
if [[ -f "$TARGET" ]]; then
    files+=("$(cd "$(dirname "$TARGET")" && pwd)/$(basename "$TARGET")")
elif [[ -d "$TARGET" ]]; then
    TARGET="$(cd "$TARGET" && pwd)"
    while IFS= read -r f; do
        files+=("$f")
    done < <(find "$TARGET" -name "SKILL.md" -type f 2>/dev/null | sort)
else
    echo "Error: $TARGET is not a file or directory" >&2
    exit 2
fi

if [[ ${#files[@]} -eq 0 ]]; then
    echo "No SKILL.md files found in $TARGET"
    exit 0
fi

# Dryrun
if [[ "$DRYRUN" == true ]]; then
    echo "[dryrun] Would validate ${#files[@]} files:"
    for f in "${files[@]}"; do
        if [[ -d "$TARGET" ]]; then
            echo "  ${f#"$TARGET"/}"
        else
            echo "  $f"
        fi
    done
    exit 0
fi

# Validation
total_errors=0
total_warnings=0
files_with_errors=0

for file in "${files[@]}"; do
    file_errors=0
    file_warnings=0
    issues=()

    # Display path
    if [[ -d "$TARGET" ]]; then
        display="${f#"$TARGET"/}"
    else
        display="$file"
    fi

    # --- Frontmatter checks ---

    first_line=$(head -1 "$file")
    if [[ "$first_line" != "---" ]]; then
        issues+=("$(printf "   ${RED}ERROR${RESET}  Line 1: File does not start with --- frontmatter delimiter")")
        file_errors=$((file_errors + 1))
        # Can't do further frontmatter checks without delimiters
    else
        # Find closing ---
        second_delim=$(awk 'NR>1 && /^---[[:space:]]*$/ {print NR; exit}' "$file")
        if [[ -z "$second_delim" ]]; then
            issues+=("$(printf "   ${RED}ERROR${RESET}  Frontmatter: Missing closing --- delimiter")")
            file_errors=$((file_errors + 1))
        else
            # Extract frontmatter
            frontmatter=$(sed -n "2,$((second_delim - 1))p" "$file")

            # Check name field
            name_value=$(echo "$frontmatter" | grep '^name:' | sed 's/^name:[[:space:]]*//' | head -1 || true)
            if [[ -z "$name_value" ]]; then
                issues+=("$(printf "   ${RED}ERROR${RESET}  Frontmatter: Missing 'name:' field")")
                file_errors=$((file_errors + 1))
            else
                if [[ ! "$name_value" =~ ^[a-z0-9-]+$ ]]; then
                    issues+=("$(printf "   ${RED}ERROR${RESET}  Frontmatter: name '${name_value}' contains invalid characters (must be lowercase+numbers+hyphens)")")
                    file_errors=$((file_errors + 1))
                fi
                if [[ ${#name_value} -gt 64 ]]; then
                    issues+=("$(printf "   ${RED}ERROR${RESET}  Frontmatter: name exceeds 64 characters")")
                    file_errors=$((file_errors + 1))
                fi
            fi

            # Check for non-ASCII characters in frontmatter (can silently break YAML parsing)
            non_ascii_lines=$(echo "$frontmatter" | grep -nP '[^\x00-\x7F]' || true)
            if [[ -n "$non_ascii_lines" ]]; then
                while IFS= read -r nal; do
                    fm_ln="${nal%%:*}"
                    # Offset by 1 for the opening --- line
                    actual_ln=$((fm_ln + 1))
                    issues+=("$(printf "   ${RED}ERROR${RESET}  Line ${actual_ln}: Frontmatter contains non-ASCII characters (em dashes, smart quotes, etc.) — can silently prevent skill loading")")
                    file_errors=$((file_errors + 1))
                done <<< "$non_ascii_lines"
            fi

            # Check description field
            desc_line=$(echo "$frontmatter" | grep '^description:' || true)
            if [[ -z "$desc_line" ]]; then
                issues+=("$(printf "   ${RED}ERROR${RESET}  Frontmatter: Missing 'description:' field")")
                file_errors=$((file_errors + 1))
            else
                desc_value=$(echo "$desc_line" | sed 's/^description:[[:space:]]*//')
                # Multi-line > or | is non-empty; empty string after colon is not
                if [[ -z "$desc_value" ]]; then
                    issues+=("$(printf "   ${RED}ERROR${RESET}  Frontmatter: 'description:' field is empty")")
                    file_errors=$((file_errors + 1))
                fi
            fi

            # Extract allowed-tools
            allowed_tools_line=$(echo "$frontmatter" | grep '^allowed-tools:' || true)
            allowed_tools_value=""
            bash_patterns=()
            if [[ -n "$allowed_tools_line" ]]; then
                allowed_tools_value=$(echo "$allowed_tools_line" | sed 's/^allowed-tools:[[:space:]]*//')
                # Parse Bash(...) patterns
                while IFS= read -r pattern; do
                    pattern=$(echo "$pattern" | xargs) # trim whitespace
                    if [[ "$pattern" == Bash\(*\) ]]; then
                        bash_patterns+=("$pattern")
                    fi
                done < <(echo "$allowed_tools_value" | tr ',' '\n')
            fi

            # Check for overly broad Bash(git *) pattern
            for pattern in "${bash_patterns[@]+"${bash_patterns[@]}"}"; do
                if [[ "$pattern" == "Bash(git *)" ]]; then
                    issues+=("$(printf "   ${YELLOW}WARN${RESET}   Frontmatter: 'Bash(git *)' is overly broad — use specific subcommands like 'Bash(git log *)', 'Bash(git diff *)'")")
                    file_warnings=$((file_warnings + 1))
                fi
            done
        fi
    fi

    # --- Bang-command checks ---

    bang_commands=()
    while IFS= read -r line; do
        [[ -n "$line" ]] && bang_commands+=("$line")
    done < <(grep -n '^!`' "$file" 2>/dev/null || true)

    # Warn if bang-commands exist but no allowed-tools
    if [[ ${#bang_commands[@]} -gt 0 && -z "$allowed_tools_value" && "$first_line" == "---" && -n "${second_delim:-}" ]]; then
        issues+=("$(printf "   ${YELLOW}WARN${RESET}   Bang-commands found but no 'allowed-tools' declared — commands won't be pre-authorised")")
        file_warnings=$((file_warnings + 1))
    fi

    for entry in "${bang_commands[@]+"${bang_commands[@]}"}"; do
        ln="${entry%%:*}"
        line_content="${entry#*:}"
        # Extract command between backticks
        cmd=$(echo "$line_content" | sed 's/^!`//' | sed 's/`$//')

        # Check for ${VAR} syntax (e.g., ${HOME}, ${VAR:-default})
        if echo "$cmd" | grep -qE '\$\{[^}]+\}'; then
            issues+=("$(printf "   ${RED}ERROR${RESET}  Line ${ln}: Bang-command uses \${VAR} syntax (rejected by permission checker)")")
            file_errors=$((file_errors + 1))
        fi

        # Check for $VAR shell expansion in paths (e.g., $HOME/dev/...)
        # Match $WORD followed by / (path context) but not $() or ${} which are caught above
        if echo "$cmd" | grep -qE '\$[A-Za-z_][A-Za-z0-9_]*/'; then
            issues+=("$(printf "   ${RED}ERROR${RESET}  Line ${ln}: Bang-command uses \$VAR in path (permission checker rejects shell expansion in paths — move to skill instructions instead)")")
            file_errors=$((file_errors + 1))
        fi

        # Check for $() command substitution
        if echo "$cmd" | grep -qE '\$\('; then
            issues+=("$(printf "   ${RED}ERROR${RESET}  Line ${ln}: Bang-command uses \$() command substitution (rejected by permission checker)")")
            file_errors=$((file_errors + 1))
        fi

        # Check for /home/ hardcoded paths (prefer ~/)
        if echo "$cmd" | grep -qF '/home/'; then
            issues+=("$(printf "   ${RED}ERROR${RESET}  Line ${ln}: Bang-command uses hardcoded /home/ path (use ~/ for portability)")")
            file_errors=$((file_errors + 1))
        fi

        # Check for ~/ paths (more portable but may still be rejected by some permission modes)
        if echo "$cmd" | grep -qF '~/'; then
            issues+=("$(printf "   ${YELLOW}WARN${RESET}   Line ${ln}: Bang-command uses ~/ path (may be rejected by sandbox — consider moving to skill instructions)")")
            file_warnings=$((file_warnings + 1))
        fi

        # Check for ../ relative paths
        if echo "$cmd" | grep -qF '../'; then
            issues+=("$(printf "   ${RED}ERROR${RESET}  Line ${ln}: Bang-command uses ../ relative path (fragile, breaks when CWD changes)")")
            file_errors=$((file_errors + 1))
        fi

        # Check allowed-tools coverage
        if [[ -n "$allowed_tools_value" && ${#bash_patterns[@]} -ge 0 ]]; then
            binary=$(echo "$cmd" | awk '{print $1}')
            matched=false
            for pattern in "${bash_patterns[@]+"${bash_patterns[@]}"}"; do
                # Extract binary from Bash(binary ...) pattern
                pattern_binary=$(echo "$pattern" | sed 's/^Bash(\([^ )]*\).*/\1/')
                if [[ "$binary" == "$pattern_binary" ]]; then
                    matched=true
                    break
                fi
            done
            if [[ "$matched" == false ]]; then
                issues+=("$(printf "   ${RED}ERROR${RESET}  Line ${ln}: Command binary '${binary}' not covered by any Bash() pattern in allowed-tools")")
                file_errors=$((file_errors + 1))
            fi
        fi
    done

    # --- Print results for this file ---

    if [[ -d "${TARGET:-}" ]]; then
        rel="${file#"$TARGET"/}"
    else
        rel="$file"
    fi
    echo -e "── ${BOLD}${rel}${RESET} ──"

    if [[ ${#issues[@]} -eq 0 ]]; then
        echo -e "   ${GREEN}No issues found.${RESET}"
    else
        for issue in "${issues[@]}"; do
            echo -e "$issue"
        done
        echo -e "   ${file_errors} errors, ${file_warnings} warnings"
    fi
    echo

    total_errors=$((total_errors + file_errors))
    total_warnings=$((total_warnings + file_warnings))
    if [[ $file_errors -gt 0 ]]; then
        files_with_errors=$((files_with_errors + 1))
    fi
done

# Summary
echo -e "Summary: ${#files[@]} files checked, ${total_errors} errors, ${total_warnings} warnings"

if [[ $total_errors -gt 0 ]]; then
    exit 1
else
    exit 0
fi
