#!/usr/bin/env bash
#
# Install GIMPBot on macOS or Linux.
#
#   bash install.sh          install into every GIMP found
#   bash install.sh --list   show where it would go, change nothing
#
# The counterpart to install.ps1, and it exists for one reason beyond
# copying files: GIMP will not load a plug-in whose file is not
# executable. It skips it silently -- no error, no menu entry, nothing to
# search for. PowerShell's Compress-Archive cannot store a Unix
# permission bit, so the zip cannot carry it and the installer has to set
# it here.

set -euo pipefail

LIST_ONLY=0
[ "${1:-}" = "--list" ] && LIST_ONLY=1

# Where the payload is. Two ways in:
#
#   bash install.sh            from the unzipped folder, files alongside
#   curl .../install.sh | bash  piped, with nothing alongside at all
#
# The piped case is the whole point of the second mode. On Linux there is
# no dependable double-click: GNOME Files stopped launching executable
# scripts years ago and later dropped the setting, so "download, unzip,
# open a terminal, cd into the folder" was the only route -- and the cd
# is where people stop. One pasted line replaces all four steps.
#
# BASH_SOURCE is unset when the script is piped, which is exactly how
# the two cases are told apart.
DOWNLOAD_URL="https://gimpbot.com/get"
TMPDIR_SELF=""

# An if, not a && chain. As a chain the last command is the test
# itself, which is false on every local install -- and a trap's last
# command sets the script's exit status, so install.sh returned 1
# after printing that it had succeeded.
cleanup() {
    if [ -n "$TMPDIR_SELF" ]; then
        rm -rf "$TMPDIR_SELF"
    fi
}
trap cleanup EXIT

case "$(uname -s)" in
    Darwin) UA_OS="macos" ;;
    *)      UA_OS="linux" ;;
esac

fetch_payload() {
    TMPDIR_SELF="$(mktemp -d)"
    zip="$TMPDIR_SELF/gimpbot.zip"

    echo "Downloading GIMPBot..."
    # The user agent is not decoration: the download counter would
    # otherwise file every install from here under "looked automated"
    # and leave them out of the number entirely.
    if command -v curl >/dev/null 2>&1; then
        curl -fsSL -A "GIMPBot-Installer ($UA_OS)" -o "$zip" "$DOWNLOAD_URL"
    elif command -v wget >/dev/null 2>&1; then
        wget -q -U "GIMPBot-Installer ($UA_OS)" -O "$zip" "$DOWNLOAD_URL"
    else
        echo "Need curl or wget to download GIMPBot." >&2
        exit 1
    fi

    if command -v unzip >/dev/null 2>&1; then
        unzip -q "$zip" -d "$TMPDIR_SELF"
    elif command -v python3 >/dev/null 2>&1; then
        python3 -m zipfile -e "$zip" "$TMPDIR_SELF"
    else
        echo "Need unzip or python3 to unpack GIMPBot." >&2
        exit 1
    fi

    # The archive holds one gimpbot-<version>/ directory; find it by what
    # is inside rather than by guessing at the version in its name.
    for d in "$TMPDIR_SELF"/*/; do
        if [ -d "$d/ai-chat" ]; then
            HERE="${d%/}"
            return 0
        fi
    done
    echo "Downloaded archive did not contain what was expected." >&2
    exit 1
}

HERE="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || pwd)"
if [ ! -d "$HERE/ai-chat" ]; then
    fetch_payload
fi
SOURCE3="$HERE/ai-chat"
SOURCE210="$HERE/ai-chat-210"

if [ ! -d "$SOURCE3" ]; then
    echo "Cannot find $SOURCE3 -- run this from the unzipped folder." >&2
    exit 1
fi

# How this install finished. The gap between "downloaded" and "ever
# opened the chat window" was invisible: 576 downloads produced two
# people who typed a message, and nothing said whether they failed at
# the installer or sailed through it and never reopened GIMP. Those have
# opposite fixes.
#
# Never blocks and never fails the install: short timeout, output
# discarded, always true. Sends no path, no hostname, no address --
# the version, the platform, how it ended, which GIMP series it reached.
# Opt out with GIMPBOT_NO_TELEMETRY=1.
# Overridable so the reporting path can be exercised against a local or
# staging Worker without editing the installer people actually run.
PING_URL="${GIMPBOT_PING_URL:-https://gimpbot.com/installed}"

# Accumulated as each profile is written, so the "ok" ping can say which
# GIMP series people are really on. Declared here because set -u makes
# an unset variable fatal, and the success path reads it unconditionally.
INSTALLED_SERIES=""

send_outcome() {
    [ -n "${GIMPBOT_NO_TELEMETRY:-}" ] && return 0
    version="unknown"
    if [ -f "$HERE/VERSION" ]; then
        # The sed strips a UTF-8 BOM. 1.1.13 shipped with one, because
        # Set-Content -Encoding utf8 on Windows PowerShell 5.1 writes
        # one; tr keeps it, and the version would go out with it glued
        # on. Windows never saw this -- Get-Content strips BOMs -- so it
        # would have corrupted only the Linux and macOS rows.
        version="$(tr -d '\r\n' < "$HERE/VERSION" \
                   | sed '1s/^\xef\xbb\xbf//')"
    fi
    body="version=$version&platform=$UA_OS&outcome=$1&detail=${2:-}"
    if command -v curl >/dev/null 2>&1; then
        curl -fsS --max-time 5 -X POST -d "$body" "$PING_URL" \
            >/dev/null 2>&1 || true
    elif command -v wget >/dev/null 2>&1; then
        wget -q -T 5 -O /dev/null --post-data "$body" "$PING_URL" \
            >/dev/null 2>&1 || true
    fi
    return 0
}

# Every place a GIMP profile might live. On Linux, "installed from the
# repository" is only one of three common answers -- Flatpak and Snap
# each sandbox their config somewhere else entirely, and on Flathub in
# particular that is how most people now get GIMP. Installing only into
# ~/.config would silently miss them.
CONFIG_ROOTS=()
case "$(uname -s)" in
    Darwin)
        CONFIG_ROOTS+=("$HOME/Library/Application Support/GIMP")
        ;;
    *)
        CONFIG_ROOTS+=("${XDG_CONFIG_HOME:-$HOME/.config}/GIMP")
        CONFIG_ROOTS+=("$HOME/.var/app/org.gimp.GIMP/config/GIMP")
        CONFIG_ROOTS+=("$HOME/snap/gimp/current/.config/GIMP")
        ;;
esac

FOUND_ROOT=0
for root in "${CONFIG_ROOTS[@]}"; do
    [ -d "$root" ] && FOUND_ROOT=1
done

if [ "$FOUND_ROOT" = "0" ]; then
    # Two different situations, one message, because from here they are
    # indistinguishable: GIMP is not installed at all, or it is installed
    # and has never been opened. Naming only the second sends anyone in
    # the first case looking for a folder that will never appear.
    echo "No GIMP found on this computer."
    echo
    echo "GIMPBot is a plug-in -- it runs inside GIMP and needs GIMP"
    echo "installed first. It is free, from https://www.gimp.org/downloads/"
    echo
    echo "If GIMP IS installed, start it once and then close it. That"
    echo "first run creates the folder this installer copies into."
    echo
    echo "Looked in:"
    for root in "${CONFIG_ROOTS[@]}"; do
        echo "  $root"
    done
    echo
    echo "Then run this again."
    send_outcome no_gimp
    exit 1
fi

installed=0

# One directory per GIMP series -- 2.10, 3.0, 3.2 and whatever comes
# next -- in each root that exists.
for root in "${CONFIG_ROOTS[@]}"; do
  [ -d "$root" ] || continue
  for profile in "$root"/*; do
    [ -d "$profile" ] || continue
    series="$(basename "$profile")"
    case "$series" in
        2.10|3.*) ;;
        *) continue ;;
    esac

    plugins="$profile/plug-ins"

    if [ "$series" = "2.10" ]; then
        # GIMP 2.10 wants a single .py directly in plug-ins, with the
        # shared modules importable beside it.
        target="$plugins/ai-chat-210.py"
        lib="$profile/ai-chat-lib"
        if [ "$LIST_ONLY" = "1" ]; then
            echo "GIMP $series  ->  $target"
            echo "GIMP $series  ->  $lib (shared modules)"
            continue
        fi
        if [ ! -d "$SOURCE210" ]; then
            echo "Skipping GIMP $series: $SOURCE210 not found." >&2
            continue
        fi
        mkdir -p "$plugins" "$lib"
        cp "$SOURCE210/ai-chat-210.py" "$target"
        chmod +x "$target"
        # The same four files install.ps1 copies. gimp3_knowledge is
        # deliberately not among them: only backend_gimp3 imports it, so
        # on the 2.10 path it is dead weight.
        for name in agent.py agent_core.py backend_gimp210.py cacert.pem; do
            [ -f "$SOURCE3/$name" ] && cp "$SOURCE3/$name" "$lib/"
        done
        # Python 2 writes bytecode next to what it imports; a stale .pyc
        # from an older copy shadows the new module.
        rm -f "$lib"/*.pyc
        echo "Installed for GIMP $series: $target"
        installed=1
        INSTALLED_SERIES="$INSTALLED_SERIES $series"
        continue
    fi

    # GIMP 3 wants plug-ins/<name>/<name>.py, and the file must be
    # executable or it is ignored without comment.
    target="$plugins/ai-chat"
    if [ "$LIST_ONLY" = "1" ]; then
        echo "GIMP $series  ->  $target"
        continue
    fi
    mkdir -p "$target"
    for f in "$SOURCE3"/*.py "$SOURCE3"/*.pem; do
        [ -e "$f" ] || continue
        cp "$f" "$target/"
    done
    rm -rf "$target/__pycache__"
    rm -f "$target/agent_log.txt"
    chmod +x "$target/ai-chat.py"
    echo "Installed for GIMP $series: $target"
    installed=1
    INSTALLED_SERIES="$INSTALLED_SERIES $series"
  done
done

if [ "$LIST_ONLY" = "1" ]; then
    exit 0
fi

if [ "$installed" = "0" ]; then
    # Spell out the version requirement. A 2.8 profile is skipped by the
    # series filter above, so without this the message reads as "your
    # GIMP is missing" to someone who is looking straight at it.
    echo "Found a GIMP config directory, but no profile GIMPBot supports." >&2
    for root in "${CONFIG_ROOTS[@]}"; do
        [ -d "$root" ] && echo "  looked in $root" >&2
    done
    echo >&2
    echo "GIMPBot needs GIMP 2.10 or newer. An older GIMP has no Python-Fu" >&2
    echo "support it can use, so there is no version that runs there." >&2
    send_outcome none_written
    exit 1
fi

send_outcome ok "$(printf '%s' "${INSTALLED_SERIES# }" | tr ' ' ',')"

echo
echo "Restart GIMP, then open Filters > AI Chat..."
if [ ! -f "${XDG_CONFIG_HOME:-$HOME/.config}/ai-chat/credentials.json" ] \
   && [ -z "${AI_CHAT_TOKEN:-}" ]; then
    echo
    echo "Not signed in yet -- just send a message in the chat window"
    echo "and GIMPBot will open your browser to finish it."
fi
