#!/bin/bash

set -e

CC="${CC:-clang}"

if command -v ccache >/dev/null 2>&1; then
    CC="ccache $CC"
fi

CFLAGS="-std=c23 -Wall -Wextra"
LIBS="-lm"
MODE="debug"
BUILDDIR=".build"
BINDIR="bin"

if command -v mold >/dev/null 2>&1; then
    LINKER_FLAGS="-fuse-ld=mold"
else
    LINKER_FLAGS=""
fi

case "$(uname)" in
    Linux)
        LINKER_FLAGS="$LINKER_FLAGS -rdynamic"
        ;;
    Darwin)
        export MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion | cut -d '.' -f 1)"
        ;;
    MINGW*|MSYS*|CYGWIN*)
        LINKER_FLAGS="$LINKER_FLAGS -Wl,--export-all-symbols"
        ;;
    *)
        LINKER_FLAGS="$LINKER_FLAGS -rdynamic"
        ;;
esac

BOLD='\033[1m'
GREEN='\033[38;2;184;187;38m'
NC='\033[0m'
RED='\033[38;2;251;73;52m'

if [[ "$(uname)" == "Linux" ]]; then
    CFLAGS="$CFLAGS -D_GNU_SOURCE"
fi

build_luajit() {
    if [[ ! -f "lib/luajit/src/libluajit.a" ]]; then
        print_info "Building LuaJIT"
        cd lib/luajit

        make clean >/dev/null 2>&1 || true
        make -j$(nproc 2>/dev/null || echo 4) > /dev/null 2>&1
        cd - > /dev/null
        print_info "Built LuaJIT"
    fi
}

build_server() {
    local mode="$1"
    if [[ -d "server" ]]; then
        print_info "Building server ($mode mode)"
        cd server
        if [[ "$mode" == "release" ]]; then
            cargo build --release
        else
            cargo build
        fi
        local status=$?
        cd - > /dev/null
        if [[ $status -eq 0 ]]; then
            print_info "Built server"
        else
            print_error "Server build failed"
        fi
    fi
}

start_server() {
    local mode="$1"
    local server_bin
    if [[ "$mode" == "release" ]]; then
        server_bin="server/target/release/pxl8-server"
    else
        server_bin="server/target/debug/pxl8-server"
    fi
    print_info "Server mode: $mode, binary: $server_bin"
    if [[ -f "$server_bin" ]]; then
        print_info "Starting server..."
        ./$server_bin &
        SERVER_PID=$!
        print_info "Server started with PID $SERVER_PID"
        sleep 0.5
    else
        print_error "Server binary not found: $server_bin"
        print_error "Build the server first with: cd server && cargo build"
    fi
}

stop_server() {
    if [[ -n "$SERVER_PID" ]]; then
        print_info "Stopping server"
        kill $SERVER_PID 2>/dev/null || true
        wait $SERVER_PID 2>/dev/null || true
    fi
}

build_sdl() {
    if [[ ! -f "lib/SDL/build/libSDL3.so" ]] && [[ ! -f "lib/SDL/build/libSDL3.a" ]] && [[ ! -f "lib/SDL/build/libSDL3.dylib" ]]; then
        print_info "Building SDL3"
        mkdir -p lib/SDL/build
        cd lib/SDL/build
        cmake .. -DCMAKE_BUILD_TYPE=Release > /dev/null 2>&1
        cmake --build . --parallel $(nproc 2>/dev/null || echo 4) > /dev/null 2>&1
        cd - > /dev/null
        print_info "Built SDL3"
    fi
}

compile_source_file() {
    local src_file="$1"
    local obj_file="$2"
    local compile_flags="$3"

    print_info "Compiling: $src_file"
    if ! $CC -c $compile_flags "$src_file" -o "$obj_file"; then
        print_error "Compilation failed for $src_file"
        exit 1
    fi
}


make_lib_dirs() {
    mkdir -p lib/linenoise lib/fennel lib/miniz
}

print_error() {
    echo -e "${RED}${BOLD}[$(timestamp) ERROR]${NC} $1" >&2
}

print_info() {
    echo -e "${GREEN}${BOLD}[$(timestamp) INFO]${NC} $1"
}

print_usage() {
    echo -e "${BOLD}pxl8${NC} - framework build tool"
    echo
    echo -e "${BOLD}USAGE:${NC}"
    echo "    ./pxl8.sh <command> [options]"
    echo
    echo -e "${BOLD}COMMANDS:${NC}"
    echo "    ase          Aseprite tools (use 'ase help' for subcommands)"
    echo "    build        Build pxl8"
    echo "    clean        Remove build artifacts"
    echo "    help         Show this help message"
    echo "    install      Install pxl8 to ~/.local/bin"
    echo "    run          Build and run pxl8 (optional: cart.pxc or folder)"
    echo "    update       Download/update all dependencies"
    echo "    vendor       Fetch source for dependencies (ex. SDL3)"
    echo
    echo -e "${BOLD}OPTIONS:${NC}"
    echo "    --all        Clean both build artifacts and dependencies"
    echo "    --deps       Clean only dependencies"
    echo "    --release    Build/run/clean in release mode (default: debug)"
    echo "    --server     Start game server before running (for networked games)"
}

setup_sdl3() {
    if [[ -d "lib/SDL/build" ]]; then
        SDL3_CFLAGS="-Ilib/SDL/include"
        SDL3_LIBS="-Llib/SDL/build -lSDL3"
        SDL3_RPATH="-Wl,-rpath,$(pwd)/lib/SDL/build"
        print_info "Using vendored SDL3"
    else
        SDL3_CFLAGS=$(pkg-config --cflags sdl3 2>/dev/null || echo "")
        SDL3_LIBS=$(pkg-config --libs sdl3 2>/dev/null || echo "")
        SDL3_RPATH=""

        if [[ -z "$SDL3_LIBS" ]]; then
            case "$(uname)" in
                Darwin)
                    if [[ -f "/usr/local/include/SDL3/SDL.h" ]]; then
                        SDL3_CFLAGS="-I/usr/local/include/SDL3"
                        SDL3_LIBS="-L/usr/local/lib -lSDL3"
                    fi
                    ;;
                Linux)
                    if [[ -f "/usr/include/SDL3/SDL.h" ]]; then
                        SDL3_CFLAGS="-I/usr/include/SDL3"
                        SDL3_LIBS="-lSDL3"
                    fi
                    ;;
                MINGW*|MSYS*|CYGWIN*)
                    if [[ -f "/mingw64/include/SDL3/SDL.h" ]]; then
                        SDL3_CFLAGS="-I/mingw64/include/SDL3"
                        SDL3_LIBS="-lSDL3"
                    fi
                    ;;
            esac
        fi

        if [[ -z "$SDL3_LIBS" ]]; then
            print_info "System SDL3 not found, vendoring SDL3..."
            update_sdl
            build_sdl
            SDL3_CFLAGS="-Ilib/SDL/include"
            SDL3_LIBS="-Llib/SDL/build -lSDL3"
            SDL3_RPATH="-Wl,-rpath,$(pwd)/lib/SDL/build"
            print_info "Using vendored SDL3"
        else
            print_info "Using system SDL3"
        fi
    fi

    CFLAGS="$CFLAGS $SDL3_CFLAGS"
    LIBS="$LIBS $SDL3_LIBS $SDL3_RPATH"
}

timestamp() {
    date +"%H:%M:%S"
}

update_fennel() {
    print_info "Fetching Fennel"

    local version="1.6.1"

    if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then
        if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then
            print_info "Updated Fennel (${version})"
        else
            print_error "Downloaded file is empty or missing"
            return 1
        fi
    else
        print_error "Failed to download Fennel"
        return 1
    fi
}

update_linenoise() {
    print_info "Fetching linenoise"

    if curl -sL --max-time 5 -o lib/linenoise/linenoise.c https://raw.githubusercontent.com/antirez/linenoise/master/linenoise.c 2>/dev/null && \
       curl -sL --max-time 5 -o lib/linenoise/linenoise.h https://raw.githubusercontent.com/antirez/linenoise/master/linenoise.h 2>/dev/null; then
        print_info "Updated linenoise"
    else
        print_error "Failed to download linenoise"
        return 1
    fi
}

update_luajit() {
    print_info "Fetching LuaJIT"

    local version="2.1"
    local branch="v${version}"

    if [[ -d "lib/luajit/.git" ]]; then
        cd lib/luajit && git pull --quiet origin ${branch}
        cd - > /dev/null
    else
        rm -rf lib/luajit
        git clone --quiet --branch ${branch} https://github.com/LuaJIT/LuaJIT.git lib/luajit
    fi

    print_info "Updated LuaJIT (${version})"
}

update_miniz() {
    print_info "Fetching miniz"

    local version="3.1.0"

    if curl -sL --max-time 5 -o /tmp/miniz.zip "https://github.com/richgel999/miniz/releases/download/${version}/miniz-${version}.zip" 2>/dev/null; then
        unzip -qjo /tmp/miniz.zip miniz.c miniz.h -d lib/miniz/ 2>/dev/null
        rm -f /tmp/miniz.zip

        if [[ -f "lib/miniz/miniz.c" ]] && [[ -f "lib/miniz/miniz.h" ]]; then
            print_info "Updated miniz (${version})"
        else
            print_error "Failed to extract miniz files"
            return 1
        fi
    else
        print_error "Failed to download miniz"
        return 1
    fi
}

update_sdl() {
    print_info "Fetching SDL3"

    if [[ -d "lib/SDL/.git" ]]; then
        cd lib/SDL && git pull --quiet origin main
        cd - > /dev/null
    else
        rm -rf lib/SDL
        git clone --quiet https://github.com/libsdl-org/SDL.git lib/SDL
    fi

    print_info "Updated SDL3"
}

COMMAND="$1"
shift || true

for arg in "$@"; do
    case $arg in
        --release)
            MODE="release"
            ;;
    esac
done

if [ "$MODE" = "release" ]; then
    CFLAGS="$CFLAGS -O3 -flto -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables"
    LINKER_FLAGS="$LINKER_FLAGS -flto"
    BUILDDIR="$BUILDDIR/release"
    BINDIR="$BINDIR/release"
else
    CFLAGS="$CFLAGS -g -O1 -DDEBUG"
    BUILDDIR="$BUILDDIR/debug"
    BINDIR="$BINDIR/debug"
fi

DEP_CFLAGS="-O3 -funroll-loops"

case "$COMMAND" in
    build)
        mkdir -p "$BUILDDIR"
        mkdir -p "$BINDIR"
        
        if [[ ! -d "lib/luajit" ]] || \
           [[ ! -f "lib/linenoise/linenoise.c" ]] || \
           [[ ! -f "lib/miniz/miniz.c" ]] || \
           [[ ! -f "lib/fennel/fennel.lua" ]]; then
            print_info "Missing dependencies, fetching..."

            make_lib_dirs
            update_fennel
            update_linenoise
            update_luajit
            update_miniz
        fi

        if [[ ! -f "bin/.gitignore" ]]; then
            echo "*" > "bin/.gitignore"
        fi

        if [[ ! -f ".build/.gitignore" ]]; then
            echo "*" > ".build/.gitignore"
        fi

        if [[ ! -f "lib/.gitignore" ]]; then
            echo "*" > "lib/.gitignore"
        fi

        if [[ -d "lib/luajit" ]]; then
            build_luajit
        fi

        if [[ -d "lib/SDL" ]]; then
            build_sdl
        fi

        setup_sdl3
        print_info "Building pxl8 ($MODE mode)"

        if [[ "$CC" == ccache* ]]; then
            print_info "Compiler cache: ccache enabled"
        fi

        INCLUDES="-Isrc/asset -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/hal -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/sfx -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz"
        COMPILE_FLAGS="$CFLAGS $INCLUDES"
        DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES"

        EXECUTABLE="$BINDIR/pxl8"

        LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c"

        PXL8_SOURCE_FILES="
            src/core/pxl8.c
            src/core/pxl8_bytes.c
            src/core/pxl8_io.c
            src/core/pxl8_log.c
            src/core/pxl8_rng.c
            src/math/pxl8_math.c
            src/gfx/pxl8_anim.c
            src/gfx/pxl8_atlas.c
            src/gfx/pxl8_blit.c
            src/gfx/pxl8_3d_camera.c
            src/gfx/pxl8_colormap.c
            src/gfx/pxl8_cpu.c
            src/gfx/pxl8_dither.c
            src/gfx/pxl8_font.c
            src/gfx/pxl8_gfx.c
            src/gfx/pxl8_lightmap.c
            src/gfx/pxl8_mesh.c
            src/gfx/pxl8_palette.c
            src/gfx/pxl8_particles.c
            src/gfx/pxl8_tilemap.c
            src/gfx/pxl8_tilesheet.c
            src/gfx/pxl8_transition.c
            src/sfx/pxl8_sfx.c
            src/script/pxl8_repl.c
            src/script/pxl8_script.c
            src/hal/pxl8_sdl3.c
            src/world/pxl8_bsp.c
            src/world/pxl8_gen.c
            src/world/pxl8_world.c
            src/procgen/pxl8_graph.c
            src/asset/pxl8_ase.c
            src/asset/pxl8_cart.c
            src/asset/pxl8_save.c
            src/gui/pxl8_gui.c
            src/core/pxl8_replay.c
            src/net/pxl8_net.c
            src/net/pxl8_protocol.c
        "

        LUAJIT_LIB="lib/luajit/src/libluajit.a"
        OBJECT_DIR="$BUILDDIR/obj"
        mkdir -p "$OBJECT_DIR"
        
        OBJECTS=""
        NEED_LINK=false
        SOURCES_COMPILED=""
        
        for src_file in $LIB_SOURCE_FILES; do
            obj_name=$(basename "$src_file" .c).o
            obj_file="$OBJECT_DIR/$obj_name"
            OBJECTS="$OBJECTS $obj_file"

            if [[ "$src_file" -nt "$obj_file" ]]; then
                NEED_LINK=true
                compile_source_file "$src_file" "$obj_file" "$DEP_COMPILE_FLAGS"
                SOURCES_COMPILED="yes"
            fi
        done
        
        for src_file in $PXL8_SOURCE_FILES; do
            obj_name=$(basename "$src_file" .c).o
            obj_file="$OBJECT_DIR/$obj_name"
            OBJECTS="$OBJECTS $obj_file"

            NEEDS_REBUILD=false
            if [[ "$src_file" -nt "$obj_file" ]] || \
               [[ "src/core/pxl8_types.h" -nt "$obj_file" ]] || \
               [[ "src/core/pxl8_macros.h" -nt "$obj_file" ]]; then
                NEEDS_REBUILD=true
            fi

            if [[ "$src_file" == "src/script/pxl8_script.c" ]]; then
                for lua_file in src/lua/*.lua src/lua/pxl8/*.lua lib/fennel/fennel.lua; do
                    if [[ -f "$lua_file" ]] && [[ "$lua_file" -nt "$obj_file" ]]; then
                        NEEDS_REBUILD=true
                        break
                    fi
                done
            fi

            if [[ "$NEEDS_REBUILD" == true ]]; then
                NEED_LINK=true
                compile_source_file "$src_file" "$obj_file" "$COMPILE_FLAGS"
                SOURCES_COMPILED="yes"
            fi
        done
        
        if [[ "$LUAJIT_LIB" -nt "$EXECUTABLE" ]] || [[ "$NEED_LINK" == true ]]; then
            print_info "Linking executable"
            if ! $CC $LINKER_FLAGS $OBJECTS $LUAJIT_LIB $LIBS -o "$EXECUTABLE"; then
                print_error "Linking failed"
                exit 1
            fi
        elif [[ -z "$SOURCES_COMPILED" ]]; then
            print_info "All files are up to date, skipping build"
        fi
        
        print_info "Build complete"
        print_info "Binary: $EXECUTABLE"
        ;;

    run)
        "$0" build "$@" || exit 1

        CART=""
        EXTRA_ARGS=""
        RUN_SERVER=false
        for arg in "$@"; do
            if [[ "$arg" == "--release" ]]; then
                MODE="release"
            elif [[ "$arg" == "--repl" ]]; then
                EXTRA_ARGS="$EXTRA_ARGS --repl"
            elif [[ "$arg" == "--server" ]]; then
                RUN_SERVER=true
            elif [[ -z "$CART" ]]; then
                CART="$arg"
            fi
        done

        if [[ "$RUN_SERVER" == true ]]; then
            build_server "$MODE"
            start_server "$MODE"
            trap stop_server EXIT
        fi

        if [[ -z "$CART" ]]; then
            "$BINDIR/pxl8" $EXTRA_ARGS
        else
            "$BINDIR/pxl8" "$CART" $EXTRA_ARGS
        fi
        ;;

    clean)
        CLEAN_ALL=false
        CLEAN_DEPS=false
        CLEAN_RELEASE=false

        for arg in "$@"; do
            case "$arg" in
                --all) CLEAN_ALL=true ;;
                --deps) CLEAN_DEPS=true ;;
                --release) CLEAN_RELEASE=true ;;
            esac
        done

        if [[ "$CLEAN_RELEASE" == true ]]; then
            BUILD_PATH=".build/release"
            BIN_PATH="bin/release"
            MODE="release"
        else
            BUILD_PATH=".build/debug"
            BIN_PATH="bin/debug"
            MODE="debug"
        fi

        if [[ "$CLEAN_ALL" == true ]]; then
            print_info "Removing build artifacts and dependencies"
            rm -rf "$BUILD_PATH" "$BIN_PATH" lib
            print_info "Cleaned all"
        elif [[ "$CLEAN_DEPS" == true ]]; then
            print_info "Removing dependencies"
            rm -rf lib
            print_info "Cleaned dependencies"
        else
            print_info "Removing build artifacts"
            rm -rf "$BUILD_PATH" "$BIN_PATH"
            print_info "Cleaned"
        fi
        ;;

    update)
        make_lib_dirs
        update_fennel
        update_linenoise
        update_luajit
        update_miniz
        print_info "All dependencies updated"
        ;;

    vendor)
        update_sdl
        ;;

    install)
        "$0" build --release || exit 1

        INSTALL_DIR="${HOME}/.local/bin"
        mkdir -p "$INSTALL_DIR"

        cp "bin/release/pxl8" "$INSTALL_DIR/pxl8"
        chmod +x "$INSTALL_DIR/pxl8"

        print_info "Installed pxl8 to $INSTALL_DIR/pxl8"

        if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
            print_info "Note: Add $INSTALL_DIR to your PATH if not already"
        fi
        ;;

    ase)
        bash tools/aseprite/pxl8-ase.sh "$@"
        ;;

    help|--help|-h|"")
        print_usage
        ;;

    *)
        print_error "Unknown command: $COMMAND"
        print_usage
        exit 1
        ;;
esac
