#!/bin/bash
# -*- coding: utf-8 -*-
# Timestamp: "2025-05-24 16:05:00 (ywatanabe)"
# File: ~/.claude/to_claude/bin/elisp-ci/elisp-ci

set -e

# Elisp-CI: Universal CI/CD for Emacs Lisp Projects
# A reusable testing framework for the Emacs ecosystem

VERSION="1.0.0"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(pwd)"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Default configuration
DEFAULT_EMACS_VERSIONS=("27.1" "28.2" "29.1" "29.4" "snapshot")
DEFAULT_TEST_PATTERN="test-*.el"
DEFAULT_TEST_DIR="tests"
DEFAULT_FRAMEWORK="ert"

usage() {
    cat << EOF
Elisp-CI v${VERSION} - Universal CI/CD for Emacs Lisp Projects

USAGE:
    elisp-ci <command> [options]

COMMANDS:
    init                Initialize elisp-ci in current project
    test                Run tests locally
    docker-build        Build Docker test environments
    docker-test         Run tests in Docker containers
    analyze             Analyze project dependencies and structure
    template            Generate CI/CD templates
    validate            Validate project configuration
    benchmark           Performance benchmarking across Emacs versions

OPTIONS:
    --version <ver>     Test specific Emacs version (27.1, 28.2, 29.1, 29.4, snapshot)
    --all-versions      Test all supported Emacs versions
    --framework <fw>    Test framework (ert, buttercup) [default: ert]
    --test-dir <dir>    Test directory [default: tests]
    --pattern <pat>     Test file pattern [default: test-*.el]
    --config <file>     Configuration file [default: .elisp-ci.yml]
    --verbose           Verbose output
    --watch             Watch mode for development
    --coverage          Generate coverage report
    --help              Show this help message

EXAMPLES:
    elisp-ci init                    # Initialize project
    elisp-ci test                    # Test current Emacs version
    elisp-ci test --all-versions     # Test all versions
    elisp-ci docker-test --version 29.1  # Test in Docker
    elisp-ci analyze                 # Analyze dependencies
    elisp-ci template github-actions # Generate GitHub Actions workflow

For more information: https://github.com/ywatanabe1989/elisp-ci
EOF
}

log() {
    local level=$1
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')

    case $level in
        "INFO")  echo -e "${GREEN}[INFO]${NC}  ${timestamp} - $message" ;;
        "WARN")  echo -e "${YELLOW}[WARN]${NC}  ${timestamp} - $message" ;;
        "ERROR") echo -e "${RED}[ERROR]${NC} ${timestamp} - $message" ;;
        "DEBUG") [[ ${VERBOSE:-false} == true ]] && echo -e "${BLUE}[DEBUG]${NC} ${timestamp} - $message" ;;
    esac
}

# Load project configuration
load_config() {
    local config_file="${1:-.elisp-ci.yml}"

    if [[ -f "$config_file" ]]; then
        log "INFO" "Loading configuration from $config_file"
        # Simple YAML parsing for our needs
        EMACS_VERSIONS=($(grep -A 10 "emacs_versions:" "$config_file" | grep "^ *-" | sed 's/^ *- *"*\([^"]*\)"*/\1/'))
        PROJECT_NAME=$(grep "name:" "$config_file" | sed 's/.*name: *"*\([^"]*\)"*/\1/')
        TEST_DIR=$(grep "directory:" "$config_file" | sed 's/.*directory: *"*\([^"]*\)"*/\1/')
        TEST_PATTERN=$(grep "pattern:" "$config_file" | sed 's/.*pattern: *"*\([^"]*\)"*/\1/')
        ENTRY_FILE=$(grep "entry:" "$config_file" | sed 's/.*entry: *"*\([^"]*\)"*/\1/')
    else
        log "DEBUG" "No config file found, using defaults"
        EMACS_VERSIONS=("${DEFAULT_EMACS_VERSIONS[@]}")
        PROJECT_NAME=$(basename "$PROJECT_ROOT")
        TEST_DIR="$DEFAULT_TEST_DIR"
        TEST_PATTERN="$DEFAULT_TEST_PATTERN"
        ENTRY_FILE=""
    fi
}

# Initialize elisp-ci in project
init_project() {
    log "INFO" "Initializing elisp-ci in $(basename "$PROJECT_ROOT")"

    # Create .elisp-ci.yml
    cat > .elisp-ci.yml << EOF
# Elisp-CI Configuration
# Generated by elisp-ci v${VERSION}

project:
  name: "$(basename "$PROJECT_ROOT")"
  entry: "$(find . -maxdepth 1 -name "*.el" -not -path "./tests/*" | head -1 | sed 's|^./||')"

emacs_versions:
  - "27.1"
  - "28.2"
  - "29.1"
  - "29.4"
  - "snapshot"

dependencies:
  # Add your package dependencies here
  # - "package-name"

test:
  framework: "ert"
  directory: "tests/"
  pattern: "test-*.el"
  timeout: 120

coverage:
  enabled: false
  threshold: 80

docker:
  enabled: true
  registry: "ghcr.io/elisp-ci"

github_actions:
  enabled: true
  continue_on_error_for_snapshot: true
EOF

    # Create basic test structure if it doesn't exist
    if [[ ! -d "tests" ]]; then
        mkdir -p tests
        log "INFO" "Created tests/ directory"
    fi

    # Create sample test file if no tests exist
    if [[ -z "$(find tests -name "test-*.el" 2>/dev/null)" ]]; then
        cat > "tests/test-$(basename "$PROJECT_ROOT").el" << EOF
;;; -*- coding: utf-8; lexical-binding: t -*-
;;; Test file for $(basename "$PROJECT_ROOT")
;;; Generated by elisp-ci v${VERSION}

(require 'ert)

(ert-deftest test-$(basename "$PROJECT_ROOT")-loadable ()
  "Test that the main package loads correctly."
  (should (require '$(basename "$PROJECT_ROOT" .el) nil t)))

(provide 'test-$(basename "$PROJECT_ROOT"))
EOF
        log "INFO" "Created sample test file"
    fi

    # Create Makefile
    cat > Makefile << EOF
# Makefile for $(basename "$PROJECT_ROOT")
# Generated by elisp-ci v${VERSION}

.PHONY: test test-all clean docker-build docker-test

# Test with current Emacs version
test:
	elisp-ci test

# Test all Emacs versions
test-all:
	elisp-ci test --all-versions

# Test in Docker
docker-test:
	elisp-ci docker-test --all-versions

# Build Docker images
docker-build:
	elisp-ci docker-build

# Clean up
clean:
	rm -rf .elisp-ci-cache/
	docker rmi \$(docker images -q elisp-ci-$(basename "$PROJECT_ROOT") 2>/dev/null) 2>/dev/null || true

# Validate configuration
validate:
	elisp-ci validate

# Generate coverage report
coverage:
	elisp-ci test --coverage

# Analyze project
analyze:
	elisp-ci analyze
EOF

    log "INFO" "Created Makefile with common targets"
    log "INFO" "Initialization complete! Run 'elisp-ci test' to verify setup"
}

# Analyze project structure and dependencies
analyze_project() {
    log "INFO" "Analyzing project structure..."

    # Find main elisp files
    local elisp_files=($(find . -maxdepth 2 -name "*.el" -not -path "./tests/*" -not -path "./.elisp-ci-cache/*"))
    log "INFO" "Found ${#elisp_files[@]} Elisp files"

    # Analyze dependencies
    local all_requires=()
    for file in "${elisp_files[@]}"; do
        local requires=($(grep -h "^(require " "$file" 2>/dev/null | sed "s/(require '\\([^)]*\\).*/\\1/" || true))
        all_requires+=("${requires[@]}")
    done

    # Remove duplicates and sort
    local unique_requires=($(printf '%s\n' "${all_requires[@]}" | sort -u))

    log "INFO" "External dependencies detected:"
    for req in "${unique_requires[@]}"; do
        if [[ -n "$req" && ! -f "${req}.el" && ! -f "*/${req}.el" ]]; then
            echo "  - $req"
        fi
    done

    # Find test files
    local test_files=($(find tests -name "$TEST_PATTERN" 2>/dev/null || true))
    log "INFO" "Found ${#test_files[@]} test files"

    # Suggest improvements
    echo
    log "INFO" "Suggestions:"

    if [[ ${#test_files[@]} -eq 0 ]]; then
        echo "  - Add test files matching pattern: $TEST_PATTERN"
    fi

    if [[ ! -f ".elisp-ci.yml" ]]; then
        echo "  - Run 'elisp-ci init' to create configuration"
    fi

    if [[ ! -f "Makefile" ]]; then
        echo "  - Add Makefile for convenient test execution"
    fi
}

# Run tests with specified framework
run_tests() {
    local emacs_version=${1:-"current"}
    local test_dir=${2:-"$TEST_DIR"}
    local test_pattern=${3:-"$TEST_PATTERN"}

    log "INFO" "Running tests with Emacs $emacs_version"

    # Create cache directory
    mkdir -p .elisp-ci-cache

    # Find test files
    local test_files=($(find "$test_dir" -name "$test_pattern" 2>/dev/null || true))

    if [[ ${#test_files[@]} -eq 0 ]]; then
        log "ERROR" "No test files found matching pattern: $test_pattern in $test_dir"
        return 1
    fi

    log "INFO" "Found ${#test_files[@]} test files"

    local failed_tests=0
    local total_tests=0

    # Run each test file
    for test_file in "${test_files[@]}"; do
        log "INFO" "Testing: $test_file"

        local test_dir_path=$(dirname "$test_file")
        local emacs_cmd="emacs"

        # Use specific Emacs version if available
        if [[ "$emacs_version" != "current" && -n "$(command -v "emacs-$emacs_version" 2>/dev/null)" ]]; then
            emacs_cmd="emacs-$emacs_version"
        fi

        # Prepare load paths
        local load_paths=(
            "."
            "./src"
            "$test_dir_path"
            "./tests/mocks"
        )

        # Add module directories
        for dir in */; do
            if [[ -d "$dir" && "$dir" != "tests/" && "$dir" != ".git/" && "$dir" != ".elisp-ci-cache/" ]]; then
                load_paths+=("$dir")
            fi
        done

        # Build Emacs command
        local emacs_args=(
            "-Q" "--batch"
        )

        for path in "${load_paths[@]}"; do
            emacs_args+=("--eval" "(add-to-list 'load-path \"$path\")")
        done

        emacs_args+=(
            "--eval" "(require 'ert)"
            "--eval" "(load-file \"$test_file\")"
            "--eval" "(ert-run-tests-batch-and-exit)"
        )

        # Run test
        if $emacs_cmd "${emacs_args[@]}" 2>&1; then
            log "INFO" "✓ $test_file passed"
        else
            log "ERROR" "✗ $test_file failed"
            ((failed_tests++))
        fi

        ((total_tests++))
    done

    # Report results
    echo
    if [[ $failed_tests -eq 0 ]]; then
        log "INFO" "All tests passed! ($total_tests/$total_tests)"
        return 0
    else
        log "ERROR" "Tests failed: $failed_tests/$total_tests"
        return 1
    fi
}

# Generate GitHub Actions workflow
generate_github_actions() {
    mkdir -p .github/workflows

    cat > .github/workflows/elisp-ci.yml << EOF
# GitHub Actions workflow for $(basename "$PROJECT_ROOT")
# Generated by elisp-ci v${VERSION}

name: Elisp CI

on:
  push:
    paths-ignore:
      - '**.md'
      - 'docs/**'
  pull_request:
    paths-ignore:
      - '**.md'
      - 'docs/**'

jobs:
  test:
    runs-on: ubuntu-latest
    continue-on-error: \${{ matrix.emacs_version == 'snapshot' }}

    strategy:
      matrix:
        emacs_version: [$(printf '"%s", ' "${EMACS_VERSIONS[@]}" | sed 's/, $//'))]

    steps:
      - uses: actions/checkout@v4

      - uses: purcell/setup-emacs@master
        with:
          version: \${{ matrix.emacs_version }}

      - name: Run Tests
        run: |
          # Install elisp-ci
          curl -L https://raw.githubusercontent.com/ywatanabe1989/elisp-ci/main/install.sh | bash

          # Run tests
          elisp-ci test --version \${{ matrix.emacs_version }}

      - name: Upload Coverage
        if: matrix.emacs_version == '29.4'
        uses: codecov/codecov-action@v3
        with:
          file: .elisp-ci-cache/coverage.xml
EOF

    log "INFO" "Generated .github/workflows/elisp-ci.yml"
}

# Docker support
docker_build() {
    log "INFO" "Building Docker images for testing..."

    mkdir -p .elisp-ci-cache/docker

    # Create base Dockerfile
    cat > .elisp-ci-cache/docker/Dockerfile.base << EOF
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

# Install dependencies
RUN apt-get update && apt-get install -y \\
    curl \\
    git \\
    build-essential \\
    && rm -rf /var/lib/apt/lists/*

# Install Nix for Emacs version management
RUN curl -L https://nixos.org/nix/install | sh
ENV PATH="/root/.nix-profile/bin:\$PATH"

WORKDIR /workspace
COPY . .

# Install elisp-ci
RUN curl -L https://raw.githubusercontent.com/ywatanabe1989/elisp-ci/main/install.sh | bash

CMD ["elisp-ci", "test"]
EOF

    # Build base image
    docker build -f .elisp-ci-cache/docker/Dockerfile.base -t "elisp-ci-$(basename "$PROJECT_ROOT"):base" .

    log "INFO" "Docker base image built successfully"
}

docker_test() {
    local version=${1:-"all"}

    if [[ "$version" == "all" ]]; then
        for v in "${EMACS_VERSIONS[@]}"; do
            log "INFO" "Testing with Emacs $v in Docker..."
            docker run --rm -v "$(pwd):/workspace" "elisp-ci-$(basename "$PROJECT_ROOT"):base" \
                elisp-ci test --version "$v"
        done
    else
        log "INFO" "Testing with Emacs $version in Docker..."
        docker run --rm -v "$(pwd):/workspace" "elisp-ci-$(basename "$PROJECT_ROOT"):base" \
            elisp-ci test --version "$version"
    fi
}

# Validate configuration
validate_config() {
    log "INFO" "Validating elisp-ci configuration..."

    local errors=0

    # Check if we're in a valid elisp project
    if [[ -z "$(find . -maxdepth 1 -name "*.el" -not -path "./tests/*")" ]]; then
        log "ERROR" "No Elisp files found in project root"
        ((errors++))
    fi

    # Check test directory
    if [[ ! -d "$TEST_DIR" ]]; then
        log "ERROR" "Test directory not found: $TEST_DIR"
        ((errors++))
    fi

    # Check for test files
    if [[ -z "$(find "$TEST_DIR" -name "$TEST_PATTERN" 2>/dev/null)" ]]; then
        log "WARN" "No test files found matching pattern: $TEST_PATTERN"
    fi

    # Check configuration file
    if [[ -f ".elisp-ci.yml" ]]; then
        log "INFO" "Configuration file found"
    else
        log "WARN" "No .elisp-ci.yml found, using defaults"
    fi

    if [[ $errors -eq 0 ]]; then
        log "INFO" "Configuration is valid"
        return 0
    else
        log "ERROR" "Configuration has $errors error(s)"
        return 1
    fi
}

# Main command dispatcher
main() {
    # Parse global options
    while [[ $# -gt 0 ]]; do
        case $1 in
            --verbose)
                VERBOSE=true
                shift
                ;;
            --config)
                CONFIG_FILE="$2"
                shift 2
                ;;
            --help|-h)
                usage
                exit 0
                ;;
            --version|-v)
                echo "elisp-ci version $VERSION"
                exit 0
                ;;
            *)
                break
                ;;
        esac
    done

    # Load configuration
    load_config "${CONFIG_FILE:-.elisp-ci.yml}"

    # Parse command
    local command=${1:-"help"}
    shift || true

    case $command in
        "init")
            init_project "$@"
            ;;
        "test")
            local version="current"
            local all_versions=false

            while [[ $# -gt 0 ]]; do
                case $1 in
                    --version)
                        version="$2"
                        shift 2
                        ;;
                    --all-versions)
                        all_versions=true
                        shift
                        ;;
                    --test-dir)
                        TEST_DIR="$2"
                        shift 2
                        ;;
                    --pattern)
                        TEST_PATTERN="$2"
                        shift 2
                        ;;
                    *)
                        shift
                        ;;
                esac
            done

            if [[ "$all_versions" == true ]]; then
                local total_failed=0
                for v in "${EMACS_VERSIONS[@]}"; do
                    if ! run_tests "$v" "$TEST_DIR" "$TEST_PATTERN"; then
                        ((total_failed++))
                    fi
                done

                if [[ $total_failed -gt 0 ]]; then
                    log "ERROR" "Failed testing $total_failed/${#EMACS_VERSIONS[@]} Emacs versions"
                    exit 1
                else
                    log "INFO" "All Emacs versions passed!"
                fi
            else
                run_tests "$version" "$TEST_DIR" "$TEST_PATTERN"
            fi
            ;;
        "analyze")
            analyze_project
            ;;
        "template")
            local template_type=${1:-"github-actions"}
            case $template_type in
                "github-actions")
                    generate_github_actions
                    ;;
                *)
                    log "ERROR" "Unknown template type: $template_type"
                    exit 1
                    ;;
            esac
            ;;
        "docker-build")
            docker_build
            ;;
        "docker-test")
            local version="all"
            [[ $# -gt 0 && "$1" == "--version" ]] && version="$2"
            docker_test "$version"
            ;;
        "validate")
            validate_config
            ;;
        "help"|"")
            usage
            ;;
        *)
            log "ERROR" "Unknown command: $command"
            usage
            exit 1
            ;;
    esac
}

# Run main function
main "$@"

# EOF
