#!/bin/sh
# Generated by ship (e7081c8f); edit internal/generate/templates and run ship sync
#
# The half of a macOS screenshot set that is the same in every repository.
#
# There are three files, as there are on Windows. `screenshot.sh` is the driver
# and knows nothing about any application: it sizes the window, fronts it,
# drives the actions given, parks the pointer and captures. `shots.sh` is the
# repository's and says which document and which frames. This is what sits
# between them — the arguments, the language, where the set lands, and the
# counting — and it existed in two divergent copies before it was one file:
# Segler's took no language and wrote its frames flat, Tommy Flyleaf's took
# `--lang` and wrote a subdirectory per locale, and only the second is the
# layout `ship <repo> --shots` reads.
#
# WHAT shots.sh HAS TO PROVIDE
#
#   WIDTH, HEIGHT    the frame size every coordinate in the recipes is
#                    measured from
#   for_language     given a language tag, sets `document` to the file or the
#                    folder to open and may set `staged_name` and whatever text
#                    the recipes type; refuses a language it has no set for
#   shots            the recipes, each line `shot NAME <actions>`
#
# and optionally:
#
#   AS_ARGS          `yes` where the application takes what it opens as an
#                    argument rather than declaring the type
#   EVERY_SHOT       actions run before every shutter, the reference frame
#                    included, for state every coordinate is measured against
#
# WHAT THIS PROVIDES BACK
#
#   shot             one frame: a name, then the actions
#   stage            the copy a frame opens, in a folder that reads like a
#                    person's machine
#   appearance       `appearance light` or `appearance dark`, set on the
#                    desktop and read back
#
# LIGHT LEADS
#
# A listing's first picture is the one the product page shows largest, an
# application that follows the system appearance is light on a fresh macOS
# installation, and that is what most people see. So a set opens light, and
# dark is worth a frame rather than half the set.
#
# and then, at the end:
#
#   . "$(dirname -- "$0")/take-shots.sh"
#   take_shots "$@"
#
# THE ENGLISH SET IS ASKED FOR TOO
#
# Every set is taken with a language named, English included, so the frames do
# not depend on what the machine taking them happens to be set to. A runner
# that came up in another language would otherwise photograph that language and
# report success.
#
# Author: David M. Anderson
# Built with AI assistance (Claude, Anthropic)

# locale_of turns a language as a person types it into the tag both stores use
# for a listing, which is also the subdirectory name ship reads.
locale_of() {
    case "$1" in
        en|en-US|en-us) echo en-US ;;
        de|de-DE|de-de) echo de-DE ;;
        *) return 1 ;;
    esac
}

take_shots() {
    here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
    app=""
    lang=en
    reference=no

    while [ $# -gt 0 ]; do
        case "$1" in
            --app) app="${2:?--app needs a bundle}"; shift 2 ;;
            --lang) lang="${2:?--lang needs a language tag}"; shift 2 ;;
            --outdir) OUTDIR="${2:?--outdir needs a directory}"; shift 2 ;;
            --reference) reference=yes; shift ;;
            -h|--help) sed -n '2,70p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
            *) echo "shots.sh: unknown argument $1" >&2; exit 2 ;;
        esac
    done

    [ -n "$app" ] || {
        echo "shots.sh: --app needs the bundle to photograph" >&2
        echo "  ./packaging/macos/shots.sh --app dist/Something.app --lang de" >&2
        exit 2
    }
    [ -d "$app" ] || { echo "shots.sh: no bundle at $app" >&2; exit 1; }

    locale=$(locale_of "$lang") || {
        echo "shots.sh: no locale is known for ${lang}" >&2
        exit 2
    }

    # The repository's half: which document this language opens, and whatever
    # its recipes type into the window.
    staged_name=""
    for_language "$lang"
    [ -n "${document:-}" ] || { echo "shots.sh: for_language set no document for ${lang}" >&2; exit 1; }
    [ -f "$document" ] || [ -d "$document" ] ||
        { echo "shots.sh: no document at ${document}" >&2; exit 1; }

    source_document=$document

    OUTDIR="${OUTDIR:-$(CDPATH= cd -- "${here}/../.." && pwd)/dist/screenshots}/${locale}"
    mkdir -p "$OUTDIR"

    taken=0
    if [ "$reference" = yes ]; then
        # One plain frame, no actions. This is what a coordinate in the recipes
        # gets measured off, and it is taken through the same driver at the
        # same size so that the measurement holds.
        shot 00-reference
    else
        shots
    fi

    # A set that came back short is a set somebody submits without noticing, so
    # the count is said rather than left to be counted.
    echo "shots.sh: ${taken} shot(s) in ${OUTDIR}"
}

# appearance sets the desktop's appearance and reads it back.
#
# Here rather than in each repository's recipe, where it was written once and
# would have been written five more times. No control in a window changes it,
# so it is set before a launch; the driver stops whatever is running and
# launches again for every frame, so a value set here is read at the start it
# belongs to.
#
# **Asked of System Events, and not written with `defaults`.** `defaults write
# -g AppleInterfaceStyle Dark` writes the value and `defaults read` returns it,
# because both go through the same preferences cache - and the window server is
# never told, so an application launched afterwards still draws light. Measured
# on 2026-09-17: the dark frame came back a pixel-for-pixel duplicate of its
# light pair. System Events sets it the way the Settings pane does, and it is
# already trusted for Accessibility on a runner, which the driver needs anyway.
#
# Read back for the same reason: a frame taken after an appearance that did not
# take is a duplicate of its pair, and a duplicate is the sort of thing that
# reaches a listing.
appearance() {
    case "$1" in
        light) want=false ;;
        dark) want=true ;;
        *) echo "shots.sh: appearance is light or dark, not $1" >&2; exit 2 ;;
    esac
    osascript -e "tell application \"System Events\" to tell appearance preferences to set dark mode to ${want}" >/dev/null
    # The first second is the settle this always waited out, kept: the value is
    # read on a machine that has had a moment to apply it. What is new is
    # everything after it. A desktop slower than one second is a runner that is
    # busy, not one that refused, and this file is sourced with `set -e` from a
    # `shots.sh` - so the exit below ends the whole set, and with it the
    # release, on a machine that was only loaded.
    sleep 1
    waited=0
    while :; do
        # `|| true` because the exit status is not the answer here and `set -e`
        # is in force: what this asks is whether the desktop reads back the
        # value, and an osascript that refuses should reach the report below
        # rather than end the run at the assignment.
        got=$(osascript -e 'tell application "System Events" to tell appearance preferences to get dark mode' || true)
        if [ "$got" = "$want" ]; then
            break
        fi
        # An empty answer is a refusal and not a slow desktop, so it is not
        # asked again: waiting out six seconds would change nothing and would
        # print osascript's refusal ten more times over the useful line.
        if [ -z "$got" ]; then
            break
        fi
        if [ "$waited" -ge 10 ]; then
            break
        fi
        waited=$((waited + 1))
        sleep 0.5
    done
    if [ "$got" != "$want" ]; then
        # An empty answer is not a desktop that disagreed. osascript prints its
        # refusal on stderr and nothing on stdout, and the likeliest reason on
        # a runner is that System Events is not trusted for Accessibility - so
        # that is what is named, rather than a line about the appearance with a
        # blank where the value should be.
        if [ -z "$got" ]; then
            echo "shots.sh: System Events answered nothing about dark mode; it needs Accessibility trust, which the driver needs too" >&2
        else
            echo "shots.sh: asked for $1 and the desktop still reads dark mode ${got} after 6s" >&2
        fi
        exit 1
    fi
    echo "shots.sh: desktop set to $1"
}

# stage copies what the shot opens somewhere that reads like a person's machine
# rather than like a build: the path is in the frame, and photographing the
# checkout put a /private/tmp workspace in the picture.
#
# Before every shot rather than once, because an application that writes beside
# what it opens leaves its output in that folder, and the next shot then
# photographs the one before it.
stage() {
    [ -n "$staged_name" ] || return 0
    staged="${HOME}/Documents"
    mkdir -p "$staged"
    rm -rf "${staged:?}/${staged_name}"
    if [ -d "$source_document" ]; then
        mkdir -p "${staged}/${staged_name}"
        cp "$source_document"/* "${staged}/${staged_name}/"
    else
        cp "$source_document" "${staged}/${staged_name}"
    fi
    document="${staged}/${staged_name}"
}

# shot takes one frame: a name, then the actions that put the window into the
# state being photographed, in the order they are given.
#
#   --click X,Y    press a control
#   --double X,Y   press it twice inside the double-click interval
#   --type TEXT    type
#   --key NAME     one key, optionally with modifiers: cmd+a, return
shot() {
    shot_name=$1
    shift
    echo "shots.sh: ${locale} ${shot_name}"
    stage
    as_args=""
    [ "${AS_ARGS:-no}" = yes ] && as_args=--args
    # shellcheck disable=SC2086
    "${here}/screenshot.sh" \
        --app "$app" \
        --document "$document" ${as_args} \
        --width "$WIDTH" --height "$HEIGHT" \
        --lang "$lang" \
        --out "${OUTDIR}/${shot_name}.png" \
        ${EVERY_SHOT:-} "$@"
    taken=$((taken + 1))
}
