#!/usr/bin/env bash

# Searching the web from the launcher, in both the forms Panama ships it.
#
# There are two, deliberately: a script command that needs nothing but bash, and
# an extension that adds live suggestions but has to be compiled. That is also
# the risk this pins -- the search engine is written down twice, and two copies
# of a URL drift.
#
# `vicinae script check` is the validator for the first, and it exits 0 even when
# it rejects a file. Checking its exit status would pass on a script Vicinae
# refuses to load, so its output is what counts.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
script="$repo_dir/config/local/share/vicinae/scripts/search-web"
extension="$repo_dir/config/local/share/vicinae/extensions/panama-search"
stage="$repo_dir/setup/scripts/link-vicinae-scripts"

findings=()
note() { findings+=("$1"); }

# ── The script command ───────────────────────────────────────────────────────

if [[ ! -x "$script" ]]; then
    note 'search-web is missing or not executable, so Vicinae will not load it'
else
    if command -v vicinae >/dev/null 2>&1; then
        output="$(vicinae script check "$script" 2>&1)"
        [[ "$output" == *Error* ]] && note "Vicinae rejects search-web: $output"
    fi

    # Exactly one argument. Vicinae only offers a no-view command as a fallback
    # -- type anything, get "Search" at the bottom -- when it takes a single
    # text argument, which is the whole point of shipping this one.
    arguments="$(grep -c '@vicinae.argument' "$script" || true)"
    (( arguments == 1 )) \
        || note "search-web declares $arguments arguments; a fallback command takes exactly 1"

    grep -q '"percentEncoded": true' "$script" \
        || note 'the argument is not percent-encoded, so a query containing & or # is truncated'

    grep -qE '@vicinae\.mode silent' "$script" \
        || note 'search-web is not a silent command, so it would render a view it has nothing to put in'

    # The default browser is a setting this desktop already owns. Naming a
    # browser here would quietly outrank the Applications page.
    grep -q 'xdg-open' "$script" \
        || note 'search-web does not open through xdg-open, so it ignores the default browser'
    grep -qE '\b(helium|firefox|chromium|google-chrome)\b' "$script" \
        && note 'search-web names a specific browser instead of following the default'
fi

# ── The extension ────────────────────────────────────────────────────────────

manifest="$extension/package.json"
if [[ ! -f "$manifest" ]]; then
    note 'the panama-search extension has no manifest'
else
    if command -v jq >/dev/null 2>&1; then
        jq -e . "$manifest" >/dev/null 2>&1 || note 'the extension manifest is not valid JSON'

        for field in name title description commands; do
            jq -e "has(\"$field\")" "$manifest" >/dev/null 2>&1 \
                || note "the extension manifest has no \"$field\", which Vicinae requires"
        done

        # A command's name is its source file. Getting this wrong builds an
        # extension with a command that cannot be opened.
        while read -r command_name; do
            [[ -n "$command_name" ]] || continue
            [[ -f "$extension/src/$command_name.tsx" || -f "$extension/src/$command_name.ts" ]] \
                || note "the manifest declares command \"$command_name\" with no matching file in src/"
        done < <(jq -r '.commands[]?.name // empty' "$manifest" 2>/dev/null)
    fi
fi

# ── One engine, written twice ────────────────────────────────────────────────
#
# The script command and the extension both have to know where a search goes.
# Nothing makes them agree, so this does: searching from the fallback and
# searching from the suggestions list must not reach different places.

engine_in_script="$(grep -oE 'https://[^"]+\?q=' "$script" 2>/dev/null | head -1)"
engine_in_extension="$(grep -oE 'https://[^"]+\?q=' "$extension/src/search.tsx" 2>/dev/null | head -1)"

if [[ -z "$engine_in_script" ]]; then
    note 'no search engine URL found in search-web'
elif [[ -z "$engine_in_extension" ]]; then
    note 'no search engine URL found in the extension'
elif [[ "$engine_in_script" != "$engine_in_extension" ]]; then
    note "the script searches $engine_in_script but the extension searches $engine_in_extension"
fi

# ── Provisioning ─────────────────────────────────────────────────────────────
#
# An extension is compiled, so unlike a script command it cannot simply be
# linked. If nothing builds it, it ships as source nobody can run.

grep -q 'npm run build' "$stage" \
    || note 'no stage builds the Vicinae extensions, so they never reach the launcher'
grep -q 'command -v npm' "$stage" \
    || note 'the extension build does not check for npm, so a machine without it fails the stage'

# Checking for npm is not enough. Node comes from nvm, whose script only an
# interactive shell sources -- and a stage is not one, so npm is absent unless
# the stage goes and gets it. This machine hid that: a system npm existed only
# because the Claude Desktop of the day pulled in nodejs. It no longer does, so
# nothing hides it any more. Without this line, a fresh install builds no
# extension and says so in one line nobody reads.
grep -q '/etc/profile.d/nvm.sh' "$stage" \
    || note 'the extension build never sources nvm, so npm is missing on any machine without a system node'

# node_modules is a dependency tree, not configuration.
git -C "$repo_dir" check-ignore -q "$extension/node_modules" 2>/dev/null \
    || note 'the extension node_modules is not gitignored'

# ── Report ───────────────────────────────────────────────────────────────────

if (( ${#findings[@]} > 0 )); then
    mapfile -t findings < <(printf '%s\n' "${findings[@]}" | sort -u)
    printf 'launcher search contract: %d finding(s)\n' "${#findings[@]}" >&2
    printf '  - %s\n' "${findings[@]}" >&2
    exit 1
fi

printf 'launcher search contract: PASS\n'
