#!/bin/sh -eu
# _tutmark
# libdmake.sh - simple build system; modular, orthogonal, sexy & posix
#               static linking made obligatory
#
# Mini-Tutorial:
# ==============
# 1. Mandatory Variables:
#    NAME, VERSION : Package identity
#                    VERSION can be "git" or "git:commithash" for git sources
#    SOURCE        : URLs    : url_primary || \\
#                              mirror1
#                    Git     : https://gitforge.com/user/repo.git
#                    Patches : disable-google-telemetry.diff
#                    Files   : sndiod::sv
#                    Suffixes: ::noextract, ::patch_args="-p0", ::sv, ::tag
#
# 2. Optional Variables:
#    BUILD_STYLE   : Force specific system (configure, cmake, meson, make, ...)
#    BUILD_OPT     : Arguments appended to the build command
#    SKIP_PATCHES  : Set to 1 to ignore patch files in SOURCE
#
# 3. Customization:
#    Define a 'build()' function to override auto-detection entirely
#    Define 'prebuild()' if needed (runs before build/auto-detection)
#    Define 'postbuild()' if needed
#    The script provides $SRC, $PKG, and $NPROC.
# _tutmark

die() { printf '\033[1;31mERROR:\033[m %s\n' "$*" >&2; exit 1; }
msg() { printf '\033[1;34m[%s]\033[m %s\n' "${NAME:-dmake}" "$*"; }

read_markers(){ sed -n "/^$1\$/,/^$2\$/{/^$1\$/d;/^$2\$/d;p;}" ${3:--}; }
has_cmd() { command -v "$1" >/dev/null 2>&1; }

# resolve absolute path of this file
case $0 in
/*) _LIBDMAKE_SH=$0 ;;
*)  _LIBDMAKE_SH=$PWD/$0 ;;
esac

if [ "$_LIBDMAKE_SH" = "$PWD/libdmake.sh" ] || [ "${0##*/}" = "libdmake.sh" ]; then
	read_markers "# _tutmark" "# _tutmark" "$_LIBDMAKE_SH"
	read_markers "# _def" "# _def" "$_LIBDMAKE_SH"
	die "libdmake.sh must not be executed directly."
fi

trim() {
    _t_val="$1"
    _t_val="${_t_val#"${_t_val%%[![:space:]]*}"}"
    _t_val="${_t_val%"${_t_val##*[![:space:]]}"}"
    printf '%s' "$_t_val"
}

sed_i() { sed "$1" "$2" > "$2.tmp" && mv "$2.tmp" "$2"; }

# Updates version in info file (replaces or appends)
_update_info_file() {
    _ver="$VERSION"
    
    # Handle git versions
    if [ "$_ver" = "git" ]; then
        # Try to read the hash captured during clone
        if [ -f "${PORTDIR}/.git_head" ]; then
            _ver="$(cat "${PORTDIR}/.git_head")"
        fi
    elif [ "${_ver#git:}" != "$_ver" ]; then
        # Extract hash from git:hash
        _ver="${_ver#git:}"
    fi
    
    # Determine info file location
    _target=""
    if [ -f "${PORTDIR}/info" ]; then
        _target="${PORTDIR}/info"
    elif [ -f "${INFO_FILE}" ]; then
        _target="${INFO_FILE}"
    else
        # Default if creating new
        _target="${INFO_FILE}"
    fi
    
    msg "updating $_target version to $_ver"
    
    # Ensure file exists
    [ ! -f "$_target" ] && touch "$_target"

    if grep -q "^version:" "$_target"; then
        # Replace existing version field using temp file for POSIX sed
        sed_i "s/^version:[[:space:]]*.*/version: $_ver/" "$_target"
    else
        # Append if missing
        printf "version: %s\n" "$_ver" >> "$_target"
    fi
}

# Resolves directory of the calling script
get_portdir() {
    if [ -f "$1" ]; then (cd "$(dirname "$1")" && pwd); else pwd; fi
}

# git handling
_clone_git() {
    _url="$1"
    _commit="${2:-}"
    _out="$3"

    _tmpdir="/tmp/git-dmake-$$"
    mkdir -p "$_tmpdir"

    if [ -n "$_commit" ]; then
        msg "cloning git repository (full clone for git:commit/tag)..."
        git clone "$_url" "$_tmpdir/repo" || die "Failed to clone: $_url"
    else
        msg "cloning git repository (depth 1)..."
        # shallow clone first
        if git clone --depth 1 "$_url" "$_tmpdir/repo" 2>/dev/null; then
            msg "shallow clone successful"
        else
            msg "shallow clone failed, attempting full clone..."
            git clone "$_url" "$_tmpdir/repo" || die "Failed to clone: $_url"
        fi
    fi

    cd "$_tmpdir/repo" || die "Failed to enter cloned repository"

    if [ -n "$_commit" ]; then
        msg "checking out commit: $_commit"
        git checkout "$_commit" || die "Failed to checkout commit: $_commit"
    else
        msg "on latest commit from default branch"
    fi

    # Capture hash for gen-checksum logic
    git rev-parse HEAD > "${PORTDIR}/.git_head"

    msg "creating archive from repo..."
    tar -C "$_tmpdir/repo" --exclude=.git --exclude=.gitignore -czf "$_out" . || die "Failed to create archive from git clone"

    rm -rf "$_tmpdir"
    msg "git archive created: $_out"
}

# _def
PORTDIR="$(get_portdir "$0")"
: "${NAME:?NAME must be set}"
: "${VERSION:?VERSION must be set}"
: "${RELEASE:=1}"
: "${SOURCE:?SOURCE must be set}"
: "${BUILD_OPT:=}"
: "${PREFIX:=/usr}"
: "${DISTDIR:=/var/cache/dtr/distfiles}"
: "${BUILDBASE:=/var/tmp/dtr}"
: "${CFLAGS:=-O2 -pipe}"
: "${CXXFLAGS:=-O2 -pipe}"
: "${LDFLAGS:=-static}"
: "${NPROC:=$(nproc 2>/dev/null || echo 1)}"
# _def

# Git version handling
_GIT_COMMIT=""
if [ "${VERSION#git}" != "$VERSION" ]; then
    case "$VERSION" in
        git:*)
            _GIT_COMMIT="${VERSION#git:}"
            ;;
    esac
fi

PKG_ID="${NAME}-${VERSION}"
SRC="${BUILDBASE}/src-${PKG_ID}"
PKG="${BUILDBASE}/pkg-${PKG_ID}"
CHECKSUM_FILE="${PORTDIR}/.checksum"
INFO_FILE="${PORTDIR}/.info"

# Source Parser
# =============
parse_source() {
    [ -z "$SOURCE" ] && return 0
    _cb="$1"

    # Collapse continuation lines: remove backslash and following whitespace+newline
    _collapsed=$(printf '%s\n' "$SOURCE" | sed ':a;/\\$/{N;s/\\\n[[:space:]]*//;ta;}')

    printf '%s\n' "$_collapsed" | while IFS= read -r _line || [ -n "$_line" ]; do
        _line="$(trim "$_line")"
        [ -z "$_line" ] && continue
        case "$_line" in \#*) continue ;; esac # Skip comments
        case "$_line" in *\#*) die "Inline comments illegal: '$_line'" ;; esac

        # 1. Extract Tags (Right-to-Left)
        _opts=""
        _clean_line="$_line"
        while :; do
            case "$_clean_line" in
                *::*)
                    _tag="${_clean_line##*::}"
                    _opts="$_tag $_opts"
                    _clean_line="${_clean_line%::*}"
                    ;;
                *) break ;;
            esac
        done

        # 2. Split Mirrors (||)
        _raw_url=""
        _mirrors=""
        case "$_clean_line" in
            *"||"*)
                # Extract everything before first ||
                _raw_url="${_clean_line%%"||"*}"
                # Extract everything after first ||
                _mirrors="${_clean_line#*"||"}"
                ;;
            *)
                _raw_url="$_clean_line"
                ;;
        esac

        _url="$(trim "$_raw_url")"

        # extract filename from primary URL only
        case "$_url" in
            *://*) _filename="${_url##*/}" ;;
            *)     _filename="$_url" ;;
        esac

        # use .tar.gz extension if not already present for git sources
        _is_git_source=0
        case "$_url" in
            *git@*:*|git://*|*://*.git|*://git.*|*://*/git|*github.com*|*gitlab.com*|*codeberg.org*|*git.sr.ht*|*git.suckless.org*)
                _is_git_source=1 ;;
        esac
        case "${VERSION:-}" in git|git:*) _is_git_source=1 ;; esac
        if [ "$_is_git_source" -eq 1 ]; then
            case "$_filename" in
                *.tar.gz|*.tgz|*.tar.xz|*.txz|*.tar.bz2|*.tbz2) ;;
                *.git) _filename="${_filename%.git}.tar.gz" ;;
                *) _filename="${_filename}.tar.gz" ;;
            esac
        fi

        [ -z "$_filename" ] && die "Cannot determine filename from '$_url'"

        "$_cb" "$_filename" "$_url" "$_mirrors" "$_opts"
    done
}

# Fetch thingies
# ==============
_download_file() {
    _out="$1" _uri="$2"
    _resume="${3:-0}"
    
    _curl_opts="-fL --retry 3 --connect-timeout 10"
    _wget_opts="-t 3 -T 10"
    
    if [ "$_resume" -eq 1 ]; then
        _curl_opts="$_curl_opts -C -"
        _wget_opts="$_wget_opts -c"
    fi

    if has_cmd curl; then
        curl $_curl_opts -o "$_out" "$_uri"
    elif has_cmd wget; then
        wget $_wget_opts -O "$_out" "$_uri"
    else
        die "No download tool found (need curl or wget)"
    fi
}

_do_fetch() {
    _fn="$1" _url="$2" _mirrors="$3" _tags="$4"
    _dest="${DISTDIR}/${_fn}"
    _tmp="${_dest}.part"
    _resume=0

    # local file in port dir or given path
    _local_path=""
    case "$_url" in
        *://*|git@*:* ) ;;
        *)
            if [ -f "$_url" ]; then
                _local_path="$_url"
            elif [ -f "$PORTDIR/$_url" ]; then
                _local_path="$PORTDIR/$_url"
            fi
            ;;
    esac

    if [ -n "$_local_path" ]; then
        mkdir -p "$DISTDIR"
        if [ -f "$_dest" ] && _verify_checksum_single "$_fn" "$_dest" 1; then
            msg "using cached file: $_fn"
            return 0
        fi
        msg "using local file: $_local_path"
        cp "$_local_path" "$_dest" || die "Failed to copy local file: $_local_path"
        return 0
    fi

    if [ "${VERSION#git}" != "$VERSION" ]; then
        case "$_url" in
            *://*|git@*:*)
                case "$_url" in
                    *.tar.gz|*.tgz|*.tar.xz|*.txz|*.tar.bz2|*.tbz2|*.tar.lz|*.tar.lzma|*.tar.zst|*.tzst|*.zip) ;;
                    *)
                        mkdir -p "$DISTDIR"
                        if [ -f "$_dest" ] && _verify_checksum_single "$_fn" "$_dest" 1; then
                            msg "using cached git archive: $_fn"
                            return 0
                        fi
                        _clone_git "$_url" "$_GIT_COMMIT" "$_dest"
                        return 0
                        ;;
                esac
                ;;
        esac
    fi

    # detect if URL is explicitly a git source (git:// or ends with .git)
    _is_git=0
    case "$_url" in
        *git@*:*|git://*|*://*.git|*://*/.git)
            _is_git=1
            ;;
    esac

    # handle explicitly detected git sources or with _GIT_COMMIT set
    if [ "$_is_git" -eq 1 ] || [ -n "$_GIT_COMMIT" ]; then
        mkdir -p "$DISTDIR"

        if [ -f "$_dest" ]; then
            if _verify_checksum_single "$_fn" "$_dest" 1; then
                msg "using cached git archive: $_fn"
                return 0
            fi
            msg "cached archive checksum invalid, re-fetching..."
            rm -f "$_dest"
        fi

        _clone_git "$_url" "$_GIT_COMMIT" "$_dest"
        return 0
    fi

    # HTTP/HTTPS fetch logic
    if [ -f "$_dest" ]; then
        if _verify_checksum_single "$_fn" "$_dest" 1; then return 0; fi
        msg "checksum mismatch for $_fn, resuming/re-fetching..."
        # If we have a mismatch on a "complete" file, it's corrupt or outdated.
        # But if it's a part file, we resume.
        # Here we just treat partial downloads as .part
    fi

    if [ -f "$_tmp" ]; then
        _resume=1
        msg "resuming $_fn..."
    else
        msg "fetching $_fn..."
    fi

    mkdir -p "$DISTDIR"

    if _download_file "$_tmp" "$_url" "$_resume"; then
        mv "$_tmp" "$_dest"
        return 0
    fi

    # Mirror fallback
    if [ -n "$_mirrors" ]; then
        _rest="$_mirrors"
        while [ -n "$_rest" ]; do
            case "$_rest" in
                *"||"*) _m_raw="${_rest%%||*}"; _rest="${_rest#*||}" ;;
                *)      _m_raw="$_rest";        _rest="" ;;
            esac
            _m_url="$(trim "$_m_raw")"
            [ -z "$_m_url" ] && continue
            msg "trying mirror: $_m_url"
            if _download_file "$_tmp" "$_m_url" 0; then
                mv "$_tmp" "$_dest"
                return 0
            fi
        done
    fi
    die "Failed to fetch $_fn"
}

# Checksum file utilities
# =======================
_do_gen_checksum() {
    _fn="$1" _url="$2" _mirrors="$3" _tags="$4"
    _path="${DISTDIR}/${_fn}"
    [ ! -f "$_path" ] && die "File missing: $_path"
    _hash=$(sha256sum "$_path" | cut -d' ' -f1)
    printf '%s  %s\n' "$_hash" "$_fn" >> "$CHECKSUM_FILE"
    msg "generated checksum for $_fn"
}

_verify_checksum_single() {
    _fn="$1" _path="$2" _silent="${3:-0}"
    [ ! -f "$CHECKSUM_FILE" ] && return 0
    _expected=$(grep -F "  $_fn" "$CHECKSUM_FILE" | tail -n1 | cut -d' ' -f1)

    if [ -z "$_expected" ]; then
        [ "$_silent" -eq 0 ] && die "No checksum found for $_fn"
        return 1
    fi
    _actual=$(sha256sum "$_path" | cut -d' ' -f1)
    if [ "$_actual" != "$_expected" ]; then
        [ "$_silent" -eq 0 ] && die "Checksum mismatch for $_fn"
        return 1
    fi
    return 0
}

# Extraction & Patching
# =====================
_do_extract() {
    _fn="$1" _url="$2" _mirrors="$3" _tags="$4"
    _path="${DISTDIR}/${_fn}"

    [ ! -f "$_path" ] && die "Source missing: $_fn"
    _verify_checksum_single "$_fn" "$_path"

    # Check for 'noextract' tag
    case "$_tags" in
        *noextract*)
            msg "copying $_fn (noextract)..."
            cp "$_path" "$SRC/"
            return 0
            ;;
        *sv*)
            msg "copying $_fn (service)"
            [ -x "$_path" ] || die "$_path is not executable"
            mkdir -p "$PKG/etc/sv" || die "unable to mkdir -p \"$PKG/etc/sv\""
            cp "$_path" "$PKG/etc/sv"
            return 0
    esac

    msg "extracting $_fn..."
    case "$_fn" in
        *.tar.gz|*.tgz)       tar -xzf "$_path" -C "$SRC" ;;
        *.tar.xz|*.txz)       tar -xJf "$_path" -C "$SRC" ;;
        *.tar.bz2|*.tbz2)     tar -xjf "$_path" -C "$SRC" ;;
        *.tar.lz)             tar --lzip -xf "$_path" -C "$SRC" ;;
        *.tar.lzma)           tar --lzma -xf "$_path" -C "$SRC" ;;
        *.tar.zst|*.tzst)     tar --zstd -xf "$_path" -C "$SRC" ;;
        *.zip)                unzip -d "$SRC" "$_path" ;;
        *)                    cp "$_path" "$SRC/" ;;
    esac
}

_do_apply_patch() {
    _fn="$1" _url="$2" _mirrors="$3" _tags="$4"

    # Only process patch files
    case "$_fn" in
        *.patch|*.diff) ;;
        *) return 0 ;;
    esac

    # Check for 'noapply' tag
    case "$_tags" in *noapply*) return 0 ;; esac

    has_cmd patch || die "patch utility missing"

    # Default patch args
    _p_args="-p1"

    # Parse 'patch_args' tag
    for _t in $_tags; do
        case "$_t" in
            patch_args=*)
                _p_args="${_t#*=}"
                _p_args="${_p_args%\"}"; _p_args="${_p_args#\"}"
                _p_args="${_p_args%\'}"; _p_args="${_p_args#\'}"
                ;;
        esac
    done

    msg "applying patch $_fn (args: $_p_args)..."
    patch $_p_args -i "$SRC/$_fn" || die "Failed to apply patch $_fn"
}

# All the build logic goes here
# =============================
_build_hell() {
    msg "build system: hell"
    if [ -f configure.ac ] || [ -f configure.in ]; then
        has_cmd demiurge && demiurge . || msg "warning: demiurge not found"
    fi
    [ ! -f .satan ] && echo "LDFLAGS=${LDFLAGS}" > .satan
    satan gen ${BUILD_OPT} PREFIX="$PREFIX" DESTDIR="$PKG" . || die "satan gen failed"
    if has_cmd samu; then
        samu || die "samu build failed"
        DESTDIR="$PKG" samu install || die "samu install failed"
    else
        ninja || die "ninja build failed"
        DESTDIR="$PKG" ninja install || die "ninja install failed"
    fi
}

_build_meson() {
    msg "build system: meson (using muon/samu)"
    muon setup \
        -Dprefix="$PREFIX" \
        -Dlibdir=lib \
        -Ddefault_library=static \
        -Dprefer_static=true \
        -Dbuildtype=release \
        ${BUILD_OPT} \
        build || die "muon setup failed"
    samu -C build || die "samu build failed"
    DESTDIR="$PKG" muon -C build install || die "muon install failed"
}

_build_cmake() {
    msg "build system: cmake"
    mkdir -p build && cd build
    cmake \
        -DCMAKE_INSTALL_PREFIX="$PREFIX" \
        -DCMAKE_INSTALL_LIBDIR=lib \
        -DCMAKE_BUILD_TYPE=Release \
        -DBUILD_SHARED_LIBS=OFF \
        -DCMAKE_C_FLAGS="$CFLAGS" \
        -DCMAKE_CXX_FLAGS="$CXXFLAGS" \
        -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \
        -DCMAKE_FIND_LIBRARY_SUFFIXES=".a" \
        ${BUILD_OPT} \
        .. || die "cmake config failed"

    if [ -f build.ninja ]; then
        if has_cmd samu; then
            samu || die "samu build failed"
            DESTDIR="$PKG" samu install || die "samu install failed"
        else
            ninja || die "ninja build failed"
            DESTDIR="$PKG" ninja install || die "ninja install failed"
        fi
    else
        make -j"$NPROC" || die "make build failed"
        make DESTDIR="$PKG" install || die "make install failed"
    fi
}

_build_configure() {
    msg "build system: autotools/configure"
    _help=$(./configure --help)
    _configure_opts="--prefix=$PREFIX"

    # Add flags only if configure supports them
    echo "$_help" | grep -q "libdir" && _configure_opts="$_configure_opts --libdir=$PREFIX/lib"
    echo "$_help" | grep -q "mandir" && _configure_opts="$_configure_opts --mandir=$PREFIX/share/man"
    echo "$_help" | grep -q "infodir" && _configure_opts="$_configure_opts --infodir=$PREFIX/share/info"
    echo "$_help" | grep -q "sysconfdir" && _configure_opts="$_configure_opts --sysconfdir=/etc"
    echo "$_help" | grep -q "disable-shared" && _configure_opts="$_configure_opts --disable-shared"
    echo "$_help" | grep -q "enable-static" && _configure_opts="$_configure_opts --enable-static"

    ./configure \
        $_configure_opts \
        LDFLAGS="$LDFLAGS" \
        ${BUILD_OPT} || die "configure failed"

    # Libtool workaround: make sure -all-static is passed
    _make_ldflags="$LDFLAGS"
    if [ -f libtool ] && echo "$LDFLAGS" | grep -q "\-static"; then
        _make_ldflags="$LDFLAGS -all-static"
    fi

    make -j"$NPROC" LDFLAGS="$_make_ldflags" || die "make failed"
    make DESTDIR="$PKG" install || die "make install failed"
}

_build_gmake() {
    msg "build system: gnu make (gmake)"
    gmake -j"$NPROC" \
        PREFIX="$PREFIX" \
        LIBDIR="$PREFIX/lib" \
        CFLAGS="${CFLAGS} -static" \
        CXXFLAGS="${CXXFLAGS} -static" \
        LDFLAGS="${LDFLAGS} -static" \
        CCFLAGS="${CFLAGS} -static" \
        ${BUILD_OPT} || die "gmake failed"

    gmake PREFIX="$PREFIX" DESTDIR="$PKG" ${BUILD_OPT} install || die "gmake install failed"
}

_build_make() {
    msg "build system: make (bmake/std)"
    make -j"$NPROC" \
        PREFIX="$PREFIX" \
        LIBDIR="$PREFIX/lib" \
        CFLAGS="${CFLAGS} -static" \
        CXXFLAGS="${CXXFLAGS} -static" \
        LDFLAGS="${LDFLAGS} -static" \
        CCFLAGS="${CFLAGS} -static" \
        ${BUILD_OPT} || die "make failed"

    make PREFIX="$PREFIX" DESTDIR="$PKG" ${BUILD_OPT} install || die "make install failed"
}

_build_python() {
    msg "build system: python"
    python3 setup.py build || die "python build failed"
    python3 setup.py install --prefix="$PREFIX" --root="$PKG" --optimize=1 ${BUILD_OPT}
}

_build_perl() {
    msg "build system: perl"
    perl Makefile.PL INSTALLDIRS=vendor || die "perl Makefile.PL failed"
    make || die "make failed"
    make DESTDIR="$PKG" install
}

# postbuild(){} and cleanup
# =========================
_prune_shared_libs() {
    [ -d "$PKG" ] || return 0

    find "$PKG" -type f \( -name '*.so' -o -name '*.so.*' \) \
    -exec sh -c '
        shown=0
        for f do
            [ "$(dd if="$f" bs=4 count=1 2>/dev/null | od -An -tx1)" = " 7f 45 4c 46" ] || continue
            [ "$shown" -eq 0 ] && msg "pruning shared elf objects..." && shown=1
            rm -vf "$f"
        done
    ' sh {} +
}
_prune_locales() {
    [ -d "$PKG" ] || return 0

    shown=0

    for d in \
        "$PKG/usr/share/locale" \
        "$PKG/usr/local/share/locale" \
        "$PKG/share/locale" \
        "$PKG/usr/share/man" \
        "$PKG/usr/local/share/man"
    do
        [ -d "$d" ] || continue
        find "$d" -mindepth 1 -type d ! -name 'man[1-9]' -exec rm -rf {} +
        [ "$shown" -eq 0 ] && msg "pruning locales..." && shown=1
    done
}

_strip_binaries() {
    [ -d "$PKG" ] || return 0
    if [ "${NOSTRIP:-0}" = "1" ]; then
        msg "skipping strip (NOSTRIP=1)"
        return 0
    fi
    msg "stripping binaries..."
    
    # Strip executables and shared objects
    find "$PKG" -type f -exec file {} + | grep -e "executable" -e "shared object" | grep "ELF" | cut -d: -f1 | \
    while read -r f; do
        strip --strip-unneeded "$f" 2>/dev/null || true
    done

    # Strip static archives
    find "$PKG" -name "*.a" -exec strip --strip-debug {} \; 2>/dev/null || true
}

# Detection & Orchestration
# =========================
_prepare_build_dir() {
    cd "$SRC" || die "Source directory missing"

    # For git sources, stay at root level
    if [ "${VERSION#git}" != "$VERSION" ]; then
        return 0
    fi

    _dirs=$(find . -maxdepth 1 -mindepth 1 -type d)
    # If the tarball contained a single top-level directory, enter it.
    if [ -n "$_dirs" ] && [ $(echo "$_dirs" | wc -l) -eq 1 ]; then cd "$_dirs"; fi
}

detect_and_build() {
    _prepare_build_dir

    # Manual override
    if [ -n "${BUILD_STYLE:-}" ]; then
        case "$BUILD_STYLE" in
            configure) _build_configure ;;
            meson)     _build_meson ;;
            cmake)     _build_cmake ;;
            gmake)     _build_gmake ;;
            make)      _build_make ;;
            hell)      _build_hell ;;
            python)    _build_python ;;
            perl)      _build_perl ;;
            *) die "Unknown BUILD_STYLE: $BUILD_STYLE" ;;
        esac
        return
    fi

    # Auto-detection priority
    if   [ -x ./configure ];                             then _build_configure
    elif [ -f meson.build ]    && has_cmd muon;          then _build_meson
    elif [ -f meson.build ]    && has_cmd meson;         then _build_meson
    elif [ -f CMakeLists.txt ] && has_cmd cmake;         then _build_cmake
    elif [ -f GNUmakefile ]    && has_cmd gmake;         then _build_gmake
    elif [ -f Makefile ] || [ -f makefile ];             then _build_make
    elif [ -f setup.py ]       && has_cmd python;        then _build_python
    elif [ -f Makefile.PL ]    && has_cmd perl;          then _build_perl
    elif has_cmd satan && ([ -f Hellfile ] || [ -f configure.ac ]); then _build_hell
    else die "No supported build system detected."
    fi
}

# Command Dispatch
# ================
cmd_fetch() { parse_source _do_fetch; }

cmd_checksum() {
    : > "$CHECKSUM_FILE"
    parse_source _do_gen_checksum
    _update_info_file
}

cmd_extract() {
    rm -rf "$SRC" && mkdir -p "$SRC"
    parse_source _do_extract

    if [ -z "${SKIP_PATCHES:-}" ]; then
        ( _prepare_build_dir; parse_source _do_apply_patch )
    fi
}

cmd_clean() {
    if [ -d "$SRC" ]; then
        msg "cleaning $SRC..."
        _prepare_build_dir || true
    else
        msg "nothing to clean"
    fi
    rm -rf "$SRC" "$PKG"
}

cmd_bclean() {
	if [ -d "$SRC" ]; then
		cd "$SRC"
    	if [ -f Makefile ] || [ -f makefile ]; then
    	    msg "running make clean..."
    	    make clean >/dev/null 2>&1 || true
    	elif [ -f build.ninja ]; then
    	    if has_cmd samu; then
    	        msg "running samu clean..."
    	        samu clean >/dev/null 2>&1 || true
    	    else
    	        msg "running ninja clean..."
    	        ninja clean >/dev/null 2>&1 || true
    	    fi
    	fi
    	cd ..
    else
        msg "nothing to clean"
    fi
}

cmd_build() {
    rm -rf "$PKG" && mkdir -p "$PKG"

    if command -v prebuild >/dev/null 2>&1; then
        _prepare_build_dir
        msg "entering prebuild()"
        prebuild
    fi

    if command -v build >/dev/null 2>&1; then
        _prepare_build_dir
        msg "entering build()"
        build
    else
        msg "no specific build() function"
        detect_and_build
    fi

    if command -v postbuild >/dev/null 2>&1; then
        _prepare_build_dir
        msg "entering postbuild()"
        postbuild
    fi

    _prune_shared_libs
    _prune_locales
    _strip_binaries
}

case "${1:-}" in
    fetch)        cmd_fetch ;;
    extract)      cmd_extract ;;
    build)        cmd_build ;;
    gen-checksum) cmd_checksum ;;
    clean)        cmd_clean ;;
    bclean)       cmd_bclean ;;
    make)         cmd_fetch && cmd_extract && cmd_build ;;
    *) die "Usage: $0 {build|make|clean|bclean} {fetch|extract|gen-checksum}" ;;
esac
