#!/usr/bin/env bash
# cleat: {"name":"Currency","keyword":"cur","icon":"dollarsign.circle","subtitle":"200 mkd to euro \u00b7 daily reference rates","version":"1.1.1","requires":["curl","osascript"],"examples":["cur 200 mkd to euro"]}
# Accepts "100 usd eur", "100 usd to eur", and "100 usd in eur".
set -euo pipefail

if [ "${1:-}" = "--run" ]; then
    printf '%s' "${2:-}" | pbcopy
    exit 0
fi

read -r amount from connector destination extra <<< "$(printf '%s' "${1:-}" | tr '[:lower:]' '[:upper:]')" || true
if [ -z "${amount:-}" ] || [ -z "${from:-}" ] || [ -z "${connector:-}" ]; then
    echo '{"title":"Type an amount and two currencies","subtitle":"For example: 100 usd to eur"}'
    exit 0
fi
if [[ "$connector" == "TO" || "$connector" == "IN" ]]; then
    to="${destination:-}"
else
    to="$connector"
    extra="${destination:-}${extra:-}"
fi
currency_code() {
    case "$1" in
        EURO|EUROS|€) echo EUR ;;
        DENAR|DENARS|DENARI) echo MKD ;;
        DOLLAR|DOLLARS|\$) echo USD ;;
        POUND|POUNDS|£) echo GBP ;;
        BITCOIN) echo BTC ;;
        ETHEREUM|ETHER) echo ETH ;;
        SOLANA) echo SOL ;;
        RIPPLE) echo XRP ;;
        CARDANO) echo ADA ;;
        DOGECOIN) echo DOGE ;;
        LITECOIN) echo LTC ;;
        TETHER) echo USDT ;;
        POLKADOT) echo DOT ;;
        AVALANCHE) echo AVAX ;;
        CHAINLINK) echo LINK ;;
        *) printf '%s\n' "$1" ;;
    esac
}
from="$(currency_code "$from")"
to="$(currency_code "$to")"
if [[ ! "$amount" =~ ^[0-9]+([.][0-9]+)?$ || ! "$from" =~ ^[A-Z]{3,4}$ || ! "$to" =~ ^[A-Z]{3,4}$ || -n "${extra:-}" ]]; then
    echo 'Use an amount and two three-letter currency codes, for example: 100 usd to eur.' >&2
    exit 1
fi

# Coins are priced by CoinGecko in USD; Frankfurter turns USD into anything else.
coin_id() {
    case "$1" in
        BTC) echo bitcoin ;; ETH) echo ethereum ;; SOL) echo solana ;; XRP) echo ripple ;; ADA) echo cardano ;;
        DOGE) echo dogecoin ;; LTC) echo litecoin ;; BNB) echo binancecoin ;; DOT) echo polkadot ;;
        USDT) echo tether ;; USDC) echo usd-coin ;; AVAX) echo avalanche-2 ;; LINK) echo chainlink ;;
        *) return 1 ;;
    esac
}
response_file="$(mktemp "${TMPDIR:-/tmp}/cleat-currency.XXXXXX")"
trap 'rm -f "$response_file"' EXIT

# fiat_rate FROM TO: the Frankfurter reference rate, 1 when they match.
fiat_rate() {
    if [[ "$1" == "$2" ]]; then echo 1; return 0; fi
    local status
    if ! status="$(curl -sSL --connect-timeout 1 --max-time 1.8 -o "$response_file" -w '%{http_code}' "https://api.frankfurter.dev/v2/rate/$1/$2")"; then
        echo 'Could not reach the rate service within the time limit. Check your connection and try again.' >&2
        exit 1
    fi
    if [[ "$status" == "404" || "$status" == "422" ]]; then
        echo "No reference rate for $1 to $2. Check the currency names or codes." >&2
        exit 1
    fi
    if [[ "$status" != "200" ]]; then
        echo "The rate service returned HTTP $status. Please try again shortly." >&2
        exit 1
    fi
    # XML retains the number's precision; plutil's raw real output rounds to
    # six decimals, which noticeably distorts small exchange rates.
    local r
    r="$(/usr/bin/plutil -extract rate xml1 -o - "$response_file" 2>/dev/null | awk -F '[<>]' '/<(real|integer)>/ {print $3}' || true)"
    if [[ ! "$r" =~ ^[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$ ]]; then
        echo 'The rate service returned an invalid response. Please try again.' >&2
        exit 1
    fi
    echo "$r"
}

# coin_usd CODE: the spot price of one coin in US dollars.
coin_usd() {
    local id status price
    id="$(coin_id "$1")" || { echo "No price source for $1." >&2; exit 1; }
    if ! status="$(curl -sSL --connect-timeout 1.5 --max-time 3 -o "$response_file" -w '%{http_code}' "https://api.coingecko.com/api/v3/simple/price?ids=$id&vs_currencies=usd")"; then
        echo 'Could not reach the price service within the time limit. Check your connection and try again.' >&2
        exit 1
    fi
    if [[ "$status" != "200" ]]; then
        echo "The price service returned HTTP $status. Please try again shortly." >&2
        exit 1
    fi
    price="$(/usr/bin/plutil -extract "$id.usd" raw -o - "$response_file" 2>/dev/null || true)"
    if [[ ! "$price" =~ ^[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$ ]]; then
        echo 'The price service returned an invalid response. Please try again.' >&2
        exit 1
    fi
    echo "$price"
}

# Results are printed as plain decimals: "%.10g" wrote 3.075e+12 for a large
# amount and 2e-05 for a small coin price, and that is what Return copied.
source_note="Frankfurter daily reference rate"
if [[ "$from" == "$to" ]]; then
    rate="$amount"
elif coin_id "$from" >/dev/null 2>&1 || coin_id "$to" >/dev/null 2>&1; then
    source_note="CoinGecko spot price"
    if coin_id "$from" >/dev/null 2>&1; then from_usd="$(coin_usd "$from")"; else from_usd="$(fiat_rate "$from" USD)"; fi
    if coin_id "$to" >/dev/null 2>&1; then to_usd="$(coin_usd "$to")"; else to_usd="$(fiat_rate "$to" USD)"; fi
    rate="$(LC_ALL=C awk -v amount="$amount" -v f="$from_usd" -v t="$to_usd" 'BEGIN {s = sprintf("%.8f", amount * f / t); sub(/0+$/, "", s); sub(/[.]$/, "", s); print s}')"
else
    rate="$(fiat_rate "$from" "$to")"
    rate="$(LC_ALL=C awk -v amount="$amount" -v rate="$rate" 'BEGIN {s = sprintf("%.8f", amount * rate); sub(/0+$/, "", s); sub(/[.]$/, "", s); print s}')"
fi
printf '{"title":"%s %s","subtitle":"%s %s · %s","arg":"%s"}\n' "$rate" "$to" "$amount" "$from" "$source_note" "$rate"
