#!/bin/bash
# Build the Altero macOS widget from this checkout, and put it where macOS can
# find it.
#
#   ./build-widget install     build it signed, install the host app into
#                              ~/Applications and register its extension, so
#                              the widget appears in the widget gallery
#   ./build-widget uninstall   deregister the extension and remove the app
#   ./build-widget release     same as packaging/build-app --release: the full
#                              Altero.app (engine included), notarized, as a .dmg
#
# The gallery only offers a widget whose extension pkd has registered, and pkd
# only looks inside apps LaunchServices knows about. So "install" is a copy
# plus an lsregister -- nothing is launched and no window is opened.
#
# Build products land in build/, which is gitignored.
set -euo pipefail

WIDGET="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$WIDGET"

APPEX_ID="com.fullcontextlabs.altero.widget"
APPEX_REL="Contents/PlugIns/AlteroWidgetExtension.appex"
DEST="$HOME/Applications/Altero.app"
BUILD="$WIDGET/build"
LSREGISTER="/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister"

die() { echo "$*" >&2; exit 1; }

registered() { pluginkit -m -i "$APPEX_ID" 2>/dev/null | grep -q .; }

# $1 is the certificate kind the chosen command needs.
preflight() {
    xcodebuild -version >/dev/null 2>&1 || die \
"xcodebuild is unusable. Install Xcode, then point at it:
    sudo xcode-select -s /Applications/Xcode.app"
    command -v xcodegen >/dev/null || die \
"xcodegen not found. Install it:
    brew install xcodegen"
    [[ -f Signing.xcconfig ]] || die \
"widget/Signing.xcconfig is missing. Create it once:
    cp Signing.xcconfig.example Signing.xcconfig
then set DEVELOPMENT_TEAM to your 10-character Team ID, from
https://developer.apple.com/account -> Membership details."
    grep -q '^DEVELOPMENT_TEAM *= *[A-Z0-9]' Signing.xcconfig || die \
"DEVELOPMENT_TEAM is empty in widget/Signing.xcconfig. Set it to your
10-character Team ID, from https://developer.apple.com/account ->
Membership details."
    security find-identity -v -p codesigning | grep -q "$1" || die \
"No \"$1\" certificate in the keychain.
Xcode > Settings > Accounts > Manage Certificates creates a development one;
a Developer ID Application certificate comes from
https://developer.apple.com/account/resources/certificates and is what
notarization requires."
    xcodegen generate
}

install_widget() {
    preflight "Apple Development"
    # A build number that changes on every install. chronod (the widget
    # timeline daemon) caches a widget's descriptor keyed on its unchanged
    # bundle version, so rebuilding the widget's own definition (e.g. from
    # StaticConfiguration to AppIntentConfiguration) without bumping this
    # left chronod requesting timelines against the stale, cached shape.
    BUILD_NUMBER="$(date +%Y%m%d%H%M%S)"
    xcodebuild -project AlteroWidget.xcodeproj -scheme AlteroWidgetHost \
        -configuration Debug -derivedDataPath "$BUILD/dd" \
        CURRENT_PROJECT_VERSION="$BUILD_NUMBER" build
    BUILT="$BUILD/dd/Build/Products/Debug/Altero.app"

    # xcodebuild registers whatever it just built. Two registered copies of one
    # extension bundle id and pkd keeps only the last it saw, so drop the build
    # directory's copy before registering the installed one. Also drop any
    # stale copy left behind under a *different* DerivedData root (e.g. from
    # Xcode.app, or an old AlteroWidget-* hash) -- same failure mode, just not
    # this run's own build directory.
    "$LSREGISTER" -u "$BUILT"
    for stale in "$HOME"/Library/Developer/Xcode/DerivedData/AlteroWidget-*/Build/Products/*/Altero.app; do
        [[ -d "$stale" ]] || continue
        # Best-effort: -u fails if it was never registered (or already was
        # unregistered), which isn't a problem worth stopping the install for.
        "$LSREGISTER" -u "$stale" || true
    done
    rm -rf "$DEST"
    mkdir -p "$(dirname "$DEST")"
    ditto "$BUILT" "$DEST"
    "$LSREGISTER" -f -R -trusted "$DEST"

    # pkd picks the change up asynchronously.
    for _ in 1 2 3 4 5; do
        registered && break
        sleep 1
    done
    registered || die \
"Installed $DEST but pkd did not register the extension.
Check it with: pluginkit -m -v -i $APPEX_ID
A copy built by Xcode.app can hold the registration instead; unregister it with
    $LSREGISTER -u <that .app>
and run this again."
    pluginkit -m -v -i "$APPEX_ID"

    # chronod caches widget descriptors in memory, keyed on bundle version --
    # restart it so it re-reads the one just installed instead of serving a
    # cached shape from before. It relaunches on its own.
    killall chronod 2>/dev/null || true

    echo "Installed $DEST"
    echo "Add it from the widget gallery: right-click the desktop > Edit Widgets."
}

uninstall_widget() {
    if [[ -d "$DEST" ]]; then
        pluginkit -r "$DEST/$APPEX_REL" || true
        "$LSREGISTER" -u "$DEST"
        rm -rf "$DEST"
        sleep 2
    fi
    ! registered || die \
"Removed $DEST but the extension is still registered. Something else is
holding it -- find it with: pluginkit -m -v -i $APPEX_ID"
    echo "Removed $DEST"
}

release() {
    # The distributable app also carries the Python engine, which this script
    # does not build. One release path: packaging/build-app.
    exec "$WIDGET/../packaging/build-app" --release
}

case "${1:-}" in
    install)   install_widget ;;
    uninstall) uninstall_widget ;;
    release)   release ;;
    *) echo "usage: $(basename "$0") [install|uninstall|release]" >&2; exit 2 ;;
esac
