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

# pdfify — Convert Markdown to beautiful PDF via Docker
# Supports: images, mermaid diagrams, tables, code blocks, Obsidian callouts
# Usage: ./pdfify <file.md> [file2.md ...] [options]

VERSION="1.2.0"
IMAGE_NAME="pdfify"
GIST_ID="23f4514a1f0da1347d3f89926c23b68f"
GIST_RAW="https://gist.githubusercontent.com/jclement/${GIST_ID}/raw/pdfify.sh"
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"

# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'

# --- Portable SHA-256 (macOS has shasum, Linux often has sha256sum) ---
_sha256() { shasum -a 256 "$@" 2>/dev/null || sha256sum "$@"; }

info()    { echo -e "${BLUE}::${RESET} ${BOLD}$*${RESET}"; }
success() { echo -e "${GREEN}✓${RESET} $*"; }
warn()    { echo -e "${YELLOW}⚠${RESET} $*"; }
detail()  { echo -e "  ${DIM}→${RESET} $*"; }
header()  { echo -e "\n${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"; echo -e "${MAGENTA}  ${BOLD}$*${RESET}"; echo -e "${MAGENTA}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}\n"; }

# --- Self-update ---
do_update() {
    info "Updating pdfify from gist..."
    local raw_url tmp
    raw_url=$(curl -fsSL "https://api.github.com/gists/${GIST_ID}" 2>/dev/null | grep '"raw_url"' | head -1 | sed 's/.*"raw_url": *"//;s/".*//')
    [[ -z "$raw_url" ]] && raw_url="$GIST_RAW"
    tmp=$(mktemp)
    if curl -fsSL "$raw_url" -o "$tmp" 2>/dev/null; then
        if [[ -s "$tmp" ]] && head -1 "$tmp" | grep -q '^#!/'; then
            chmod +x "$tmp"
            mv "$tmp" "$SELF"
            success "Updated to latest version"
            detail "${CYAN}${SELF}${RESET}"
        else
            rm -f "$tmp"
            echo -e "${RED}Error:${RESET} Downloaded file doesn't look like a script"
            exit 1
        fi
    else
        rm -f "$tmp"
        echo -e "${RED}Error:${RESET} Failed to download update"
        exit 1
    fi
    exit 0
}

check_for_update() {
    local remote_hash local_hash raw_url
    raw_url=$(curl -fsSL --connect-timeout 2 --max-time 3 "https://api.github.com/gists/${GIST_ID}" 2>/dev/null | grep '"raw_url"' | head -1 | sed 's/.*"raw_url": *"//;s/".*//') || return 0
    [[ -z "$raw_url" ]] && return 0
    remote_hash=$(curl -fsSL --connect-timeout 2 --max-time 5 "$raw_url" 2>/dev/null | _sha256 | cut -d' ' -f1) || return 0
    local_hash=$(_sha256 < "$SELF" | cut -d' ' -f1)
    if [[ -n "$remote_hash" && "$remote_hash" != "$local_hash" ]]; then
        echo -e "${YELLOW}⚠${RESET} ${DIM}A newer version of pdfify is available. Run ${CYAN}pdfify --update${DIM} to upgrade.${RESET}"
    fi
}

# --- Args (CLI overrides frontmatter; "" means "use frontmatter default") ---
REBUILD=0
WATCH=0
OPEN=0
PREVIEW=0
OUT_FILE=""
NEXT_KEY=""
POSITIONAL=()

# CLI overrides — empty string means "not set, defer to frontmatter"
CLI_TOC_LEVEL=""
CLI_NUMBERS=""
CLI_NUMBER_FROM=""
CLI_TITLE=""
CLI_SUBTITLE=""
CLI_AUTHOR=""
CLI_HEADER=""
CLI_FOOTER=""
CLI_DATE=""
CLI_WATERMARK=""

for arg in "$@"; do
    if [[ -n "$NEXT_KEY" ]]; then
        case "$NEXT_KEY" in
            toc-level)    CLI_TOC_LEVEL="$arg" ;;
            number-from)  CLI_NUMBER_FROM="$arg" ;;
            out)          OUT_FILE="$arg" ;;
            title)        CLI_TITLE="$arg" ;;
            subtitle)     CLI_SUBTITLE="$arg" ;;
            author)       CLI_AUTHOR="$arg" ;;
            header)       CLI_HEADER="$arg" ;;
            footer)       CLI_FOOTER="$arg" ;;
            date)         CLI_DATE="$arg" ;;
            watermark)    CLI_WATERMARK="$arg" ;;
        esac
        NEXT_KEY=""
        continue
    fi
    case "$arg" in
        --rebuild) REBUILD=1 ;;
        --update) do_update ;;
        --watch) WATCH=1 ;;
        --open) OPEN=1 ;;
        --preview) PREVIEW=1; OPEN=1 ;;
        --no-numbers) CLI_NUMBERS="false" ;;
        --numbers) CLI_NUMBERS="true" ;;
        --clean) echo -e "${BLUE}::${RESET} ${BOLD}Removing Docker image ${CYAN}${IMAGE_NAME}${RESET}..."
                 docker rmi "$IMAGE_NAME" >/dev/null 2>&1 && echo -e "${GREEN}✓${RESET} Image removed" || echo -e "${DIM}Image not found${RESET}"
                 exit 0 ;;
        --toc-level)    NEXT_KEY="toc-level" ;;
        --toc-level=*)  CLI_TOC_LEVEL="${arg#*=}" ;;
        --number-from)  NEXT_KEY="number-from" ;;
        --number-from=*) CLI_NUMBER_FROM="${arg#*=}" ;;
        --out)          NEXT_KEY="out" ;;
        --out=*)        OUT_FILE="${arg#*=}" ;;
        --title)        NEXT_KEY="title" ;;
        --title=*)      CLI_TITLE="${arg#*=}" ;;
        --subtitle)     NEXT_KEY="subtitle" ;;
        --subtitle=*)   CLI_SUBTITLE="${arg#*=}" ;;
        --author)       NEXT_KEY="author" ;;
        --author=*)     CLI_AUTHOR="${arg#*=}" ;;
        --header)       NEXT_KEY="header" ;;
        --header=*)     CLI_HEADER="${arg#*=}" ;;
        --footer)       NEXT_KEY="footer" ;;
        --footer=*)     CLI_FOOTER="${arg#*=}" ;;
        --date)         NEXT_KEY="date" ;;
        --date=*)       CLI_DATE="${arg#*=}" ;;
        --watermark)    NEXT_KEY="watermark" ;;
        --watermark=*)  CLI_WATERMARK="${arg#*=}" ;;
        --version) echo "pdfify v${VERSION}"; exit 0 ;;
        --help|-h) echo -e "${BOLD}pdfify${RESET} v${VERSION} — Markdown to PDF"
                   echo ""
                   echo -e "${BOLD}Usage:${RESET} pdfify ${CYAN}<file.md> [file2.md ...]${RESET} [options]"
                   echo ""
                   echo -e "${BOLD}Options:${RESET}"
                   echo -e "  ${DIM}--out FILE${RESET}          Output file (single input only)"
                   echo -e "  ${DIM}--toc-level N${RESET}       TOC depth: 0=none, 1=H1, 2=H2, 3=H3 (default: 3)"
                   echo -e "  ${DIM}--numbers${RESET}            Enable numbered headings (default)"
                   echo -e "  ${DIM}--no-numbers${RESET}         Disable numbered headings"
                   echo -e "  ${DIM}--number-from N${RESET}     Start numbering at heading level N (default: 2)"
                   echo -e "  ${DIM}--open${RESET}               Open PDF after generation"
                   echo -e "  ${DIM}--preview${RESET}            Render to /tmp and open (no permanent file)"
                   echo -e "  ${DIM}--watch${RESET}              Watch for changes and regenerate"
                   echo -e "  ${DIM}--rebuild${RESET}            Force rebuild the Docker image"
                   echo -e "  ${DIM}--clean${RESET}              Remove the Docker image"
                   echo -e "  ${DIM}--update${RESET}             Update pdfify to latest version from gist"
                   echo -e "  ${DIM}--version${RESET}            Show version"
                   echo ""
                   echo -e "${BOLD}Overrides${RESET} (CLI trumps frontmatter):"
                   echo -e "  ${DIM}--title TEXT${RESET}         ${DIM}--subtitle TEXT${RESET}"
                   echo -e "  ${DIM}--author TEXT${RESET}        ${DIM}--header TEXT${RESET}"
                   echo -e "  ${DIM}--footer TEXT${RESET}        ${DIM}--date TEXT${RESET}"
                   echo -e "  ${DIM}--watermark TEXT${RESET}"
                   echo ""
                   echo -e "${BOLD}Frontmatter:${RESET}"
                   echo -e "  title, subtitle, author, header, footer, toc-level, date,"
                   echo -e "  numbersections (true/false), numberfrom (1-4), watermark"
                   exit 0 ;;
        *) POSITIONAL+=("$arg") ;;
    esac
done

if [[ ${#POSITIONAL[@]} -lt 1 ]]; then
    echo -e "${BOLD}Usage:${RESET} pdfify ${CYAN}<file.md> [file2.md ...]${RESET} [options]"
    echo -e "  Run ${CYAN}pdfify --help${RESET} for all options"
    exit 1
fi

if [[ -n "$OUT_FILE" && ${#POSITIONAL[@]} -gt 1 ]]; then
    echo -e "${RED}Error:${RESET} --out cannot be used with multiple input files"
    exit 1
fi

# --- Open helper ---
open_pdf() {
    local pdf="$1"
    if command -v open >/dev/null 2>&1; then
        open "$pdf"
    elif command -v xdg-open >/dev/null 2>&1; then
        xdg-open "$pdf"
    fi
}

header "pdfify v${VERSION}"

# --- Embedded Dockerfile ---
DOCKERFILE=$(cat <<'DOCKERFILE_END'
FROM node:20-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
        pandoc \
        texlive-latex-recommended \
        texlive-latex-extra \
        texlive-fonts-recommended \
        texlive-fonts-extra \
        texlive-xetex \
        lmodern \
        librsvg2-bin \
        chromium \
        ca-certificates \
        fonts-liberation \
        fonts-roboto \
        fonts-roboto-unhinted \
        fonts-noto-color-emoji \
        wget \
        fontconfig \
    && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /usr/share/fonts/truetype/roboto-mono && \
    for style in Regular Bold Italic BoldItalic Medium MediumItalic Light LightItalic; do \
        wget -q "https://github.com/googlefonts/RobotoMono/raw/main/fonts/ttf/RobotoMono-${style}.ttf" \
             -O "/usr/share/fonts/truetype/roboto-mono/RobotoMono-${style}.ttf" 2>/dev/null || true; \
    done && \
    fc-cache -f
RUN npm install -g @mermaid-js/mermaid-cli
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
ENV CHROME_PATH=/usr/bin/chromium
RUN echo '{"maxTextSize": 90000, "flowchart": {"useMaxWidth": true}, "theme": "default"}' > /opt/mermaid-config.json
RUN echo '{"args": ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu"]}' > /opt/puppeteer-config.json
WORKDIR /work
ENTRYPOINT ["/bin/bash"]
DOCKERFILE_END
)

# --- Build Docker image ---
echo ""
if [[ $REBUILD -eq 1 ]]; then
    info "Removing existing Docker image ${CYAN}${IMAGE_NAME}${RESET}..."
    docker rmi "$IMAGE_NAME" >/dev/null 2>&1 || true
    success "Image removed"
fi

if docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
    info "Docker image ${GREEN}${IMAGE_NAME}${RESET} found ${DIM}(cached)${RESET}"
    success "Reusing existing image"
else
    info "Building Docker image ${CYAN}${IMAGE_NAME}${RESET}..."
    detail "Installing: pandoc, XeLaTeX, mermaid-cli, Chromium, fonts"
    detail "This takes 2-3 minutes on first run (cached after)"
    echo ""
    BUILD_CTX=$(mktemp -d)
    echo "$DOCKERFILE" | DOCKER_BUILDKIT=0 docker build -t "$IMAGE_NAME" -f - "$BUILD_CTX" 2>&1 | while IFS= read -r line; do
        if [[ "$line" =~ ^Step\ ([0-9]+)/([0-9]+) ]]; then
            echo -e "  ${CYAN}[${BASH_REMATCH[1]}/${BASH_REMATCH[2]}]${RESET} ${DIM}${line#*: }${RESET}"
        elif [[ "$line" == *"Successfully tagged"* ]]; then
            echo -e "  ${GREEN}${line}${RESET}"
        elif [[ "$line" == *"ERROR"* || "$line" == *"error"* ]]; then
            echo -e "  ${RED}${line}${RESET}"
        fi
    done
    rm -rf "$BUILD_CTX"
    if ! docker image inspect "$IMAGE_NAME" >/dev/null 2>&1; then
        echo -e "\n${RED}Docker build failed. Re-running with full output:${RESET}\n"
        BUILD_CTX=$(mktemp -d)
        echo "$DOCKERFILE" | DOCKER_BUILDKIT=0 docker build -t "$IMAGE_NAME" -f - "$BUILD_CTX"
        rm -rf "$BUILD_CTX"
        exit 1
    fi
    success "Docker image built"
fi

# === Per-file conversion ===
convert_file() {
    local INPUT_PATH="$1"
    local OUTPUT_OVERRIDE="$2"

    # --- Resolve paths ---
    local INPUT INPUT_DIR INPUT_FILE OUTPUT OUTPUT_DIR OUTPUT_FILE
    INPUT="$(cd "$(dirname "$INPUT_PATH")" && pwd)/$(basename "$INPUT_PATH")"
    if [[ ! -f "$INPUT" ]]; then
        echo -e "${RED}Error:${RESET} $INPUT_PATH not found"
        return 1
    fi
    INPUT_DIR="$(dirname "$INPUT")"
    INPUT_FILE="$(basename "$INPUT")"
    OUTPUT="${OUTPUT_OVERRIDE:-${INPUT%.md}.pdf}"
    OUTPUT_DIR="$(cd "$(dirname "$OUTPUT")" 2>/dev/null && pwd || (mkdir -p "$(dirname "$OUTPUT")" && cd "$(dirname "$OUTPUT")" && pwd))"
    OUTPUT="${OUTPUT_DIR}/$(basename "$OUTPUT")"
    OUTPUT_FILE="$(basename "$OUTPUT")"

    # Preview mode: write temp file in input dir (Docker-mountable), move to /tmp after
    local PREVIEW_FINAL=""
    if [[ $PREVIEW -eq 1 ]]; then
        local base="${INPUT_FILE%.md}"
        PREVIEW_FINAL="/tmp/pdfify-preview-${base}.pdf"
        OUTPUT_FILE=".pdfify-preview-${base}.pdf"
        OUTPUT="${OUTPUT_DIR}/${OUTPUT_FILE}"
    fi

    info "Input:  ${CYAN}${INPUT}${RESET}"
    if [[ -n "$PREVIEW_FINAL" ]]; then
        info "Output: ${CYAN}${PREVIEW_FINAL}${RESET} ${DIM}(preview)${RESET}"
    else
        info "Output: ${CYAN}${OUTPUT}${RESET}"
    fi

    # --- Parse YAML frontmatter ---
    local FM_TITLE="" FM_SUBTITLE="" FM_AUTHOR="" FM_FOOTER="" FM_HEADER=""
    local FM_TOC_LEVEL="" FM_DATE="" FM_NUMBERSECTIONS="" FM_NUMBERFROM="" FM_WATERMARK=""
    local FM_DATE_HASH="" FM_DATE_DIRTY="" FM_DATE_LABEL=""

    if head -1 "$INPUT" | grep -q '^---'; then
        local FM_BLOCK
        FM_BLOCK=$(awk 'NR==1 && /^---/{found=1; next} found && /^---/{exit} found{print}' "$INPUT")
        extract_fm() { echo "$FM_BLOCK" | sed -n "s/^$1:[[:space:]]*//p" | sed 's/^["'"'"']\(.*\)["'"'"']$/\1/'; }
        FM_TITLE=$(extract_fm "title")
        FM_AUTHOR=$(extract_fm "author")
        FM_SUBTITLE=$(extract_fm "subtitle")
        FM_FOOTER=$(extract_fm "footer")
        FM_HEADER=$(extract_fm "header")
        FM_TOC_LEVEL=$(extract_fm "toc-level")
        FM_DATE=$(extract_fm "date")
        FM_NUMBERSECTIONS=$(extract_fm "numbersections")
        FM_NUMBERFROM=$(extract_fm "numberfrom")
        FM_WATERMARK=$(extract_fm "watermark")
    fi

    # --- CLI overrides frontmatter ---
    [[ -n "$CLI_TITLE" ]]       && FM_TITLE="$CLI_TITLE"
    [[ -n "$CLI_SUBTITLE" ]]    && FM_SUBTITLE="$CLI_SUBTITLE"
    [[ -n "$CLI_AUTHOR" ]]      && FM_AUTHOR="$CLI_AUTHOR"
    [[ -n "$CLI_FOOTER" ]]      && FM_FOOTER="$CLI_FOOTER"
    [[ -n "$CLI_HEADER" ]]      && FM_HEADER="$CLI_HEADER"
    [[ -n "$CLI_DATE" ]]        && FM_DATE="$CLI_DATE"
    [[ -n "$CLI_WATERMARK" ]]   && FM_WATERMARK="$CLI_WATERMARK"
    [[ -n "$CLI_TOC_LEVEL" ]]   && FM_TOC_LEVEL="$CLI_TOC_LEVEL"
    [[ -n "$CLI_NUMBER_FROM" ]] && FM_NUMBERFROM="$CLI_NUMBER_FROM"
    [[ -n "$CLI_NUMBERS" ]]     && FM_NUMBERSECTIONS="$CLI_NUMBERS"

    # --- Auto-detect document structure ---
    # Count H1 headings (outside code blocks)
    local H1_COUNT=0 IN_CODE_SCAN=0 FIRST_H1_TEXT=""
    while IFS= read -r scanline || [[ -n "$scanline" ]]; do
        [[ "$scanline" =~ ^\`\`\` ]] && { if [[ $IN_CODE_SCAN -eq 0 ]]; then IN_CODE_SCAN=1; else IN_CODE_SCAN=0; fi; continue; }
        if [[ $IN_CODE_SCAN -eq 0 && "$scanline" =~ ^#\  ]]; then
            H1_COUNT=$((H1_COUNT + 1))
            [[ $H1_COUNT -eq 1 ]] && FIRST_H1_TEXT="${scanline#\# }"
        fi
    done < "$INPUT"

    local FILE_TOC_LEVEL="${FM_TOC_LEVEL:-3}"
    local FILE_NUMBERS=1
    [[ "$FM_NUMBERSECTIONS" == "false" ]] && FILE_NUMBERS=0

    # Auto-determine numberfrom based on structure (if not explicitly set)
    local FILE_NUMBER_FROM="${FM_NUMBERFROM:-}"
    local HIDE_FIRST_H1=0
    if [[ -z "$FILE_NUMBER_FROM" ]]; then
        if [[ $H1_COUNT -eq 1 ]]; then
            # Single H1 = document title; number from H2, hide H1 in body
            FILE_NUMBER_FROM=2
            HIDE_FIRST_H1=1
            # Use H1 text as title if no title set
            [[ -z "$FM_TITLE" ]] && FM_TITLE="$FIRST_H1_TEXT"
            detail "Auto:     ${DIM}single H1 detected → using as title, numbering from H2${RESET}"
        else
            # Multiple H1s = sections; number from H1
            FILE_NUMBER_FROM=1
            detail "Auto:     ${DIM}${H1_COUNT} H1s detected → numbering from H1${RESET}"
        fi
    fi
    # Default date: current date/time
    # Set to "none" in frontmatter or --date to suppress
    FM_DATE_HASH="${FM_DATE_HASH:-}"
    FM_DATE_DIRTY="${FM_DATE_DIRTY:-}"
    if [[ "$FM_DATE" == "none" || "$FM_DATE" == "false" ]]; then
        FM_DATE=""
    elif [[ -z "$FM_DATE" && -z "$CLI_DATE" ]]; then
        FM_DATE="$(date +"%Y-%m-%d %H:%M")"
    fi

    echo ""
    [[ -n "$FM_TITLE" ]]     && detail "Title:       ${CYAN}${FM_TITLE}${RESET}"
    [[ -n "$FM_SUBTITLE" ]]  && detail "Subtitle:    ${CYAN}${FM_SUBTITLE}${RESET}"
    [[ -n "$FM_AUTHOR" ]]    && detail "Author:      ${CYAN}${FM_AUTHOR}${RESET}"
    [[ -n "$FM_HEADER" ]]    && detail "Header:      ${CYAN}${FM_HEADER}${RESET}"
    [[ -n "$FM_FOOTER" ]]    && detail "Footer:      ${CYAN}${FM_FOOTER}${RESET}"
    detail "Date:        ${CYAN}${FM_DATE}${RESET}"
    detail "TOC:         ${CYAN}level ${FILE_TOC_LEVEL}${RESET}"
    detail "Numbered:    ${CYAN}$([ $FILE_NUMBERS -eq 1 ] && echo "yes (from H${FILE_NUMBER_FROM})" || echo no)${RESET}"
    [[ -n "$FM_WATERMARK" ]] && detail "Watermark:   ${CYAN}${FM_WATERMARK}${RESET}"

    # --- Git hash for source file ---
    local GIT_STAMP=""

# --- Discover images referenced in the markdown ---
echo ""
info "Scanning ${CYAN}${INPUT_FILE}${RESET} for assets..."

IMAGES=()
while IFS= read -r img; do
    [[ -z "$img" ]] && continue
    [[ "$img" =~ ^https?:// ]] && continue
    if [[ -f "$INPUT_DIR/$img" ]]; then
        IMAGES+=("$img")
        success "Image: ${CYAN}${img}${RESET} ${DIM}($(du -h "$INPUT_DIR/$img" | cut -f1 | tr -d ' '))${RESET}"
    else
        warn "Image: ${YELLOW}${img}${RESET} ${RED}(not found)${RESET}"
    fi
done < <(sed -n 's/.*!\[[^]]*\](\([^)]*\)).*/\1/p' "$INPUT"; sed -n 's/.*src="\([^"]*\)".*/\1/p' "$INPUT")

MERMAID_COUNT=$(grep -c '```mermaid' "$INPUT" || true)
if [[ $MERMAID_COUNT -gt 0 ]]; then
    success "Mermaid diagrams: ${CYAN}${MERMAID_COUNT}${RESET}"
fi

CALLOUT_COUNT=$(grep -c '> \[!' "$INPUT" || true)
if [[ $CALLOUT_COUNT -gt 0 ]]; then
    success "Callouts: ${CYAN}${CALLOUT_COUNT}${RESET}"
fi

TABLE_COUNT=$(grep -c '^|' "$INPUT" || true)
CODE_COUNT=$(grep -c '```' "$INPUT" || true)
CODE_COUNT=$(( (CODE_COUNT - MERMAID_COUNT * 2) / 2 ))
[[ $TABLE_COUNT -gt 0 ]] && detail "Tables: ${TABLE_COUNT} rows"
[[ $CODE_COUNT -gt 0 ]] && detail "Code blocks: ~${CODE_COUNT}"

echo ""
info "Found ${GREEN}${#IMAGES[@]}${RESET} image(s), ${GREEN}${MERMAID_COUNT}${RESET} mermaid diagram(s), ${GREEN}${CALLOUT_COUNT}${RESET} callout(s)"

# --- Write the conversion script to a temp file (mounted into Docker) ---
CONVERT_SCRIPT="${INPUT_DIR}/.pdfify-convert-$$.sh"
trap 'rm -f "$CONVERT_SCRIPT"' EXIT
cat > "$CONVERT_SCRIPT" <<'INNER_SCRIPT'
#!/bin/bash
set -euo pipefail

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'

info()    { echo -e "${BLUE}::${RESET} ${BOLD}$*${RESET}"; }
success() { echo -e "${GREEN}✓${RESET} $*"; }
detail()  { echo -e "  ${DIM}→${RESET} $*"; }

INPUT_FILE="$1"
OUTPUT_FILE="$2"
WORKDIR="/work"

cd "$WORKDIR"

# --- Step 0: Strip first H1 if it's being used as document title ---
HIDE_FIRST_H1="${HIDE_FIRST_H1:-0}"
EFFECTIVE_INPUT="$INPUT_FILE"
if [[ "$HIDE_FIRST_H1" == "1" ]]; then
    STRIPPED=$(mktemp /tmp/pdfify-stripped-XXXXXX.md)
    FOUND_H1=0
    IN_CODE_BLK=0
    IN_FMATTER=0
    while IFS= read -r line || [[ -n "$line" ]]; do
        [[ "$line" =~ ^\`\`\` ]] && { if [[ $IN_CODE_BLK -eq 0 ]]; then IN_CODE_BLK=1; else IN_CODE_BLK=0; fi; }
        if [[ "$line" == "---" && $IN_CODE_BLK -eq 0 ]]; then
            if [[ $IN_FMATTER -eq 0 && $FOUND_H1 -eq 0 ]]; then IN_FMATTER=1; else IN_FMATTER=0; fi
        fi
        # Skip the first H1 (and any blank line immediately after)
        if [[ $FOUND_H1 -eq 0 && $IN_CODE_BLK -eq 0 && $IN_FMATTER -eq 0 && "$line" =~ ^#\  ]]; then
            FOUND_H1=1
            continue
        fi
        # Skip blank line right after removed H1
        if [[ $FOUND_H1 -eq 1 && -z "$line" ]]; then
            FOUND_H1=2
            continue
        fi
        [[ $FOUND_H1 -eq 1 ]] && FOUND_H1=2
        echo "$line" >> "$STRIPPED"
    done < "$INPUT_FILE"
    EFFECTIVE_INPUT="$(basename "$STRIPPED")"
    detail "Stripped first H1 (promoted to title)"
fi

# --- Step 1: Pre-process Obsidian callouts ---
info "Pre-processing callouts..."

CALLOUT_MD=$(mktemp /tmp/pdfify-callout-XXXXXX.md)
IN_CALLOUT=0
CALLOUT_TYPE=""
CALLOUT_TITLE=""
CALLOUT_BUF=""
CALLOUT_COUNT=0

flush_callout() {
    if [[ $IN_CALLOUT -eq 1 && -n "$CALLOUT_TYPE" ]]; then
        CALLOUT_COUNT=$((CALLOUT_COUNT + 1))
        local latex_type
        case "${CALLOUT_TYPE,,}" in
            info|note)       latex_type="calloutinfo" ;;
            tip|hint)        latex_type="callouttip" ;;
            warning|caution) latex_type="calloutwarning" ;;
            danger|error|bug) latex_type="calloutdanger" ;;
            example)         latex_type="calloutexample" ;;
            quote|cite)      latex_type="calloutquote" ;;
            *)               latex_type="calloutinfo" ;;
        esac
        echo "" >> "$CALLOUT_MD"
        echo '```{=latex}' >> "$CALLOUT_MD"
        echo "\\begin{${latex_type}}{${CALLOUT_TITLE}}" >> "$CALLOUT_MD"
        echo '```' >> "$CALLOUT_MD"
        echo "" >> "$CALLOUT_MD"
        echo "$CALLOUT_BUF" >> "$CALLOUT_MD"
        echo "" >> "$CALLOUT_MD"
        echo '```{=latex}' >> "$CALLOUT_MD"
        echo "\\end{${latex_type}}" >> "$CALLOUT_MD"
        echo '```' >> "$CALLOUT_MD"
        echo "" >> "$CALLOUT_MD"
    fi
    IN_CALLOUT=0
    CALLOUT_TYPE=""
    CALLOUT_TITLE=""
    CALLOUT_BUF=""
}

while IFS= read -r line || [[ -n "$line" ]]; do
    if [[ "$line" =~ ^\>\ *\[!([a-zA-Z]+)\]\ *(.*) ]]; then
        flush_callout
        IN_CALLOUT=1
        CALLOUT_TYPE="${BASH_REMATCH[1]}"
        CALLOUT_TITLE="${BASH_REMATCH[2]:-${BASH_REMATCH[1]^}}"
        continue
    fi

    if [[ $IN_CALLOUT -eq 1 ]]; then
        if [[ "$line" =~ ^\>\ ?(.*) ]]; then
            CALLOUT_BUF="${CALLOUT_BUF}${BASH_REMATCH[1]}
"
            continue
        else
            flush_callout
        fi
    fi

    echo "$line" >> "$CALLOUT_MD"
done < "${STRIPPED:-$INPUT_FILE}"
flush_callout

if [[ $CALLOUT_COUNT -gt 0 ]]; then
    success "Converted $CALLOUT_COUNT callout(s)"
fi

# --- Step 1b+1c: Inject page breaks (after TOC, before each H1) ---
BREAK_INJECTED=$(mktemp /tmp/pdfify-breaks-XXXXXX.md)
H1_COUNT=0
IN_FM=0
IN_CODE=0
DONE_TOC_BREAK=0
while IFS= read -r line || [[ -n "$line" ]]; do
    # Track code blocks (``` opens/closes)
    if [[ "$line" =~ ^\`\`\` ]]; then
        if [[ $IN_CODE -eq 0 ]]; then IN_CODE=1; else IN_CODE=0; fi
        echo "$line" >> "$BREAK_INJECTED"
        continue
    fi

    # Track frontmatter (only at start of file)
    if [[ "$line" == "---" && $IN_CODE -eq 0 ]]; then
        if [[ $IN_FM -eq 0 && $H1_COUNT -eq 0 ]]; then IN_FM=1; else IN_FM=0; fi
        echo "$line" >> "$BREAK_INJECTED"
        continue
    fi

    if [[ $IN_CODE -eq 0 && $IN_FM -eq 0 ]]; then
        # Before first content after frontmatter: inject TOC page break
        if [[ $DONE_TOC_BREAK -eq 0 && "$TOC_LEVEL" -gt 0 && -n "$line" ]]; then
            echo "" >> "$BREAK_INJECTED"
            echo '```{=latex}' >> "$BREAK_INJECTED"
            echo '\newpage' >> "$BREAK_INJECTED"
            echo '```' >> "$BREAK_INJECTED"
            echo "" >> "$BREAK_INJECTED"
            DONE_TOC_BREAK=1
        fi

        # Page break before each top-level section (except first)
        # Build the marker: numberfrom=1 → "# ", numberfrom=2 → "## "
        BREAK_HASHES=$(printf '#%.0s' $(seq 1 "$FILE_NUMBER_FROM"))
        if [[ "$line" == "${BREAK_HASHES} "* ]]; then
            # Make sure it's exactly that level, not deeper
            NEXT_CHAR="${line:${#BREAK_HASHES}:1}"
            if [[ "$NEXT_CHAR" != "#" ]]; then
                H1_COUNT=$((H1_COUNT + 1))
                if [[ $H1_COUNT -gt 1 ]]; then
                    echo "" >> "$BREAK_INJECTED"
                    echo '```{=latex}' >> "$BREAK_INJECTED"
                    echo '\newpage' >> "$BREAK_INJECTED"
                    echo '```' >> "$BREAK_INJECTED"
                    echo "" >> "$BREAK_INJECTED"
                fi
            fi
        fi
    fi

    echo "$line" >> "$BREAK_INJECTED"
done < "$CALLOUT_MD"
rm -f "$CALLOUT_MD"
CALLOUT_MD="$BREAK_INJECTED"

# --- Step 2: Pre-render Mermaid blocks to PNG ---
info "Pre-rendering Mermaid diagrams..."

TEMP_MD=$(mktemp /tmp/pdfify-XXXXXX.md)
MERMAID_COUNT=0
IN_MERMAID=0
MERMAID_BUF=""

while IFS= read -r line || [[ -n "$line" ]]; do
    if [[ "$line" =~ ^\`\`\`mermaid ]]; then
        IN_MERMAID=1
        MERMAID_BUF=""
        continue
    fi

    if [[ $IN_MERMAID -eq 1 ]]; then
        if [[ "$line" =~ ^\`\`\` ]]; then
            IN_MERMAID=0
            MERMAID_COUNT=$((MERMAID_COUNT + 1))
            MERMAID_FILE="/tmp/mermaid-${MERMAID_COUNT}.mmd"
            MERMAID_PNG="/tmp/mermaid-${MERMAID_COUNT}.png"

            echo "$MERMAID_BUF" > "$MERMAID_FILE"

            detail "Rendering diagram ${CYAN}#${MERMAID_COUNT}${RESET}..."
            mmdc -i "$MERMAID_FILE" \
                 -o "$MERMAID_PNG" \
                 -w 1600 \
                 -b transparent \
                 -c /opt/mermaid-config.json \
                 -p /opt/puppeteer-config.json \
                 2>/dev/null || {
                echo -e "  ${YELLOW}⚠${RESET} Diagram $MERMAID_COUNT failed — inserting as code block"
                echo '```' >> "$TEMP_MD"
                echo "$MERMAID_BUF" >> "$TEMP_MD"
                echo '```' >> "$TEMP_MD"
                continue
            }

            SIZE=$(du -h "$MERMAID_PNG" 2>/dev/null | cut -f1 | tr -d ' ')
            success "Diagram #${MERMAID_COUNT} rendered ${DIM}(${SIZE})${RESET}"

            echo "" >> "$TEMP_MD"
            echo "![Diagram ${MERMAID_COUNT}](${MERMAID_PNG})\\" >> "$TEMP_MD"
            echo "" >> "$TEMP_MD"
        else
            MERMAID_BUF="${MERMAID_BUF}${line}
"
        fi
    else
        echo "$line" >> "$TEMP_MD"
    fi
done < "$CALLOUT_MD"

# --- Lua filter: protect brackets in headings for titlesec ---
# Square brackets in headings break titlesec (\SQSPL@scan error) because LaTeX
# interprets [ as the start of an optional argument.
BRACKET_FILTER=$(mktemp /tmp/pdfify-bracket-filter-XXXXXX.lua)
cat > "$BRACKET_FILTER" <<'LUAFILTER'
-- Protect square brackets in headings to prevent titlesec \SQSPL@scan errors.
-- Brackets in headings make titlesec think they are optional arguments.
-- We replace [ and ] with \lbrack/\rbrack in all inline types.

function Header(el)
  if FORMAT ~= "latex" and FORMAT ~= "pdf" then return nil end

  el = el:walk {
    Str = function(s)
      if s.text:find("[%[%]]") then
        local t = s.text:gsub("%[", "\\lbrack{}"):gsub("%]", "\\rbrack{}")
        return pandoc.RawInline("latex", t)
      end
    end,
    Code = function(c)
      -- All code in headings must use \oldtexttt to bypass seqsplit
      -- (seqsplit in titlesec moving arguments causes \SQSPL@scan errors)
      local t = c.text
      t = t:gsub("\\", "\\textbackslash ")
      t = t:gsub("%%", "\\%%")
      t = t:gsub("%#", "\\#")
      t = t:gsub("%$", "\\$")
      t = t:gsub("%&", "\\&")
      t = t:gsub("_", "\\_")
      t = t:gsub("%{", "\\{")
      t = t:gsub("%}", "\\}")
      t = t:gsub("~", "\\textasciitilde{}")
      t = t:gsub("%^", "\\textasciicircum{}")
      t = t:gsub("%[", "\\lbrack{}"):gsub("%]", "\\rbrack{}")
      return pandoc.RawInline("latex", "\\oldtexttt{" .. t .. "}")
    end
  }
  return el
end
LUAFILTER

echo ""
info "Generating PDF with Pandoc + XeLaTeX..."
detail "Engine: xelatex"
detail "Font: Roboto / Roboto Mono"
detail "Margins: 0.5in, Font size: 10pt"
echo ""

# Write LaTeX preamble for modern styling
PREAMBLE=$(mktemp /tmp/pdfify-preamble-XXXXXX.tex)
cat > "$PREAMBLE" <<'LATEX'
% --- Modern color scheme ---
\usepackage{xcolor}
\definecolor{accent}{HTML}{374151}
\definecolor{accentdark}{HTML}{111827}
\definecolor{codebg}{HTML}{F8F9FA}
\definecolor{codeborder}{HTML}{E2E8F0}
\definecolor{headrulecolor}{HTML}{E2E8F0}

% --- Callout colors ---
\definecolor{infobg}{HTML}{EFF6FF}
\definecolor{infobar}{HTML}{3B82F6}
\definecolor{infofg}{HTML}{1E40AF}
\definecolor{tipbg}{HTML}{F0FDF4}
\definecolor{tipbar}{HTML}{22C55E}
\definecolor{tipfg}{HTML}{166534}
\definecolor{warningbg}{HTML}{FFFBEB}
\definecolor{warningbar}{HTML}{F59E0B}
\definecolor{warningfg}{HTML}{92400E}
\definecolor{dangerbg}{HTML}{FEF2F2}
\definecolor{dangerbar}{HTML}{EF4444}
\definecolor{dangerfg}{HTML}{991B1B}
\definecolor{examplebg}{HTML}{F5F3FF}
\definecolor{examplebar}{HTML}{8B5CF6}
\definecolor{examplefg}{HTML}{5B21B6}
\definecolor{quotecallbg}{HTML}{F8F9FA}
\definecolor{quotecallbar}{HTML}{6B7280}
\definecolor{quotecallfg}{HTML}{374151}

% --- Code block wrapping and styling ---
\usepackage{fvextra}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{
  breaklines,
  breakanywhere,
  commandchars=\\\{\},
  fontsize=\small
}

% Background on code blocks via mdframed
\usepackage[framemethod=tikz]{mdframed}

% Override pandoc's Shaded environment
\renewenvironment{Shaded}{%
  \begin{mdframed}[
    backgroundcolor=codebg,
    hidealllines=true,
    roundcorner=4pt,
    innertopmargin=8pt,
    innerbottommargin=8pt,
    innerleftmargin=10pt,
    innerrightmargin=10pt,
    skipabove=10pt,
    skipbelow=10pt
  ]
}{%
  \end{mdframed}
}

% --- Callout environments ---
\newenvironment{calloutbase}[3]{%
  \begin{mdframed}[
    backgroundcolor=#1,
    linecolor=#2,
    linewidth=3pt,
    topline=false,
    bottomline=false,
    rightline=false,
    innertopmargin=12pt,
    innerbottommargin=12pt,
    innerleftmargin=12pt,
    innerrightmargin=12pt,
    skipabove=12pt,
    skipbelow=12pt,
    roundcorner=0pt
  ]
  \textbf{\color{#2}#3}\par\smallskip\setlength{\parindent}{0pt}
}{%
  \end{mdframed}
}

\newenvironment{calloutinfo}[1]{\begin{calloutbase}{infobg}{infobar}{#1}}{\end{calloutbase}}
\newenvironment{callouttip}[1]{\begin{calloutbase}{tipbg}{tipbar}{#1}}{\end{calloutbase}}
\newenvironment{calloutwarning}[1]{\begin{calloutbase}{warningbg}{warningbar}{#1}}{\end{calloutbase}}
\newenvironment{calloutdanger}[1]{\begin{calloutbase}{dangerbg}{dangerbar}{#1}}{\end{calloutbase}}
\newenvironment{calloutexample}[1]{\begin{calloutbase}{examplebg}{examplebar}{#1}}{\end{calloutbase}}
\newenvironment{calloutquote}[1]{\begin{calloutbase}{quotecallbg}{quotecallbar}{#1}}{\end{calloutbase}}

% --- PDF bookmarks (sidebar navigation in PDF viewers) ---
\usepackage{bookmark}
\bookmarksetup{
  numbered=false,
  open,
  openlevel=2
}

% --- Title banner ---
\definecolor{titlebg}{HTML}{E5E7EB}

% --- Page break after TOC ---
\let\oldtableofcontents\tableofcontents
\renewcommand{\tableofcontents}{\oldtableofcontents\clearpage}

% --- TOC styling ---
\usepackage{tocloft}
\setlength{\cftbeforetoctitleskip}{0.5em}
\renewcommand{\cfttoctitlefont}{\LARGE\bfseries\color{accentdark}\scshape}
\renewcommand{\cftaftertoctitle}{\par\vspace{2pt}{\color{headrulecolor}\hrule height 1pt}\vspace{10pt}}
\renewcommand{\cftsecfont}{\bfseries\color{accentdark}}
\renewcommand{\cftsecpagefont}{\bfseries\color{accentdark}}
\renewcommand{\cftsubsecfont}{\color{accent}}
\renewcommand{\cftsubsecpagefont}{\color{accent}}
\renewcommand{\cftsubsubsecfont}{\small\color{accent}}
\renewcommand{\cftsubsubsecpagefont}{\small\color{accent}}
\renewcommand{\cftsecleader}{\cftdotfill{\cftsecdotsep}}
\renewcommand{\cftsecdotsep}{\cftdotsep}
\setlength{\cftbeforesecskip}{6pt}
\setlength{\cftbeforesubsecskip}{2pt}

% --- Heading font ---
\newfontfamily\headingfont{Roboto}[BoldFont={Roboto Bold}]

% --- Symbol fallback (arrows, etc.) ---
\usepackage{newunicodechar}
\newfontfamily\fallbackfont{Liberation Sans}[Scale=MatchLowercase]
\newunicodechar{→}{{\fallbackfont →}}
\newunicodechar{←}{{\fallbackfont ←}}
\newunicodechar{↔}{{\fallbackfont ↔}}
\newunicodechar{⇒}{{\fallbackfont ⇒}}
\newunicodechar{⇐}{{\fallbackfont ⇐}}
\newunicodechar{✓}{{\fallbackfont ✓}}
\newunicodechar{✗}{{\fallbackfont ✗}}

% --- Modern section headings (tight, bold, dark) ---
\usepackage{titlesec}

% H1: # headings — large, small caps, dark, with rule
\titleformat{\section}
  {\LARGE\headingfont\bfseries\color{accentdark}\addfontfeatures{LetterSpace=5}\scshape}
  {\thesection}{0.5em}{}[\vspace{2pt}{\color{headrulecolor}\titlerule[1pt]}]
\titlespacing*{\section}{0pt}{20pt}{10pt}

% H2: ## headings
\titleformat{\subsection}
  {\Large\headingfont\bfseries\color{accentdark}\addfontfeatures{LetterSpace=-1}}
  {\thesubsection}{0.5em}{}
\titlespacing*{\subsection}{0pt}{16pt}{8pt}

% H3: ### headings
\titleformat{\subsubsection}
  {\large\bfseries\color{accent}}
  {\thesubsubsection}{0.5em}{}
\titlespacing*{\subsubsection}{0pt}{12pt}{6pt}

% H4: #### headings
\titleformat{\paragraph}[hang]
  {\normalsize\bfseries\color{accent}}
  {\theparagraph}{0.5em}{}
\titlespacing*{\paragraph}{0pt}{10pt}{4pt}

%%SECNUMDEPTH_PLACEHOLDER%%

% --- Page style (header/footer injected by pdfify) ---
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\setlength{\headheight}{14pt}
%%HEADER_PLACEHOLDER%%
%%FOOTER_PLACEHOLDER%%
% Make plain style identical to fancy (so title/TOC pages get the same footer)
\fancypagestyle{plain}{\fancyhf{}\renewcommand{\headrulewidth}{0pt}\renewcommand{\footrulewidth}{0pt}%%FOOTER_PLAIN%%}

% --- Blockquote styling (plain > quotes, not callouts) ---
\usepackage{etoolbox}
\renewenvironment{quote}{%
  \begin{mdframed}[
    backgroundcolor=infobg,
    linecolor=infobar,
    linewidth=3pt,
    topline=false,
    bottomline=false,
    rightline=false,
    innertopmargin=12pt,
    innerbottommargin=12pt,
    innerleftmargin=12pt,
    innerrightmargin=12pt,
    skipabove=10pt,
    skipbelow=10pt,
    roundcorner=0pt
  ]%
}{%
  \end{mdframed}%
}

% --- Table styling ---
\usepackage{booktabs}
\usepackage{colortbl}
\usepackage{longtable}
\usepackage{tabularx}
\arrayrulecolor{codeborder}

% Allow line breaks in table cells and shrink monospace to fit
\usepackage{array}
\renewcommand{\arraystretch}{1.4}
\let\oldtexttt\texttt
\renewcommand{\texttt}[1]{{\small\oldtexttt{\seqsplit{#1}}}}
\usepackage{seqsplit}
\setlength{\tabcolsep}{4pt}

% --- Images constrained to page ---
\usepackage{grffile}
\usepackage[export]{adjustbox}
\let\oldincludegraphics\includegraphics
\renewcommand{\includegraphics}[2][]{%
  \oldincludegraphics[max width=\textwidth,max height=0.45\textheight,keepaspectratio,#1]{#2}%
}

% --- Figures don't float ---
\usepackage{float}
\floatplacement{figure}{H}

% --- Caption styling ---
\usepackage{caption}
\captionsetup{labelformat=empty,font={small,color=gray},skip=4pt}

% --- Tighter lists ---
\usepackage{enumitem}
\setlist{nosep,leftmargin=1.5em}

% --- Links ---
\usepackage{hyperref}
\hypersetup{
  colorlinks=true,
  linkcolor=accent,
  urlcolor=accent,
  citecolor=accent
}

% --- Horizontal rules ---
\renewcommand{\rule}[2]{\textcolor{headrulecolor}{\vrule width \textwidth height 0.5pt}}
LATEX

TOC_LEVEL="${TOC_LEVEL:-3}"
FM_FOOTER="${FM_FOOTER:-}"
FM_HEADER="${FM_HEADER:-}"
FM_AUTHOR="${FM_AUTHOR:-}"
FM_DATE="${FM_DATE:-}"
FM_DATE_LABEL="${FM_DATE_LABEL:-}"
FM_DATE_HASH="${FM_DATE_HASH:-}"
FM_DATE_DIRTY="${FM_DATE_DIRTY:-}"
FILE_NUMBERS="${FILE_NUMBERS:-1}"
FILE_NUMBER_FROM="${FILE_NUMBER_FROM:-2}"

# Escape LaTeX special characters in text fields (uses sed to avoid
# bash parameter substitution brace-parsing issues with } in replacements)
latex_escape() {
    printf '%s' "$1" | sed \
        -e 's/\\/@@BSLASH@@/g' \
        -e 's/&/\\&/g' \
        -e 's/%/\\%/g' \
        -e 's/\$/\\$/g' \
        -e 's/#/\\#/g' \
        -e 's/_/\\_/g' \
        -e 's/{/\\{/g' \
        -e 's/}/\\}/g' \
        -e 's/~/\\textasciitilde{}/g' \
        -e 's/\^/\\textasciicircum{}/g' \
        -e 's/@@BSLASH@@/\\textbackslash{}/g'
}

# Inject title banner into preamble
FM_TITLE="${FM_TITLE:-}"
FM_TITLE_TEX="$(latex_escape "$FM_TITLE")"
FM_SUBTITLE_TEX="$(latex_escape "${FM_SUBTITLE:-}")"
FM_AUTHOR_TEX="$(latex_escape "${FM_AUTHOR:-}")"

{
if [[ -n "$FM_TITLE" ]]; then
    cat <<'TITLE_STATIC'
\makeatletter
\renewcommand{\maketitle}{%
  \thispagestyle{fancy}%
  \vspace*{-\topskip}%
  \vspace*{-\headsep}%
  \vspace*{-\headheight}%
  \vspace*{-0.55in}%
  \noindent\hspace*{-0.5in}%
  \fcolorbox{titlebg}{titlebg}{%
    \parbox{\dimexpr\paperwidth-2\fboxsep-2\fboxrule}{%
      \hspace*{0.3in}\begin{minipage}{\dimexpr\textwidth}%
        \vspace{20pt}%
TITLE_STATIC

    echo "        {\\fontsize{28}{34}\\selectfont\\bfseries\\color{black}${FM_TITLE_TEX}}\\\\[6pt]%"

    FM_SUBTITLE="${FM_SUBTITLE:-}"
    if [[ -n "$FM_SUBTITLE" ]]; then
        echo "        {\\fontsize{14}{18}\\selectfont\\color{black}${FM_SUBTITLE_TEX}}\\\\[8pt]%"
    fi

    if [[ -n "$FM_AUTHOR" ]]; then
        echo "        {\\fontsize{11}{14}\\selectfont\\color{black}${FM_AUTHOR_TEX}}\\\\[6pt]%"
    fi

    if [[ -n "$FM_DATE" ]]; then
        DATE_VAL=""
        if [[ -n "$FM_DATE_HASH" ]]; then
            DATE_VAL="${FM_DATE% · *} · {\\texttt{${FM_DATE_HASH}}}"
        else
            DATE_VAL="${FM_DATE}"
        fi
        DIRTY_PART=""
        if [[ -n "${FM_DATE_DIRTY:-}" ]]; then
            DIRTY_PART=" {\\color{gray}\\itshape (dirty)}"
        fi
        if [[ -n "$FM_DATE_LABEL" ]]; then
            echo "        {\\fontsize{10}{12}\\selectfont\\color{black}${DATE_VAL} {\\color{gray}--- ${FM_DATE_LABEL}}${DIRTY_PART}}\\\\[4pt]%"
        else
            echo "        {\\fontsize{10}{12}\\selectfont\\color{black}${DATE_VAL}${DIRTY_PART}}\\\\[4pt]%"
        fi
    fi

    cat <<'TITLE_END'
        \vspace{6pt}%
      \end{minipage}%
    }%
  }%
  \par\vspace{20pt}%
}
\makeatother
TITLE_END
    echo '\AtBeginDocument{\maketitle}'
else
    echo '\renewcommand{\maketitle}{}'
fi
} >> "$PREAMBLE"

# Inject header/footer into preamble
GIT_STAMP="${GIT_STAMP:-}"

FOOTER_L=""
FOOTER_C=""
FOOTER_R="\\\\fancyfoot[R]{\\\\color{gray}\\\\small Page \\\\thepage\\\\ of \\\\pageref*{LastPage}}"

[[ -n "$FM_FOOTER" ]] && FOOTER_L="\\\\fancyfoot[L]{\\\\color{gray}\\\\small ${FM_FOOTER}}"

sed -i "s|%%FOOTER_PLACEHOLDER%%|\\\\usepackage{lastpage}${FOOTER_L}${FOOTER_C}${FOOTER_R}|" "$PREAMBLE"
sed -i "s|%%FOOTER_PLAIN%%|${FOOTER_L}${FOOTER_C}${FOOTER_R}|" "$PREAMBLE"

if [[ -n "$FM_HEADER" ]]; then
    sed -i "s|%%HEADER_PLACEHOLDER%%|\\\\fancyhead[C]{\\\\color{gray}\\\\small ${FM_HEADER}}|" "$PREAMBLE"
else
    sed -i "s|%%HEADER_PLACEHOLDER%%||" "$PREAMBLE"
fi

# Inject watermark if set
FM_WATERMARK="${FM_WATERMARK:-}"
if [[ -n "$FM_WATERMARK" ]]; then
    cat >> "$PREAMBLE" <<WATERMARK
\\usepackage{eso-pic}
\\usepackage{tikz}
\\AddToShipoutPictureFG{%
  \\begin{tikzpicture}[remember picture,overlay]
    \\node[rotate=45,opacity=0.12,scale=10,text=red] at (current page.center) {\\textsf{\\textbf{\\MakeUppercase{${FM_WATERMARK}}}}};
  \\end{tikzpicture}%
}
WATERMARK
    detail "Watermark: ${CYAN}${FM_WATERMARK}${RESET}"
fi

# Build TOC flags
# When numbering is on, headings shift by -1, so TOC depth needs +1 to compensate
TOC_FLAGS=()
if [[ "$TOC_LEVEL" -gt 0 ]]; then
    TOC_FLAGS+=(--toc --toc-depth="$TOC_LEVEL")
    detail "TOC depth: ${CYAN}${TOC_LEVEL}${RESET}"
else
    detail "TOC: ${DIM}disabled${RESET}"
fi

AUTHOR_FLAGS=()
if [[ -n "$FM_AUTHOR" ]]; then
    AUTHOR_FLAGS+=(-M "author=$FM_AUTHOR")
fi

# Numbered sections
NUMBER_FLAGS=()
if [[ "$FILE_NUMBERS" == "1" ]]; then
    NUMBER_FLAGS+=(--number-sections)

    # numberfrom controls which heading level starts getting numbers
    # pandoc: section=1, subsection=2, subsubsection=3
    cat >> "$PREAMBLE" <<SECNUM
\\setcounter{secnumdepth}{4}
SECNUM

    if [[ "$FILE_NUMBER_FROM" -ge 2 ]]; then
        # H1 (\section) unnumbered, H2 numbered as 1, 2, 3
        cat >> "$PREAMBLE" <<'SECNUM2'
\makeatletter
\renewcommand{\thesection}{}
\renewcommand{\thesubsection}{\arabic{subsection}}
\renewcommand{\thesubsubsection}{\thesubsection.\arabic{subsubsection}}
% Remove section number from titleformat without changing style
\titleformat{\section}
  {\LARGE\headingfont\bfseries\color{accentdark}\addfontfeatures{LetterSpace=5}\scshape}
  {}{0em}{}[\vspace{2pt}{\color{headrulecolor}\titlerule[1pt]}]
\makeatother
SECNUM2
    fi

    if [[ "$FILE_NUMBER_FROM" -ge 3 ]]; then
        cat >> "$PREAMBLE" <<'SECNUM3'
\renewcommand{\thesubsection}{}
\renewcommand{\thesubsubsection}{\arabic{subsubsection}}
\titleformat{\subsection}
  {\Large\headingfont\bfseries\color{accentdark}\addfontfeatures{LetterSpace=-1}}
  {}{0em}{}
SECNUM3
    fi
fi

# Remove placeholder
sed -i 's|%%SECNUMDEPTH_PLACEHOLDER%%||' "$PREAMBLE"

pandoc "$TEMP_MD" \
    -o "$OUTPUT_FILE" \
    --pdf-engine=xelatex \
    --lua-filter="$BRACKET_FILTER" \
    --resource-path=".:$WORKDIR" \
    --columns=72 \
    -V geometry:"margin=0.5in,includehead,includefoot" \
    -V fontsize=10pt \
    -V mainfont="Roboto" \
    -V monofont="Roboto Mono" \
    "${TOC_FLAGS[@]}" \
    "${AUTHOR_FLAGS[@]}" \
    "${NUMBER_FLAGS[@]}" \
    --highlight-style=tango \
    -H "$PREAMBLE" \
    --standalone

rm -f "$TEMP_MD" "$CALLOUT_MD" "$PREAMBLE" "${BRACKET_FILTER:-}" "${STRIPPED:-}" /tmp/mermaid-*.mmd /tmp/mermaid-*.png

PAGES=$(strings "$OUTPUT_FILE" 2>/dev/null | grep -c '/Type /Page' || echo "?")
SIZE=$(du -h "$OUTPUT_FILE" | cut -f1 | tr -d ' ')
success "PDF generated: ${CYAN}${SIZE}${RESET}, ~${CYAN}${PAGES}${RESET} pages"
INNER_SCRIPT

chmod +x "$CONVERT_SCRIPT"

# --- Run Docker ---
echo ""
info "Launching Docker container..."
detail "Mounting: ${CYAN}${INPUT_DIR}${RESET} → /work ${DIM}(read-only)${RESET}"
detail "Output:   ${CYAN}${OUTPUT_DIR}${RESET} → /output"
echo ""

CONVERT_BASENAME="$(basename "$CONVERT_SCRIPT")"
docker run --rm \
    -v "$INPUT_DIR:/work:ro" \
    -v "$OUTPUT_DIR:/output" \
    -e "TOC_LEVEL=$FILE_TOC_LEVEL" \
    -e "FM_FOOTER=$FM_FOOTER" \
    -e "FM_HEADER=$FM_HEADER" \
    -e "FM_AUTHOR=$FM_AUTHOR" \
    -e "FM_TITLE=$FM_TITLE" \
    -e "FM_SUBTITLE=$FM_SUBTITLE" \
    -e "FM_DATE=$FM_DATE" \
    -e "FM_DATE_LABEL=${FM_DATE_LABEL:-}" \
    -e "FM_DATE_HASH=${FM_DATE_HASH:-}" \
    -e "FM_DATE_DIRTY=${FM_DATE_DIRTY:-}" \
    -e "GIT_STAMP=${GIT_STAMP:-}" \
    -e "FILE_NUMBERS=$FILE_NUMBERS" \
    -e "FILE_NUMBER_FROM=$FILE_NUMBER_FROM" \
    -e "HIDE_FIRST_H1=$HIDE_FIRST_H1" \
    -e "FM_WATERMARK=$FM_WATERMARK" \
    --tmpfs /tmp:exec \
    "$IMAGE_NAME" "/work/$CONVERT_BASENAME" "$INPUT_FILE" "/output/$OUTPUT_FILE" \
    || {
        echo ""
        echo -e "  ${RED}${BOLD}Error producing PDF.${RESET} Docker/pandoc exited with a non-zero status."
        echo ""
        return 1
    }

# Move preview file to /tmp and clean up
if [[ -n "$PREVIEW_FINAL" ]]; then
    mv "$OUTPUT" "$PREVIEW_FINAL"
    OUTPUT="$PREVIEW_FINAL"
fi

echo ""
echo -e "  ${GREEN}${BOLD}PDF created:${RESET} ${CYAN}${OUTPUT}${RESET}"
echo ""

# Open if requested
if [[ $OPEN -eq 1 ]]; then
    open_pdf "$OUTPUT"
fi
}

# --- Process each input file ---
run_all() {
    local FAILED=0
    for input_file in "${POSITIONAL[@]}"; do
        convert_file "$input_file" "$OUT_FILE" || FAILED=$((FAILED + 1))
    done

    if [[ $FAILED -eq 0 ]]; then
        header "Complete! (${#POSITIONAL[@]} file(s))"
    else
        header "${FAILED} of ${#POSITIONAL[@]} file(s) failed"
    fi
}

run_all

# --- Watch mode ---
if [[ $WATCH -eq 1 ]]; then
    info "Watching for changes... ${DIM}(Ctrl+C to stop)${RESET}"
    echo ""

    # Get initial checksums (using a temp file instead of associative array for bash 3 compat)
    CHECKSUM_FILE=$(mktemp)
    trap 'rm -f "$CHECKSUM_FILE"' EXIT
    for f in "${POSITIONAL[@]}"; do
        fpath="$(cd "$(dirname "$f")" && pwd)/$(basename "$f")"
        echo "$(_sha256 < "$fpath" | cut -d' ' -f1)  $fpath" >> "$CHECKSUM_FILE"
    done

    while true; do
        sleep 2
        CHANGED=0
        for f in "${POSITIONAL[@]}"; do
            fpath="$(cd "$(dirname "$f")" && pwd)/$(basename "$f")"
            NEW_HASH=$(_sha256 < "$fpath" | cut -d' ' -f1)
            OLD_HASH=$(grep "  $fpath\$" "$CHECKSUM_FILE" | cut -d' ' -f1)
            if [[ "$NEW_HASH" != "$OLD_HASH" ]]; then
                CHANGED=1
                # Update stored checksum
                grep -v "  $fpath\$" "$CHECKSUM_FILE" > "${CHECKSUM_FILE}.tmp" || true
                echo "$NEW_HASH  $fpath" >> "${CHECKSUM_FILE}.tmp"
                mv "${CHECKSUM_FILE}.tmp" "$CHECKSUM_FILE"
            fi
        done
        if [[ $CHANGED -eq 1 ]]; then
            echo ""
            info "Change detected — rebuilding..."
            echo ""
            run_all
        fi
    done
fi

# Check for updates (runs after success, fast timeout)
check_for_update
