#!/bin/sh
# apt - native Terminal package front-end (Terminal-compatible .deb packages)
# This is not Debian apt: it manages packages built for iOS/Terminal.

ROOT="${TERM_ROOT:-$HOME/Documents/.terminal}"
LISTS="$ROOT/var/lib/apt/lists"
INDEX="$LISTS/Packages"
CACHE="$ROOT/var/cache/apt/archives"
SOURCES="$ROOT/etc/apt/sources.list"
VISITED=""
DPKG="${APPDIR:-}/bin/dpkg"
[ -f "$DPKG" ] || DPKG="dpkg"

usage() {
    printf '%s\n' "Usage: apt <command>"
    printf '%s\n' ""
    printf '%s\n' "  update                  Refresh package indexes"
    printf '%s\n' "  search <text>           Search package names/descriptions"
    printf '%s\n' "  show <package>          Show package details"
    printf '%s\n' "  install <package|deb>   Install a package (dependencies are resolved locally)"
    printf '%s\n' "  remove|purge <package>  Remove a package"
    printf '%s\n' "  list --installed        List installed packages"
    printf '%s\n' "  list                    List packages in the current index"
    printf '%s\n' "  clean                   Clear downloaded archives"
    printf '%s\n' "  --version               Show version"
    printf '%s\n' ""
    printf '%s\n' "Default repository: file://$ROOT/repo"
    printf '%s\n' "Create packages with: mkdeb <name> <version> <payload-dir> [arch]"
}
ensure() {
    mkdir -p "$ROOT" "$LISTS" "$CACHE" "$ROOT/var/lib/dpkg" "$ROOT/tmp"
    [ -f "$ROOT/var/lib/dpkg/status" ] || : > "$ROOT/var/lib/dpkg/status"
}

field() {
    key="$1"; stanza="$2"
    tmp="$ROOT/tmp/field.$$"
    printf '%s\n' "$stanza" > "$tmp"
    awk -v key="$key" 'index($0,key": ")==1 { sub("^" key ": ",""); print; exit }' "$tmp"
    rm -f "$tmp"
}

find_stanza() {
    local pkg="$1"
    [ -f "$INDEX" ] || return 0
    awk -v pkg="$pkg" 'BEGIN{RS=""; ORS="\n\n"} $0 ~ "(^|\\n)Package: " pkg "(\\n|$)" { print; exit }' "$INDEX"
}

is_installed() {
    out="$ROOT/tmp/dpkg-status.$$"
    sh "$DPKG" -s "$1" > "$out" 2>/dev/null
    grep -q '^Status: install ok installed' "$out"
    result=$?
    rm -f "$out"
    return $result
}

decompress_index() {
    file="$1"
    out="$2"
    case "$file" in
        *.xz)
            xz -d -c "$file" >> "$out" 2>/dev/null || python3 -c 'import lzma,sys; sys.stdout.buffer.write(lzma.open(sys.argv[1],"rb").read())' "$file" >> "$out" 2>/dev/null
            ;;
        *.gz)
            gzip -dc "$file" >> "$out" 2>/dev/null || python3 -c 'import gzip,sys; sys.stdout.buffer.write(gzip.open(sys.argv[1],"rb").read())' "$file" >> "$out" 2>/dev/null
            ;;
        *)
            cat "$file" >> "$out"
            ;;
    esac
}

fetch_index() {
    URI="$1"
    TMPIDX="$ROOT/tmp/index.$$"
    rm -f "$TMPIDX" "$TMPIDX.xz" "$TMPIDX.gz"
    case "$URI" in
        file://*)
            DIR="${URI#file://}"
            for name in Packages Packages.xz Packages.gz; do
                if [ -f "$DIR/$name" ]; then
                    decompress_index "$DIR/$name" "$INDEX"
                    return 0
                fi
            done
            ;;
        http://*|https://*)
            if curl -L --fail --silent --connect-timeout 10 --max-time 120 "$URI/Packages.xz" -o "$TMPIDX.xz" 2>/dev/null; then
                decompress_index "$TMPIDX.xz" "$INDEX"
                rm -f "$TMPIDX.xz"
                return 0
            fi
            if curl -L --fail --silent --connect-timeout 10 --max-time 120 "$URI/Packages.gz" -o "$TMPIDX.gz" 2>/dev/null; then
                decompress_index "$TMPIDX.gz" "$INDEX"
                rm -f "$TMPIDX.gz"
                return 0
            fi
            if curl -L --fail --silent --connect-timeout 10 --max-time 120 "$URI/Packages" -o "$TMPIDX" 2>/dev/null; then
                cat "$TMPIDX" >> "$INDEX"
                rm -f "$TMPIDX"
                return 0
            fi
            ;;
    esac
    return 1
}

update_lists() {
    ensure
    echo "apt: updating package lists..."
    : > "$INDEX"
    if [ ! -f "$SOURCES" ]; then
        mkdir -p "$(dirname "$SOURCES")"
        printf 'deb [trusted=yes] file://%s/repo ./\n' "$ROOT" > "$SOURCES"
    fi
    n=1
    while [ $n -le 8 ]; do
        LINE=$(awk -v want="$n" '/^deb /{ count++; if (count == want) { print; exit } }' "$SOURCES")
        [ -n "$LINE" ] || break
        set -- $LINE
        if [ "${2:-}" = "[trusted=yes]" ]; then
            URI="${3:-}"; SUITE="${4:-./}"
        else
            URI="${2:-}"; SUITE="${3:-./}"
        fi
        if [ -n "$URI" ]; then
            case "$URI" in
                http://*|https://*)
                    if [ "${APT_SKIP_REMOTE:-0}" = "1" ]; then
                        echo "Ign:$n $URI (remote skipped)"
                        n=$((n + 1))
                        continue
                    fi
                    ;;
            esac
            if fetch_index "$URI"; then
                echo "Get:$n $URI $SUITE"
            else
                echo "Err:$n $URI (no index / unreachable)" >&2
            fi
        fi
        n=$((n + 1))
    done
    echo "Reading package lists... Done"
}

search_packages() {
    text="${1:-}"
    ensure
    [ -f "$INDEX" ] || { echo "E: Run 'apt update' first" >&2; return 1; }
    awk -v pat="$text" 'BEGIN{RS="";FS="\n"} {
        if (pat == "" || tolower($0) ~ tolower(pat)) {
            p=""; v=""; d="";
            for (i=1;i<=NF;i++) {
                if ($i ~ /^Package: /) p=substr($i,10);
                if ($i ~ /^Version: /) v=substr($i,10);
                if ($i ~ /^Description: /) d=substr($i,14);
            }
            if (p != "") printf "%-28s %-16s %s\n", p, v, d;
        }
    }' "$INDEX"
}

show_package() {
    pkg="${1:-}"
    ensure
    stanza=$(find_stanza "$pkg")
    if [ -z "$stanza" ]; then
        echo "E: Unable to locate package $pkg" >&2
        return 1
    fi
    printf '%s\n' "$stanza"
}

install_one() {
    local pkg="$1"
    local stanza version depends dep filename local_archive base oldifs
    [ -n "$pkg" ] || return 0
    case " $VISITED " in *" $pkg "*) return 0 ;; esac
    VISITED="$VISITED $pkg"
    if is_installed "$pkg"; then
        echo "$pkg is already the newest version."
        return 0
    fi
    stanza=$(find_stanza "$pkg")
    if [ -z "$stanza" ]; then
        for localdeb in "$ROOT/repo/pool/${pkg}"_*.deb; do
            if [ -f "$localdeb" ]; then
                echo "Installing $pkg from local pool..."
                sh "$DPKG" -i "$localdeb" || return 1
                return 0
            fi
        done
        echo "E: Unable to locate package $pkg" >&2
        return 1
    fi
    version=$(field Version "$stanza")
    depends=$(field Depends "$stanza")
    if [ -n "$depends" ]; then
        oldifs="$IFS"
        IFS=','
        for dep in $depends; do
            IFS="$oldifs"
            dep=${dep%%|*}
            dep=${dep%%(*}
            dep=$(echo "$dep" | tr -d ' ')
            dep=${dep%:any}
            [ -n "$dep" ] || continue
            if ! is_installed "$dep"; then
                if [ -n "$(find_stanza "$dep")" ]; then
                    echo "Installing dependency: $dep"
                    install_one "$dep" || return 1
                else
                    echo "W: dependency '$dep' is not in the index" >&2
                fi
            fi
            IFS=','
        done
        IFS="$oldifs"
    fi
    filename=$(field Filename "$stanza")
    if [ -z "$filename" ]; then
        echo "E: package $pkg has no Filename field" >&2
        return 1
    fi
    filename="${filename#./}"
    local_archive="$CACHE/${pkg}_${version}.deb"
    if [ -f "$ROOT/repo/$filename" ]; then
        cp "$ROOT/repo/$filename" "$local_archive" || return 1
    else
        fetched=0
        BASES=$(awk '/^deb /{for(i=1;i<=NF;i++) if($i ~ /^https?:/) print $i}' "$SOURCES")
        for base in $BASES; do
            if curl -L --fail --silent --connect-timeout 10 --max-time 180 "$base/$filename" -o "$local_archive" 2>/dev/null; then
                fetched=1
                break
            fi
        done
        if [ $fetched -ne 1 ]; then
            echo "E: cannot fetch $filename" >&2
            return 1
        fi
    fi
    echo "Installing $pkg ($version)..."
    echo "apt: invoking dpkg..."
    sh "$DPKG" -i "$local_archive" || return 1
    return 0
}

install_packages() {
    ensure
    echo "apt: resolving packages..."
    [ -f "$INDEX" ] || { echo "E: Run 'apt update' first" >&2; return 1; }
    status=0
    for arg in "$@"; do
        case "$arg" in
            *.deb)
                sh "$DPKG" -i "$arg" || status=1
                ;;
            *)
                install_one "$arg" || status=1
                ;;
        esac
    done
    echo "Done."
    return $status
}

remove_packages() {
    status=0
    for pkg in "$@"; do
        sh "$DPKG" -r "$pkg" || status=1
    done
    return $status
}

list_installed() {
    sh "$DPKG" -l
}

list_all() {
    ensure
    [ -f "$INDEX" ] || { echo "E: Run 'apt update' first" >&2; return 1; }
    awk 'BEGIN{RS="";FS="\n"} {
        p="";v="";a="";
        for(i=1;i<=NF;i++){
            if($i~/^Package: /)p=substr($i,10);
            if($i~/^Version: /)v=substr($i,10);
            if($i~/^Architecture: /)a=substr($i,14);
        }
        if(p!="") printf "%-28s %-16s %s\n", p, v, a;
    }' "$INDEX"
}

case "${1:-}" in
    update) update_lists ;;
    search) shift; search_packages "${1:-}" ;;
    show) shift; show_package "${1:-}" ;;
    install) shift; install_packages "$@" ;;
    remove|uninstall) shift; remove_packages "$@" ;;
    purge) shift; remove_packages "$@" ;;
    list)
        shift
        if [ "${1:-}" = "--installed" ]; then list_installed; else list_all; fi
        ;;
    clean)
        rm -f "$CACHE"/*.deb
        echo "Cleaned package archives"
        ;;
    --version|-v)
        echo "apt 1.0 (Terminal native package front-end)"
        ;;
    ""|-h|--help|help) usage ;;
    *) usage; exit 2 ;;
esac
