#!/usr/bin/env bash
#
# Build a Debian package for QuickSearch.
#
#   ./packaging/build-deb.sh                 build and package
#   ./packaging/build-deb.sh --no-build      package an existing release binary
#   ./packaging/build-deb.sh --no-strip      keep debug symbols (25 MB vs 20 MB)
#   ./packaging/build-deb.sh -o /tmp/out     write the .deb somewhere else
#
# Environment: DEB_MAINTAINER, SOURCE_DATE_EPOCH.
#
# Deliberately does not use cargo-deb, debhelper, fakeroot or an SVG rasteriser:
# dpkg-deb and desktop-file-utils are the only tools required, and both are part
# of a standard Debian or Ubuntu install.

set -euo pipefail
# Directories created along the way must be 0755, not whatever the caller's
# umask happens to be, or the package ships group-writable directories.
umask 022

readonly PKG=quicksearch
# The GUI binary and the console-subsystem terminal binary. Both ship: on Unix
# `quicksearch` does both jobs, but the README and the shared man page name
# `quicksearch-cli` too, so it has to exist wherever the docs are installed.
readonly BINARIES=(quicksearch quicksearch-cli)
readonly REPO_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
readonly ICON_SRC="$REPO_ROOT/crates/quicksearch-gui/assets/icons"
readonly ICON_SVG="$ICON_SRC/quicksearch_icon.svg"
readonly METAINFO=com.karsttech.quicksearch.metainfo.xml

do_build=1
do_strip=1
out_dir="$REPO_ROOT/dist"

die() { printf 'build-deb: %s\n' "$*" >&2; exit 1; }
say() { printf '\033[1m==>\033[0m %s\n' "$*"; }

while [ $# -gt 0 ]; do
    case "$1" in
        --no-build) do_build=0 ;;
        --no-strip) do_strip=0 ;;
        -o|--output-dir) shift; [ $# -gt 0 ] || die "--output-dir needs a path"; out_dir="$1" ;;
        # Print the header comment block, however long it grows.
        -h|--help) awk 'NR > 1 { if ($0 !~ /^#/) exit; sub(/^# ?/, ""); print }' "${BASH_SOURCE[0]}"; exit 0 ;;
        *) die "unknown option: $1 (try --help)" ;;
    esac
    shift
done

for tool in dpkg-deb dpkg desktop-file-validate objdump gzip; do
    command -v "$tool" >/dev/null 2>&1 || die "missing required tool: $tool"
done
[ "$do_strip" -eq 0 ] || command -v strip >/dev/null 2>&1 || die "missing strip (install binutils, or pass --no-strip)"

# Version comes from [workspace.package] so the package can never drift from the
# crate version.
version="$(sed -n '/^\[workspace\.package\]/,/^\[/{ s/^version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p }' "$REPO_ROOT/Cargo.toml")"
[ -n "$version" ] || die "could not read version from Cargo.toml"

# No Debian revision: QuickSearch is only ever packaged from its own source, so
# the version is native (no hyphen) and the .deb is named for the crate version
# alone, matching the Windows installer.
maintainer="${DEB_MAINTAINER:-Jeremy <jeremy@karsttech.com>}"
arch="$(dpkg --print-architecture)"
stage="$out_dir/${PKG}_${version}_${arch}"
deb="$out_dir/${PKG}_${version}_${arch}.deb"

# ---------------------------------------------------------------- build ----

if [ "$do_build" -eq 1 ]; then
    say "Building quicksearch $version (release)"
    ( cd "$REPO_ROOT" && cargo build --release -p quicksearch-gui )
fi

for bin in "${BINARIES[@]}"; do
    [ -x "$REPO_ROOT/target/release/$bin" ] \
        || die "no release binary at target/release/$bin (drop --no-build?)"
done
# Both binaries link the same crates, so either gives the same glibc floor.
readonly primary_binary="$REPO_ROOT/target/release/$PKG"
[ -f "$ICON_SVG" ] || die "no icon at $ICON_SVG"
[ -f "$REPO_ROOT/packaging/$METAINFO" ] || die "no metainfo at packaging/$METAINFO"

say "Validating desktop entry"
desktop-file-validate "$REPO_ROOT/packaging/$PKG.desktop"

# --------------------------------------------------------------- stage -----

say "Staging $stage"
rm -rf "$stage"
mkdir -p "$stage"

for bin in "${BINARIES[@]}"; do
    install -Dm755 "$REPO_ROOT/target/release/$bin"     "$stage/usr/bin/$bin"
done
install -Dm644 "$REPO_ROOT/packaging/$PKG.desktop"      "$stage/usr/share/applications/$PKG.desktop"
install -Dm644 "$ICON_SVG"                              "$stage/usr/share/icons/hicolor/scalable/apps/$PKG.svg"
install -Dm644 "$REPO_ROOT/packaging/copyright"         "$stage/usr/share/doc/$PKG/copyright"
install -Dm644 "$REPO_ROOT/config_example.toml"         "$stage/usr/share/doc/$PKG/config_example.toml"
install -Dm644 "$REPO_ROOT/README.md"                   "$stage/usr/share/doc/$PKG/README.md"

# The PNGs are committed next to the quicksearch_icon.svg they came from, so
# this script only copies them and an ordinary `cargo build` needs no image
# tooling. To re-render them after editing the SVG, build a throwaway crate
# (outside this workspace, to keep it out of Cargo.lock) depending on
# `resvg = { version = "0.45", default-features = false }` and, for each size N,
# parse with usvg::Tree::from_data, make a tiny_skia::Pixmap::new(N, N), call
# resvg::render with Transform::from_scale(N/240.0, N/240.0) and save_png. The
# SVG contains no <text>, so no font support is needed.
#
# Sizes follow whatever is committed, so adding one needs no script change.
shopt -s nullglob
icons=("$ICON_SRC"/$PKG-*.png)
shopt -u nullglob
[ "${#icons[@]}" -gt 0 ] || die "no icons in $ICON_SRC"
for png in "${icons[@]}"; do
    n="$(basename "$png" .png)"; n="${n#$PKG-}"
    # The glob would also catch a non-size name like quicksearch-cli.png.
    case "$n" in ''|*[!0-9]*) die "unexpected icon name: $(basename "$png")" ;; esac
    install -Dm644 "$png" "$stage/usr/share/icons/hicolor/${n}x${n}/apps/$PKG.png"
done
say "Installed ${#icons[@]} icon sizes plus the scalable SVG"

# AppStream metadata, so software centres show a real listing rather than a bare
# package name. @VERSION@/@DATE@ are substituted the same way the man page .TH
# line is below, keeping [workspace.package] version the only thing a release
# bumps. build-appimage.sh ships the identical file and is where it gets
# validated - appstreamcli is not something a stock Debian install has, and this
# script deliberately needs nothing beyond dpkg-deb and desktop-file-utils.
if [ -n "${SOURCE_DATE_EPOCH:-}" ]; then
    metainfo_date="$(date -u -d "@$SOURCE_DATE_EPOCH" +%Y-%m-%d)"
else
    metainfo_date="$(date -u +%Y-%m-%d)"
fi
install -dm755 "$stage/usr/share/metainfo"
sed -e "s/@VERSION@/$version/" -e "s/@DATE@/$metainfo_date/" \
    "$REPO_ROOT/packaging/$METAINFO" > "$stage/usr/share/metainfo/$METAINFO"
chmod 644 "$stage/usr/share/metainfo/$METAINFO"

# Debian wants man pages and the changelog compressed, with no gzip timestamp so
# repeat builds are byte-identical. quicksearch-cli.1 is a one-line .so stub
# pointing at quicksearch.1, which documents both binaries.
#
# The .TH version field is rewritten from $version on the way through, so the
# footer of an installed page tracks [workspace.package] version instead of
# needing a hand-edit every release. The stub has no .TH, so the sed is a
# no-op there.
install -dm755 "$stage/usr/share/man/man1"
for page in "${BINARIES[@]}"; do
    sed "/^\.TH /s/\"quicksearch [^\"]*\"/\"quicksearch $version\"/" \
        "$REPO_ROOT/packaging/$page.1" \
        | gzip -9nc > "$stage/usr/share/man/man1/$page.1.gz"
    chmod 644 "$stage/usr/share/man/man1/$page.1.gz"
done

if [ -n "${SOURCE_DATE_EPOCH:-}" ]; then
    changelog_date="$(date -R -u -d "@$SOURCE_DATE_EPOCH")"
else
    changelog_date="$(date -R)"
fi
# changelog.gz, not changelog.Debian.gz: with a native version there is no
# separate Debian revision to log, and policy puts the one changelog here.
gzip -9nc <<EOF > "$stage/usr/share/doc/$PKG/changelog.gz"
$PKG ($version) unstable; urgency=medium

  * Package build of $PKG $version.

 -- $maintainer  $changelog_date
EOF
chmod 644 "$stage/usr/share/doc/$PKG/changelog.gz"

# No config.toml is installed anywhere. Config::config_path() treats a
# config.toml sitting next to the executable as portable mode and lets it
# override the per-user config outright, so one in /usr/bin would hijack every
# account on the machine. Config::load_from creates ~/.config/quicksearch/
# config.toml on first run instead.

if [ "$do_strip" -eq 1 ]; then
    for bin in "${BINARIES[@]}"; do
        before="$(du -h "$stage/usr/bin/$bin" | cut -f1)"
        strip --strip-unneeded "$stage/usr/bin/$bin"
        say "Stripped $bin: $before -> $(du -h "$stage/usr/bin/$bin" | cut -f1)"
    done
fi

installed_size="$(du -ks "$stage" | cut -f1)"

# ------------------------------------------------------------- control -----

# The dynamic section only names libc, libgcc, libm and libbz2: winit and glutin
# dlopen the entire display stack, so dpkg-shlibdeps cannot see it and the list
# below is maintained by hand. Re-derive it with
#   objdump -p target/release/quicksearch | grep NEEDED
#   strings -a target/release/quicksearch | grep -oE 'lib[A-Za-z0-9_+-]+\.so(\.[0-9]+)*' | sort -u
# and map each soname to a package with `dpkg -S`.
glibc_min="$(objdump -T "$primary_binary" | sed -n 's/.*GLIBC_\([0-9][0-9.]*\).*/\1/p' | sort -V | tail -1)"
[ -n "$glibc_min" ] || die "could not determine the glibc version requirement"

depends="libc6 (>= ${glibc_min}), libgcc-s1 (>= 3.0), libbz2-1.0"
depends="$depends, libx11-6, libxcb1, libxcursor1, libxi6, libxrender1"
depends="$depends, libxkbcommon0, libxkbcommon-x11-0"
depends="$depends, libwayland-client0, libwayland-egl1"
depends="$depends, libegl1, libgl1"
# Required by policy for anything installing into the hicolor theme; it also
# provides the dpkg trigger that refreshes the icon cache on install.
depends="$depends, hicolor-icon-theme"

# desktop-file-utils owns the /usr/share/applications trigger; dbus-bin provides
# the dbus-send used by "reveal in folder"; xdg-utils provides the xdg-open
# fallback; xdg-desktop-portal backs the native folder picker. None are needed
# to search, so none are hard dependencies.
#
# The index-password "remember on this device" feature talks Secret Service
# over the session bus with a statically linked libdbus (keyring's `vendored`
# feature), so it adds no library dependency; without a Secret Service
# provider (gnome-keyring, kwalletd) it simply falls back to prompting.
recommends="desktop-file-utils, xdg-utils, dbus-bin, xdg-desktop-portal"

install -dm755 "$stage/DEBIAN"
cat > "$stage/DEBIAN/control" <<EOF
Package: $PKG
Version: $version
Section: utils
Priority: optional
Architecture: $arch
Maintainer: $maintainer
Installed-Size: $installed_size
Depends: $depends
Recommends: $recommends
Homepage: https://code.karsttech.com/jeremy/quick_search
Description: fast full-text search across your files
 QuickSearch keeps a SQLite/FTS5 index of the directories you choose and
 searches them by both filename and file content. It extracts text from
 documents, PDFs, archives and office files, watches the indexed paths for
 changes and reindexes in the background while the application is open.
 .
 The same binary doubles as a terminal search tool: "quicksearch <terms>"
 prints ranked results and exits without starting the indexer, the file
 watcher or any background thread.
EOF

# No postinst/postrm: hicolor-icon-theme and desktop-file-utils declare
# interest-noawait on /usr/share/icons/hicolor and /usr/share/applications, so
# dpkg refreshes both caches on install and removal by itself.

# --------------------------------------------------------------- build -----

say "Building $deb"
dpkg-deb --root-owner-group --build "$stage" "$deb" >/dev/null
rm -rf "$stage"

echo
dpkg-deb --info "$deb"
echo
say "Done: $deb"
echo "    install with:  sudo apt install $deb"
echo "    inspect with:  dpkg-deb --contents $deb"
