#!/bin/bash
set -euo pipefail

# --- Configuration ---

readonly PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly BUILD_DIR="$PROJECT_ROOT/build"
readonly CACHE_DIR="$BUILD_DIR/.cache"
readonly DIST_DIR="$PROJECT_ROOT/dist"
readonly LIB_DIR="$PROJECT_ROOT/lib"

readonly PLATFORMS=("linux_x86_64" "windows_x86_64")
readonly BUILD_TYPES=("debug" "release")

readonly MINGW_TOOLCHAIN_FILE="$PROJECT_ROOT/mingw-toolchain.cmake"

readonly PACK_MANIFOLD_FILE="data.manifest"
readonly PACK_EXTENSION=".zip"
readonly PACK_FOLDER="data"
readonly DATA_DIRS=(
    "$PROJECT_ROOT/assets"
    "$PROJECT_ROOT/lua"
)

readonly DEFAULT_PLATFORM="linux_x86_64"
readonly DEFAULT_BUILD_TYPE="debug"
readonly DEFAULT_PACKAGE_TYPE="nozip"

# --- Utility Functions --- 

log_info() {
    echo -e "\033[1;36m[INFO]\033[0m $@"
}

log_success() {
    echo -e "\033[1;32m[SUCCESS]\033[0m $@"
}

log_error() {
    echo -e "\033[1;31m[ERROR]\033[0m $@"
}

get_build_dir() {
    local platform=$1
    local build_type=$2
    echo "$BUILD_DIR/$platform/$build_type"
}

get_project_name() {
    local build_dir=$1
    local cache_file="$build_dir/CMakeCache.txt"
    sed -n 's/^CACHED_PROJECT_NAME:STRING=//p' "$cache_file" | head -n 1
}

get_zip_name() {
    local project_name=$1
    local platform=$2
    local build_type=$3
    echo "${project_name}-${platform}-${build_type}.zip"
}

check_commands() {
    local missing=0
    for cmd in cmake make zip git; do
        if ! command -v "$cmd" &> /dev/null; then
            log_error "Required command '$cmd' not found."
            missing=1
        fi
    done
    if [ $missing -ne 0 ]; then exit 1; fi
}

get_cores() {
    if command -v "nproc" &> /dev/null; then
        nproc --all
    else
        echo 1 
    fi
}

# --- Platform Hooks ---
#
# Define platform-specific hook functions here:
#   - prebuild_hook_<platform>: Before cmake build
#   - postbuild_hook_<platform>: After build, before packaging
#   - postpackage_hook_<platform>: After packaging
#
# Parameters: build_dir, project_name, platform, build_type
# Example: postbuild_hook_windows_x86_64() { echo "Post-build for $2 on $3"; }
#

postbuild_hook_linux_x86_64() {
    local build_dir=$1
    local project_name=$2
    local platform=$3
    local build_type=$4
    
    local project_dir="$build_dir/$project_name"
    
    # Only create launcher if it's not already a script
    if [ ! -f "$project_dir/.${project_name}" ]; then
        mv "$project_dir/$project_name" "$project_dir/.${project_name}"
        
        cat > "$project_dir/$project_name" <<-	EOF
		#!/bin/bash
		DIR="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
		cd "\$DIR"
		export LD_LIBRARY_PATH="\$DIR:\$LD_LIBRARY_PATH"
		exec "\$DIR/.${project_name}" "\$@"
		EOF
        
        chmod +x "$project_dir/$project_name"
    fi
}

# --- Build Functions ---

init() {
    log_info "Initializing build environment"
    for platform in "${PLATFORMS[@]}"; do
        for build_type in "${BUILD_TYPES[@]}"; do
            mkdir -p "$(get_build_dir "$platform" "$build_type")"
        done
    done

    # linux targets
    cd "$(get_build_dir "linux_x86_64" "debug")"
    cmake -DPLATFORM=linux_x86_64 -DCMAKE_BUILD_TYPE=Debug "$PROJECT_ROOT"
    cd "$(get_build_dir "linux_x86_64" "release")"
    cmake -DPLATFORM=linux_x86_64 -DCMAKE_BUILD_TYPE=Release "$PROJECT_ROOT"

    # windows targets
    cd "$(get_build_dir "windows_x86_64" "debug")"
    cmake -DPLATFORM=windows_x86_64 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE="$MINGW_TOOLCHAIN_FILE" "$PROJECT_ROOT"
    cd "$(get_build_dir "windows_x86_64" "release")"
    cmake -DPLATFORM=windows_x86_64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="$MINGW_TOOLCHAIN_FILE" "$PROJECT_ROOT"
    
    cd "$PROJECT_ROOT"
    log_success "Build environment initialized"
}

package() {
    local project_dir="$1"
    local output_dir="$project_dir/$PACK_FOLDER"

    mkdir -p "$output_dir"
    mkdir -p "$CACHE_DIR"    
    
    # Pack data files
    local filenames=()
    for data_dir in "${DATA_DIRS[@]}"; do
        local parent_dir="$(dirname "$data_dir")"
        local data_folder="$(basename "$data_dir")"
        local pak_file_name="${data_folder}${PACK_EXTENSION}"
        (
            cd "$parent_dir"
            zip -FSr "$CACHE_DIR/$pak_file_name" "$data_folder"
        )
        cp -upv "$CACHE_DIR/$pak_file_name" "$output_dir"
        filenames+=("$PACK_FOLDER/$pak_file_name")
    done

    # Create manifest file
    local manifest_file="$project_dir/$PACK_MANIFOLD_FILE"
    {
        echo "# auto-generated by build.sh"
        for filename in "${filenames[@]}"; do
            echo "$filename"
        done
    } > "$manifest_file"
}

dist() {
    local build_dir=$1
    local project_name=$2
    local platform=$3
    local build_type=$4
    local zip_name="$(get_zip_name "$project_name" "$platform" "$build_type")"

    mkdir -p "$DIST_DIR"
    log_info "Creating archive: $zip_name"
    (
        cd "$build_dir"
        zip -FSr "$zip_name" "$project_name"
    )
    cp -upv "$build_dir/$zip_name" "$DIST_DIR"
    log_success "Archive created: $zip_name"
}

build() {
    local platform=$1
    local build_type=$2
    local package_type=$3

    [ "$build_type" != "debug" ] && [ "$build_type" != "release" ] && log_error "Invalid build type: $build_type" && exit 1
    [ "$package_type" != "zip" ] && [ "$package_type" != "nozip" ] && log_error "Invalid package type: $package_type" && exit 1
    
    local build_dir="$(get_build_dir "$platform" "$build_type")"
    [ ! -d "$build_dir" ] && log_info "build environment not initialized, running ./build.sh init" && init

    local project_name="$(get_project_name "$build_dir")"

    # Run pre-build hooks
    type -t "prebuild_hook_$platform" &>/dev/null && "prebuild_hook_$platform" "$build_dir" "$project_name" "$platform" "$build_type"
    
    # Build cmake project
    log_info "Building $project_name $platform $build_type"
    cmake --build "$build_dir" -j "$(get_cores)" || {
        log_error "Build failed for $platform $build_type"
        exit 1
    }
    log_success "Build $project_name $platform $build_type successful"

    # Ensure dynamic libraries are available
    local lib_dir="$LIB_DIR/$platform"
    cp -upv "$lib_dir"/* "$build_dir/$project_name/"

    # Run post-build hooks
    type -t "postbuild_hook_$platform" &>/dev/null && "postbuild_hook_$platform" "$build_dir" "$project_name" "$platform" "$build_type"

    # Package data files
    package "$build_dir/$project_name"
    
    # Run post-package hooks
    type -t "postpackage_hook_$platform" &>/dev/null && "postpackage_hook_$platform" "$build_dir" "$project_name" "$platform" "$build_type"

    # Create distribution archive if requested
    if [ "$package_type" == "zip" ]; then
        dist $build_dir "$project_name" "$platform" "$build_type"
    fi
}

all() {
    local package_type=$1
    for platform in "${PLATFORMS[@]}"; do
        for build_type in "${BUILD_TYPES[@]}"; do
            log_info "Building $platform $build_type"
            build "$platform" "$build_type" "$package_type"
        done
    done
    log_success "All builds completed"
}

run() {
    local platform=$1
    local build_type=$2
    local package_type=$3
    
    build "$platform" "$build_type" "$package_type"

    local build_dir="$(get_build_dir "$platform" "$build_type")"
    local project_name="$(get_project_name "$build_dir")"

    log_info "Running $project_name $platform $build_type"
    cd $build_dir/$project_name
    [ -f $project_name ] && ./$project_name
}

clean() {
    log_info "Cleaning build environment"
    rm -rf ${BUILD_DIR} 
}

# --- Command Handling ---

show_help() {
    echo "Usage: $0 <command> [options]"
    echo "Commands:"
    echo "  init             Initialize the build environment"
    echo "  linux_x86_64     Build for Linux x86_64 [debug|release] [zip]"
    echo "  windows_x86_64   Build for Windows x86_64 [debug|release] [zip]"
    echo "  all              Build for all platforms and types [zip]"
    echo "  run              Build and runs default target [debug|release] [zip]"
    echo "  clean            Clean build environment"
    echo "  help             Show this help message"
}

check_commands

if [ $# -eq 0 ]; then
    show_help
    exit 1
fi

case $1 in
    init)
        clean
        init
        ;;
    linux_x86_64)
        build "linux_x86_64" "${2:-$DEFAULT_BUILD_TYPE}" "${3:-$DEFAULT_PACKAGE_TYPE}"
        ;;
    windows_x86_64)
        build "windows_x86_64" "${2:-$DEFAULT_BUILD_TYPE}" "${3:-$DEFAULT_PACKAGE_TYPE}"
        ;;
    all)
        all "${2:-$DEFAULT_PACKAGE_TYPE}"
        ;;
    run)
        run "${DEFAULT_PLATFORM}" "${2:-$DEFAULT_BUILD_TYPE}" "${3:-$DEFAULT_PACKAGE_TYPE}"
        ;;
    clean)
        clean
        ;;
    help|--help|-h)
        show_help
        ;;
    *) 
        log_error "Unknown command: $1"
        show_help
        exit 1
        ;; 
esac

exit 0
