#!/usr/bin/env bash
set -euo pipefail

die() {
  echo "Error: $*" >&2
  exit 1
}

require_command() {
  command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
}

assert_non_negative_number() {
  local label="$1"
  local value="$2"

  awk -v v="$value" 'BEGIN { exit !(v ~ /^[0-9]+([.][0-9]+)?$/) }' || die "$label must be a non-negative number"
}

assert_positive_number() {
  local label="$1"
  local value="$2"

  assert_non_negative_number "$label" "$value"
  awk -v v="$value" 'BEGIN { exit !(v > 0) }' || die "$label must be positive"
}

ensure_parent_dir() {
  mkdir -p "$(dirname "$1")"
}

has_stream() {
  local input_file="$1"
  local selector="$2"

  ffprobe -v error -select_streams "$selector" -show_entries stream=index -of csv=p=0 "$input_file" |
    sed -n '1p' |
    grep -q .
}

normalized_extension() {
  printf '%s' "${1##*.}" | tr '[:upper:]' '[:lower:]'
}

usage() {
  cat <<'EOF'
Usage:
  render-meme-vertical.sh [--dry-run] [--top TEXT] [--bottom TEXT] [--width N] [--height N] [--fps N] [--freeze-seconds N] <input> <output>

Examples:
  render-meme-vertical.sh \
    --top "WHEN YOU SHIP TO PROD ON FRIDAY" \
    --bottom "I AM A GREAT SINNER" \
    clip.mp4 meme.mp4

Environment:
  FONT_FILE                 Path to a .ttf font file. Default: DejaVuSans-Bold.ttf
  FONT_SIZE                 Default caption size in pixels. Default: 86
  TOP_FONT_SIZE             Top caption size. Default: FONT_SIZE
  BOTTOM_FONT_SIZE          Bottom caption size. Default: FONT_SIZE
  BORDER_WIDTH              Caption outline width. Default: 4
  SHADOW_OPACITY            Caption shadow opacity percent. Default: 80
  SHADOW_BLUR               Caption shadow blur radius. Default: 10
  SHADOW_OFFSET             Caption shadow Y offset. Default: 8
  TOP_MARGIN                Top caption offset. Default: 110
  BOTTOM_MARGIN             Bottom caption offset. Default: 130
  CAPTION_WIDTH_PERCENT     Caption box width as percent of canvas width. Default: 88
  FOREGROUND_SCALE_PERCENT  Main clip width as percent of canvas width. Default: 92
  BACKGROUND_BLUR           Blur strength for the background plate. Default: 28
  CRF                       Video quality. Default: 18
  AUDIO_BITRATE             Audio bitrate. Default: 192k
EOF
}

dry_run=0
top_text=""
bottom_text=""
width="1080"
height="1920"
fps="30"
freeze_seconds="0.8"
font_file="${FONT_FILE:-/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf}"
font_size="${FONT_SIZE:-86}"
top_font_size="${TOP_FONT_SIZE:-$font_size}"
bottom_font_size="${BOTTOM_FONT_SIZE:-$font_size}"
border_width="${BORDER_WIDTH:-4}"
shadow_opacity="${SHADOW_OPACITY:-80}"
shadow_blur="${SHADOW_BLUR:-10}"
shadow_offset="${SHADOW_OFFSET:-8}"
top_margin="${TOP_MARGIN:-110}"
bottom_margin="${BOTTOM_MARGIN:-130}"
caption_width_percent="${CAPTION_WIDTH_PERCENT:-88}"
foreground_scale_percent="${FOREGROUND_SCALE_PERCENT:-92}"
background_blur="${BACKGROUND_BLUR:-28}"
crf="${CRF:-18}"
audio_bitrate="${AUDIO_BITRATE:-192k}"

while [ "$#" -gt 0 ]; do
  case "$1" in
    --dry-run)
      dry_run=1
      shift
      ;;
    --top)
      shift
      [ "$#" -gt 0 ] || die "missing value for --top"
      top_text="$1"
      shift
      ;;
    --bottom)
      shift
      [ "$#" -gt 0 ] || die "missing value for --bottom"
      bottom_text="$1"
      shift
      ;;
    --width)
      shift
      [ "$#" -gt 0 ] || die "missing value for --width"
      width="$1"
      shift
      ;;
    --height)
      shift
      [ "$#" -gt 0 ] || die "missing value for --height"
      height="$1"
      shift
      ;;
    --fps)
      shift
      [ "$#" -gt 0 ] || die "missing value for --fps"
      fps="$1"
      shift
      ;;
    --freeze-seconds)
      shift
      [ "$#" -gt 0 ] || die "missing value for --freeze-seconds"
      freeze_seconds="$1"
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    --)
      shift
      break
      ;;
    -*)
      die "unknown option: $1"
      ;;
    *)
      break
      ;;
  esac
done

[ "$#" -eq 2 ] || {
  usage
  exit 1
}

input_file="$1"
output_file="$2"

[ -f "$input_file" ] || die "input file not found: $input_file"
[ -f "$font_file" ] || die "font file not found: $font_file"

assert_positive_number "width" "$width"
assert_positive_number "height" "$height"
assert_positive_number "fps" "$fps"
assert_non_negative_number "freeze_seconds" "$freeze_seconds"
assert_positive_number "FONT_SIZE" "$font_size"
assert_positive_number "TOP_FONT_SIZE" "$top_font_size"
assert_positive_number "BOTTOM_FONT_SIZE" "$bottom_font_size"
assert_positive_number "BORDER_WIDTH" "$border_width"
assert_positive_number "SHADOW_OPACITY" "$shadow_opacity"
assert_positive_number "SHADOW_BLUR" "$shadow_blur"
assert_positive_number "SHADOW_OFFSET" "$shadow_offset"
assert_positive_number "TOP_MARGIN" "$top_margin"
assert_positive_number "BOTTOM_MARGIN" "$bottom_margin"
assert_positive_number "CAPTION_WIDTH_PERCENT" "$caption_width_percent"
assert_positive_number "FOREGROUND_SCALE_PERCENT" "$foreground_scale_percent"
assert_positive_number "BACKGROUND_BLUR" "$background_blur"
assert_positive_number "CRF" "$crf"

require_command ffmpeg
require_command ffprobe
require_command convert

has_audio=0
if has_stream "$input_file" a:0; then
  has_audio=1
fi

foreground_width="$(awk -v w="$width" -v pct="$foreground_scale_percent" 'BEGIN { printf "%d\n", (w * pct / 100) }')"
caption_width="$(awk -v w="$width" -v pct="$caption_width_percent" 'BEGIN { printf "%d\n", (w * pct / 100) }')"

if [ "$dry_run" -eq 1 ]; then
  cat <<EOF
input_file=$input_file
output_file=$output_file
width=$width
height=$height
fps=$fps
freeze_seconds=$freeze_seconds
has_audio=$has_audio
foreground_width=$foreground_width
caption_width=$caption_width
top_font_size=$top_font_size
bottom_font_size=$bottom_font_size
border_width=$border_width
shadow_opacity=$shadow_opacity
shadow_blur=$shadow_blur
shadow_offset=$shadow_offset
top_text=$top_text
bottom_text=$bottom_text
EOF
  exit 0
fi

ensure_parent_dir "$output_file"

faststart_args=()
case "$(normalized_extension "$output_file")" in
  mp4|m4v|mov)
    faststart_args=(-movflags +faststart)
    ;;
esac

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

top_caption_file="$tmp_dir/top-caption.png"
bottom_caption_file="$tmp_dir/bottom-caption.png"

render_caption_image() {
  local text="$1"
  local point_size="$2"
  local output_png="$3"

  if [ -z "$text" ]; then
    convert -size 1x1 xc:none "PNG32:$output_png"
    return
  fi

  convert \
    \
    \( \
      -background none \
      -fill white \
      -stroke 'rgba(0,0,0,0.55)' \
      -strokewidth "$border_width" \
      -font "$font_file" \
      -gravity center \
      -size "${caption_width}x" \
      -pointsize "$point_size" \
      caption:"$text" \
    \) \
    \( \
      -background none \
      -fill 'rgba(0,0,0,0.95)' \
      -font "$font_file" \
      -gravity center \
      -size "${caption_width}x" \
      -pointsize "$point_size" \
      caption:"$text" \
      -background black \
      -shadow "${shadow_opacity}x${shadow_blur}+0+${shadow_offset}" \
    \) \
    +swap \
    -background none \
    -layers merge \
    +repage \
    "PNG32:$output_png"
}

render_caption_image "$top_text" "$top_font_size" "$top_caption_file"
render_caption_image "$bottom_text" "$bottom_font_size" "$bottom_caption_file"

# Build a dark blurred background plate, center the source, and overlay wrapped caption cards.
video_filter="\
  [0:v:0]fps=${fps},scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height},boxblur=${background_blur}:1,eq=brightness=-0.10:saturation=0.85[bg]; \
  [0:v:0]fps=${fps},scale=${foreground_width}:-2:force_original_aspect_ratio=decrease,tpad=stop_mode=clone:stop_duration=${freeze_seconds}[fg]; \
  [bg][fg]overlay=(W-w)/2:(H-h)/2[base]; \
  [base][1:v:0]overlay=(W-w)/2:${top_margin}[with_top]; \
  [with_top][2:v:0]overlay=(W-w)/2:H-h-${bottom_margin},setsar=1[v]"

if [ "$has_audio" -eq 1 ]; then
  ffmpeg \
    -y \
    -i "$input_file" \
    -i "$top_caption_file" \
    -i "$bottom_caption_file" \
    -filter_complex "${video_filter}; [0:a:0]aresample=async=1:first_pts=0,apad=pad_dur=${freeze_seconds}[a]" \
    -map "[v]" \
    -map "[a]" \
    -c:v libx264 \
    -crf "$crf" \
    -pix_fmt yuv420p \
    -c:a aac \
    -b:a "$audio_bitrate" \
    "${faststart_args[@]}" \
    "$output_file"
else
  ffmpeg \
    -y \
    -i "$input_file" \
    -i "$top_caption_file" \
    -i "$bottom_caption_file" \
    -filter_complex "${video_filter}" \
    -map "[v]" \
    -c:v libx264 \
    -crf "$crf" \
    -pix_fmt yuv420p \
    "${faststart_args[@]}" \
    "$output_file"
fi
