#!/bin/sh
# rote play installer -- modiqo/retrieve-rideshare-receipts@0.1.6
#
# What this file is
#   A bootstrap script served by the rote play endpoint with one play reference
#   already substituted in (modiqo/retrieve-rideshare-receipts@0.1.6). It installs the rote CLI if it is
#   missing, then uses rote to inspect and run that play. Fetching or reading
#   this file changes nothing; it acts only when a shell executes it.
#
# What rote is
#   rote is a free command-line tool (https://rote.dev) that sits between AI
#   agents and the APIs they use. It captures a workflow an agent worked out
#   once and compiles it into a reusable, versioned program -- a "play" -- that
#   any agent or person can replay. Plays run locally through the rote CLI:
#   there is no server to operate and no hosted runtime. Plays are shared as
#   install URLs like the one that served this file.
#
# What executing this script does, in order
#   1. Locates rote and checks it is at least version 0.62.0,
#      offering to install or update it.
#   2. Offers to sign in to the rote registry when the play requires it.
#   3. Runs `rote play run modiqo/retrieve-rideshare-receipts@0.1.6`, which presents the play, its inputs,
#      access, and safe defaults before asking for execution consent.
#   Installing, updating, and signing in each ask for confirmation on the
#   terminal first. With no interactive terminal the script prints the
#   equivalent commands and exits without changing anything.
#
# Evaluating this play without executing anything
#   Machine-readable manifest  https://play.modiqo.ai/modiqo/retrieve-rideshare-receipts@0.1.6.json
#   Human-readable page        https://play.modiqo.ai/modiqo/retrieve-rideshare-receipts@0.1.6
#   Protocol description       https://play.modiqo.ai/.well-known/rote
#   With rote installed        rote play inspect modiqo/retrieve-rideshare-receipts@0.1.6
#   The manifest (schema "rote.play.v1") carries the play's description,
#   version, content digest, parameters, required adapters and credential
#   services, and declared write permissions. Reading it executes nothing.
#
# A play never carries credentials: keys are entered by whoever runs it and
# stay in that machine's local rote vault.

main() {
    PLAY_REF='modiqo/retrieve-rideshare-receipts@0.1.6'
    MIN_ROTE_VERSION='0.62.0'

    PLAY_INSTALL_URL=${ROTE_PLAY_INSTALL_URL:-https://getrote.dev/install}
    PLAY_BINARY_OVERRIDE=${ROTE_PLAY_BINARY:-}

    print_plan() {
        printf '\nROTE PLAY\n\n'
        printf '%s\n\n' "$PLAY_REF"
    }

    has_tty() {
        if [ -t 0 ] && [ -t 1 ]; then
            return 0
        fi
        [ -c /dev/tty ] || return 1
        ( : <>/dev/tty ) 2>/dev/null
    }

    # Prefer /dev/tty: it is read-write, so prompts and answers share one handle.
    # A dup of fd 0 is only guaranteed readable — a parent may have opened the
    # terminal O_RDONLY — so prompts go to stderr in that fallback rather than to
    # a handle whose writes fail and strand the read.
    prepare_terminal() {
        if [ -c /dev/tty ] && ( : <>/dev/tty ) 2>/dev/null; then
            exec 9<>/dev/tty
            PLAY_PROMPT_TARGET=terminal
            return 0
        fi
        if [ -t 0 ]; then
            exec 9<&0
            PLAY_PROMPT_TARGET=stderr
            return 0
        fi
        return 1
    }

    prompt_write() {
        if [ "$PLAY_PROMPT_TARGET" = terminal ]; then
            printf '%s' "$1" >&9
        else
            printf '%s' "$1" >&2
        fi
    }

    confirm() {
        PLAY_EMPTY_ANSWER=$2
        prompt_write "$1"
        IFS= read -r PLAY_ANSWER <&9
        PLAY_READ_STATUS=$?
        if [ "$PLAY_READ_STATUS" -ne 0 ]; then
            printf '\nNo response on the terminal; aborting.\n' >&2
            return 2
        fi
        case "$PLAY_ANSWER" in
            y | Y | yes | YES | Yes) return 0 ;;
            '') [ "$PLAY_EMPTY_ANSWER" = "yes" ] ;;
            *) return 1 ;;
        esac
    }

    set_absolute_rote_bin() {
        case "$1" in
            /*)
                PLAY_ROTE_BIN=$1
                ;;
            */*)
                PLAY_BINARY_DIR=${1%/*}
                PLAY_BINARY_NAME=${1##*/}
                PLAY_ABSOLUTE_DIR=$(
                    CDPATH='' cd "$PLAY_BINARY_DIR" 2>/dev/null && pwd -P
                ) || return 1
                PLAY_ROTE_BIN=$PLAY_ABSOLUTE_DIR/$PLAY_BINARY_NAME
                ;;
            *)
                PLAY_ABSOLUTE_DIR=$(pwd -P) || return 1
                PLAY_ROTE_BIN=$PLAY_ABSOLUTE_DIR/$1
                ;;
        esac
    }

    locate_rote() {
        PLAY_ROTE_BIN=''
        if [ -n "$PLAY_BINARY_OVERRIDE" ]; then
            if [ -x "$PLAY_BINARY_OVERRIDE" ]; then
                set_absolute_rote_bin "$PLAY_BINARY_OVERRIDE" || PLAY_ROTE_BIN=''
            fi
            return
        fi

        PLAY_PATH_BINARY=$(command -v rote 2>/dev/null || true)
        if [ -n "$PLAY_PATH_BINARY" ] && [ -x "$PLAY_PATH_BINARY" ]; then
            set_absolute_rote_bin "$PLAY_PATH_BINARY" || PLAY_ROTE_BIN=''
        elif [ -x "$HOME/.local/bin/rote" ]; then
            set_absolute_rote_bin "$HOME/.local/bin/rote" || PLAY_ROTE_BIN=''
        fi
    }

    read_rote_version() {
        PLAY_VERSION_OUTPUT=$("$PLAY_ROTE_BIN" --version 2>/dev/null) || return 1
        INSTALLED_ROTE_VERSION=${PLAY_VERSION_OUTPUT##* }
        [ -n "$INSTALLED_ROTE_VERSION" ]
    }

    version_at_least() {
        PLAY_CURRENT=$1
        PLAY_MINIMUM=$2
        PLAY_CURRENT_PRERELEASE=0
        PLAY_MINIMUM_PRERELEASE=0

        PLAY_CURRENT=${PLAY_CURRENT#v}
        PLAY_MINIMUM=${PLAY_MINIMUM#v}
        PLAY_CURRENT=${PLAY_CURRENT%%+*}
        PLAY_MINIMUM=${PLAY_MINIMUM%%+*}
        case "$PLAY_CURRENT" in
            *-*) PLAY_CURRENT_PRERELEASE=1; PLAY_CURRENT=${PLAY_CURRENT%%-*} ;;
        esac
        case "$PLAY_MINIMUM" in
            *-*) PLAY_MINIMUM_PRERELEASE=1; PLAY_MINIMUM=${PLAY_MINIMUM%%-*} ;;
        esac

        PLAY_CURRENT_MAJOR=${PLAY_CURRENT%%.*}
        if [ "$PLAY_CURRENT_MAJOR" = "$PLAY_CURRENT" ]; then
            PLAY_CURRENT_MINOR=0
            PLAY_CURRENT_PATCH=0
        else
            PLAY_CURRENT_REST=${PLAY_CURRENT#*.}
            PLAY_CURRENT_MINOR=${PLAY_CURRENT_REST%%.*}
            if [ "$PLAY_CURRENT_MINOR" = "$PLAY_CURRENT_REST" ]; then
                PLAY_CURRENT_PATCH=0
            else
                PLAY_CURRENT_PATCH=${PLAY_CURRENT_REST#*.}
            fi
        fi

        PLAY_MINIMUM_MAJOR=${PLAY_MINIMUM%%.*}
        if [ "$PLAY_MINIMUM_MAJOR" = "$PLAY_MINIMUM" ]; then
            PLAY_MINIMUM_MINOR=0
            PLAY_MINIMUM_PATCH=0
        else
            PLAY_MINIMUM_REST=${PLAY_MINIMUM#*.}
            PLAY_MINIMUM_MINOR=${PLAY_MINIMUM_REST%%.*}
            if [ "$PLAY_MINIMUM_MINOR" = "$PLAY_MINIMUM_REST" ]; then
                PLAY_MINIMUM_PATCH=0
            else
                PLAY_MINIMUM_PATCH=${PLAY_MINIMUM_REST#*.}
            fi
        fi

        for PLAY_PART in \
            "$PLAY_CURRENT_MAJOR" "$PLAY_CURRENT_MINOR" "$PLAY_CURRENT_PATCH" \
            "$PLAY_MINIMUM_MAJOR" "$PLAY_MINIMUM_MINOR" "$PLAY_MINIMUM_PATCH"
        do
            case "$PLAY_PART" in
                '' | *[!0-9]*) return 2 ;;
            esac
        done

        if [ "$PLAY_CURRENT_MAJOR" -ne "$PLAY_MINIMUM_MAJOR" ]; then
            [ "$PLAY_CURRENT_MAJOR" -gt "$PLAY_MINIMUM_MAJOR" ]
            return
        fi
        if [ "$PLAY_CURRENT_MINOR" -ne "$PLAY_MINIMUM_MINOR" ]; then
            [ "$PLAY_CURRENT_MINOR" -gt "$PLAY_MINIMUM_MINOR" ]
            return
        fi
        if [ "$PLAY_CURRENT_PATCH" -ne "$PLAY_MINIMUM_PATCH" ]; then
            [ "$PLAY_CURRENT_PATCH" -gt "$PLAY_MINIMUM_PATCH" ]
            return
        fi
        if [ "$PLAY_CURRENT_PRERELEASE" -ne "$PLAY_MINIMUM_PRERELEASE" ]; then
            [ "$PLAY_CURRENT_PRERELEASE" -lt "$PLAY_MINIMUM_PRERELEASE" ]
            return
        fi
        return 0
    }

    browser_failure_in_log() {
        grep -Eiq \
            'browser failed to open|cannot open (a )?browser|could not open (a )?browser|browser could not be opened' \
            "$1" 2>/dev/null
    }

    create_login_dir() {
        PLAY_LOGIN_COUNTER=0
        while [ "$PLAY_LOGIN_COUNTER" -lt 100 ]; do
            PLAY_LOGIN_DIR=${TMPDIR:-/tmp}/rote-play-login.$$.${PLAY_LOGIN_COUNTER}
            if (umask 077 && mkdir "$PLAY_LOGIN_DIR") 2>/dev/null; then
                return 0
            fi
            PLAY_LOGIN_COUNTER=$((PLAY_LOGIN_COUNTER + 1))
        done
        return 1
    }

    cleanup_login() {
        rm -f "$PLAY_LOGIN_FIFO" "$PLAY_LOGIN_LOG" "$PLAY_LOGIN_FLAG"
        rmdir "$PLAY_LOGIN_DIR" 2>/dev/null || true
    }

    stop_login_processes() {
        for PLAY_LOGIN_CHILD in \
            "$PLAY_LOGIN_PID" "$PLAY_LOGIN_MONITOR_PID" "$PLAY_LOGIN_TEE_PID"
        do
            if [ -n "$PLAY_LOGIN_CHILD" ]; then
                kill "$PLAY_LOGIN_CHILD" 2>/dev/null || true
            fi
        done
    }

    abort_login() {
        PLAY_LOGIN_ABORT_STATUS=$1
        stop_login_processes
        cleanup_login
        trap - HUP INT TERM
        exit "$PLAY_LOGIN_ABORT_STATUS"
    }

    monitor_login_browser_failure() {
        while kill -0 "$PLAY_LOGIN_PID" 2>/dev/null; do
            if browser_failure_in_log "$PLAY_LOGIN_LOG"; then
                : >"$PLAY_LOGIN_FLAG"
                kill "$PLAY_LOGIN_PID" 2>/dev/null || true
                return
            fi
            sleep 1
        done
    }

    run_login() {
        PLAY_LOGIN_BROWSER_FAILED=0
        PLAY_LOGIN_PID=''
        PLAY_LOGIN_MONITOR_PID=''
        PLAY_LOGIN_TEE_PID=''
        if ! create_login_dir; then
            printf 'Could not create temporary files for browser sign-in.\n' >&2
            return 1
        fi
        PLAY_LOGIN_FIFO=$PLAY_LOGIN_DIR/output
        PLAY_LOGIN_LOG=$PLAY_LOGIN_DIR/login.log
        PLAY_LOGIN_FLAG=$PLAY_LOGIN_DIR/browser-failed

        # Arm traps before the fifo/children so any signal tears everything down.
        trap 'abort_login 129' HUP
        trap 'abort_login 130' INT
        trap 'abort_login 143' TERM

        if ! mkfifo "$PLAY_LOGIN_FIFO"; then
            printf 'Could not prepare browser sign-in output.\n' >&2
            cleanup_login
            trap - HUP INT TERM
            return 1
        fi

        tee "$PLAY_LOGIN_LOG" <"$PLAY_LOGIN_FIFO" &
        PLAY_LOGIN_TEE_PID=$!
        "$PLAY_ROTE_BIN" login <&9 >"$PLAY_LOGIN_FIFO" 2>&1 &
        PLAY_LOGIN_PID=$!
        monitor_login_browser_failure &
        PLAY_LOGIN_MONITOR_PID=$!

        wait "$PLAY_LOGIN_PID"
        PLAY_LOGIN_STATUS=$?
        kill "$PLAY_LOGIN_MONITOR_PID" 2>/dev/null || true
        wait "$PLAY_LOGIN_MONITOR_PID" 2>/dev/null || true
        wait "$PLAY_LOGIN_TEE_PID" 2>/dev/null || true

        if [ -f "$PLAY_LOGIN_FLAG" ] || browser_failure_in_log "$PLAY_LOGIN_LOG"; then
            PLAY_LOGIN_BROWSER_FAILED=1
        fi
        cleanup_login
        trap - HUP INT TERM
        return "$PLAY_LOGIN_STATUS"
    }

    print_remote_login_guidance() {
        printf '\nThis machine could not complete browser sign-in.\n'
        printf 'On a machine with a browser, create a claim token:\n'
        printf '  rote provision\n'
        printf 'Then claim it on this machine:\n'
        printf '  "%s" claim <token>\n' "$PLAY_ROTE_BIN"
    }

    complete_sign_in() {
        run_login
        PLAY_SIGN_IN_STATUS=$?
        if [ "$PLAY_LOGIN_BROWSER_FAILED" -eq 1 ]; then
            print_remote_login_guidance
            return 1
        fi
        if [ "$PLAY_SIGN_IN_STATUS" -ne 0 ]; then
            printf 'Sign-in did not complete successfully.\n' >&2
            return "$PLAY_SIGN_IN_STATUS"
        fi
        return 0
    }

    print_noninteractive_next_steps() {
        printf '\nNo interactive terminal is available; no changes were made.\n'
        printf 'Run these commands from an interactive terminal:\n'
        if [ "$1" = "install" ]; then
            printf '  curl -fsSL --proto =https --proto-redir =https "%s" | ROTE_YES=1 bash\n' "$PLAY_INSTALL_URL"
        elif [ "$1" = "update" ]; then
            printf '  "%s" update\n' "$PLAY_ROTE_BIN"
        fi
        if [ "$1" != "ready" ]; then
            if [ -n "$PLAY_ROTE_BIN" ]; then
                PLAY_NEXT_BINARY=$PLAY_ROTE_BIN
            elif [ -n "$PLAY_BINARY_OVERRIDE" ]; then
                PLAY_NEXT_BINARY=$PLAY_BINARY_OVERRIDE
            else
                PLAY_NEXT_BINARY=$HOME/.local/bin/rote
            fi
            printf '  "%s" play inspect "%s"\n' "$PLAY_NEXT_BINARY" "$PLAY_REF"
        fi
        if [ -n "$PLAY_ROTE_BIN" ]; then
            PLAY_NEXT_BINARY=$PLAY_ROTE_BIN
        elif [ -n "$PLAY_BINARY_OVERRIDE" ]; then
            PLAY_NEXT_BINARY=$PLAY_BINARY_OVERRIDE
        else
            PLAY_NEXT_BINARY=$HOME/.local/bin/rote
        fi
        printf '  "%s" login\n' "$PLAY_NEXT_BINARY"
        printf '  "%s" play run "%s"\n' "$PLAY_NEXT_BINARY" "$PLAY_REF"
    }

    print_plan

    case "$PLAY_REF" in
        '<owner>/<name>[@version]')
            printf 'The play reference was not substituted by the serving endpoint.\n' >&2
            return 1
            ;;
    esac
    case "$MIN_ROTE_VERSION" in
        '<minimum-rote-version>')
            printf 'The minimum rote version was not stamped into this script.\n' >&2
            return 1
            ;;
    esac

    # Reject substituted values that aren't a plain reference/version before use.
    case "$PLAY_REF" in
        -* | *[!A-Za-z0-9._/@-]*)
            printf 'Refusing to proceed: the play reference contains unexpected characters.\n' >&2
            return 1
            ;;
    esac
    case "$MIN_ROTE_VERSION" in
        *[!A-Za-z0-9.+-]*)
            printf 'Refusing to proceed: the minimum rote version contains unexpected characters.\n' >&2
            return 1
            ;;
    esac

    if [ -n "$PLAY_BINARY_OVERRIDE" ]; then
        if ! set_absolute_rote_bin "$PLAY_BINARY_OVERRIDE"; then
            printf 'Could not resolve the configured rote binary path.\n' >&2
            return 1
        fi
        PLAY_BINARY_OVERRIDE=$PLAY_ROTE_BIN
    fi

    if has_tty; then
        if ! prepare_terminal; then
            printf 'Could not open the interactive terminal.\n' >&2
            return 1
        fi
    else
        # Keep fd 9 valid either way so every child can be given an explicit
        # stdin. Inheriting ours would hand `curl … | sh` children the pipe that
        # still holds the unparsed remainder of this script.
        exec 9</dev/null
        PLAY_PROMPT_TARGET=stderr
    fi

    locate_rote
    if [ -z "$PLAY_ROTE_BIN" ]; then
        case "$PLAY_INSTALL_URL" in
            https://*) ;;
            *)
                printf 'Refusing to install rote from a non-https URL: %s\n' \
                    "$PLAY_INSTALL_URL" >&2
                return 1
                ;;
        esac
        if ! has_tty; then
            print_noninteractive_next_steps install
            return 1
        fi
        confirm 'Install rote? [Y/n] ' yes
        PLAY_CONFIRM_STATUS=$?
        if [ "$PLAY_CONFIRM_STATUS" -eq 2 ]; then
            return 1
        fi
        if [ "$PLAY_CONFIRM_STATUS" -ne 0 ]; then
            printf '\nInstallation cancelled.\n'
            return 0
        fi
        printf '\nInstalling rote...\n'
        if ! curl -fsSL --proto =https --proto-redir =https "$PLAY_INSTALL_URL" | ROTE_YES=1 bash; then
            printf 'rote installation failed.\n' >&2
            return 1
        fi
        locate_rote
        if [ -z "$PLAY_ROTE_BIN" ]; then
            printf 'rote was installed, but no executable was found.\n' >&2
            return 1
        fi
    fi

    if ! read_rote_version; then
        printf 'Could not determine the installed rote version.\n' >&2
        return 1
    fi
    version_at_least "$INSTALLED_ROTE_VERSION" "$MIN_ROTE_VERSION"
    PLAY_VERSION_STATUS=$?
    if [ "$PLAY_VERSION_STATUS" -eq 2 ]; then
        printf 'Invalid rote version: installed=%s minimum=%s\n' \
            "$INSTALLED_ROTE_VERSION" "$MIN_ROTE_VERSION" >&2
        return 1
    fi
    if [ "$PLAY_VERSION_STATUS" -ne 0 ]; then
        if ! has_tty; then
            print_noninteractive_next_steps update
            return 1
        fi
        printf '\nrote %s is older than the required %s.\n' \
            "$INSTALLED_ROTE_VERSION" "$MIN_ROTE_VERSION"
        confirm 'Update rote now? [Y/n] ' yes
        PLAY_CONFIRM_STATUS=$?
        if [ "$PLAY_CONFIRM_STATUS" -eq 2 ]; then
            return 1
        fi
        if [ "$PLAY_CONFIRM_STATUS" -ne 0 ]; then
            printf '\nUpdate cancelled. The play was not inspected or run.\n'
            return 0
        fi
        if ! "$PLAY_ROTE_BIN" update --yes <&9; then
            printf 'rote update failed.\n' >&2
            return 1
        fi
        if ! read_rote_version || ! version_at_least "$INSTALLED_ROTE_VERSION" "$MIN_ROTE_VERSION"; then
            printf 'rote is still older than the required %s after updating.\n' \
                "$MIN_ROTE_VERSION" >&2
            return 1
        fi
    fi

    if ! has_tty; then
        print_noninteractive_next_steps ready
        return 0
    fi

    "$PLAY_ROTE_BIN" whoami --check </dev/null >/dev/null 2>&1
    PLAY_AUTH_STATUS=$?
    if [ "$PLAY_AUTH_STATUS" -eq 77 ]; then
        confirm 'Sign in to run this play? [Y/n] ' yes
        PLAY_CONFIRM_STATUS=$?
        if [ "$PLAY_CONFIRM_STATUS" -eq 2 ]; then
            return 1
        fi
        if [ "$PLAY_CONFIRM_STATUS" -ne 0 ]; then
            printf '\nSign-in cancelled. The play was not run.\n'
            return 0
        fi
        complete_sign_in || return $?
        "$PLAY_ROTE_BIN" whoami --check </dev/null >/dev/null 2>&1
        PLAY_AUTH_STATUS=$?
        if [ "$PLAY_AUTH_STATUS" -ne 0 ]; then
            print_remote_login_guidance
            return "$PLAY_AUTH_STATUS"
        fi
    elif [ "$PLAY_AUTH_STATUS" -ne 0 ]; then
        printf 'Could not determine whether rote is signed in (exit %s).\n' \
            "$PLAY_AUTH_STATUS" >&2
        return "$PLAY_AUTH_STATUS"
    fi

    "$PLAY_ROTE_BIN" play run "$PLAY_REF" <&9
    PLAY_RUN_STATUS=$?
    if [ "$PLAY_RUN_STATUS" -ne 0 ]; then
        printf '\nThe play did not complete successfully.\n' >&2
        return 1
    fi

}

main "$@"
