#!/bin/bash
# freeslapmac-silent — build a silent copy of FreeSlapMac.
#
# FreeSlapMac has no mute: its slap/tap sounds are MP3s inside
# Contents/Resources/Sounds/, played by the bundled `spank` process. This script copies
# the app and overwrites every MP3 with a hand-built silent MPEG frame of the same
# duration, then ad-hoc re-signs the copy (the original is ad-hoc signed too).
# Detection, screen flash, combos, counters and settings are untouched.
#
# Usage:  bash build.sh [SOURCE_APP] [DEST_APP]
# Default: bash build.sh "/Applications/FreeSlapMac.app" "/Applications/FreeSlapMac Silent.app"
# Needs:  macOS only. No python, no brew, no ffmpeg — afinfo/afconvert/codesign ship with macOS.

set -euo pipefail

SRC="${1:-/Applications/FreeSlapMac.app}"
DEST="${2:-/Applications/FreeSlapMac Silent.app}"
SOUNDS_REL="Contents/Resources/Sounds"

die() { printf 'error: %s\n' "$*" >&2; exit 1; }

[ -d "$SRC" ] || die "source app not found: $SRC"
[ -d "$SRC/$SOUNDS_REL" ] || die "not a FreeSlapMac bundle (no $SOUNDS_REL): $SRC"
[ "$(cd "$SRC" && pwd -P)" != "$(cd "$DEST" 2>/dev/null && pwd -P || echo -)" ] || die "source and destination are the same app"
if [ -e "$DEST" ] && [ "${FORCE:-0}" != "1" ]; then
  die "destination already exists: $DEST  (re-run with FORCE=1 to replace it)"
fi

TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT

# One silent MPEG-1 Layer III frame: 44.1 kHz, mono, 128 kb/s = 417 bytes, 1152 samples
# (26.122 ms). Zeroed side-info + zeroed main data decode to exact digital silence.
printf '\xff\xfb\x90\xc0' > "$TMP/frame"
head -c 413 /dev/zero >> "$TMP/frame"
[ "$(wc -c < "$TMP/frame" | tr -d ' ')" = "417" ] || die "frame build failed"
FRAME_MS=26.122448979591837
# 3000 frames (78 s) covers every clip in the shipped library with room to spare.
: > "$TMP/silence.mp3"
args=(); for ((i = 0; i < 3000; i++)); do args+=("$TMP/frame"); done
cat "${args[@]}" > "$TMP/silence.mp3"

printf 'copying %s\n     -> %s\n' "$SRC" "$DEST"
rm -rf "$DEST"
ditto "$SRC" "$DEST"

total=0 replaced=0
while IFS= read -r -d '' f; do
  total=$((total + 1))
  dur="$(afinfo "$f" 2>/dev/null | awk -F': ' '/estimated duration/ {print $2; exit}' | awk '{print $1}')"
  if [ -z "$dur" ]; then
    printf 'warn: no duration for %s — using 1.0 s\n' "$f" >&2
    dur=1.0
  fi
  frames="$(awk -v d="$dur" -v m="$FRAME_MS" 'BEGIN { n = int(d * 1000 / m + 0.5); if (n < 1) n = 1; print n }')"
  head -c $((frames * 417)) "$TMP/silence.mp3" > "$f.new" && mv "$f.new" "$f"
  replaced=$((replaced + 1))
done < <(find "$DEST/$SOUNDS_REL" -name '*.mp3' -print0)
[ "$replaced" -gt 0 ] || die "no MP3s found to silence under $DEST/$SOUNDS_REL"

# Distinguish it in the menu bar / Dock. Bundle id is deliberately preserved so the
# silent build inherits your sensitivity, mode, cooldown and creak preferences.
PB=/usr/libexec/PlistBuddy
INFO="$DEST/Contents/Info.plist"
$PB -c "Set :CFBundleName FreeSlapMac Silent" "$INFO" 2>/dev/null || $PB -c "Add :CFBundleName string FreeSlapMac Silent" "$INFO"
$PB -c "Set :CFBundleDisplayName FreeSlapMac Silent" "$INFO" 2>/dev/null || $PB -c "Add :CFBundleDisplayName string FreeSlapMac Silent" "$INFO"

# Modifying resources breaks the original seal — re-sign ad-hoc, exactly like the original.
codesign --force --deep --sign - "$DEST" >/dev/null 2>&1 || die "codesign failed"
xattr -cr "$DEST" 2>/dev/null || true

printf 'silenced %d/%d MP3s, re-signed ad-hoc\n' "$replaced" "$total"

if [ -x "$(dirname "$0")/verify.sh" ] || [ -f "$(dirname "$0")/verify.sh" ]; then
  printf '\nverifying…\n'
  bash "$(dirname "$0")/verify.sh" "${VERIFY_SRC:-$SRC}" "$DEST"
else
  printf '\n(verify.sh not found next to build.sh — skipping the proof step)\n'
fi

printf '\nDone. Launch with:\n  open -a "%s"\nRun only one build at a time — both write /tmp/freeslapmac_cmd.\n' "$DEST"
