#!/usr/bin/env bash

###################################################################################
# This script is run by git before a commit is made.                              #
# To use it, copy it to .git/hooks/pre-commit and make it executable.             #
# Alternatively, run the following command from the root of the repo:             #
# git config core.hooksPath .githooks                                             #
#                                                                                 #
#                                  FEATURES                                       #
# Updates the "updated" field in the front matter of .md files.                   #
# Compresses PNG files with either oxipng or optipng if available.                #
# Creates/updates the social media card for .md files.                            #
# Aborts the commit if a draft .md file or a file with "TODO" is being committed. #
###################################################################################

# Check if the script is being run from the root of the repo.
if [[ ! $(git rev-parse --show-toplevel) == $(pwd) ]]; then
    error_exit "This script must be run from the root of the repo."
fi

# Function to exit the script with an error message.
function error_exit() {
    echo "ERROR: $1" >&2
    exit "${2:-1}"
}

# Function to extract the date from the front matter.
function extract_date() {
    local file="$1"
    local field="$2"
    grep -m 1 "^$field =" "$file" | sed -e "s/$field = //" -e 's/ *$//'
}

# Function to check if the .md file is a draft.
function is_draft() {
    local file="$1"
    grep -q "draft = true" "$file"
}

# Check if the file contains "TODO".
function contains_todo() {
    local file="$1"
    grep -q "TODO" "$file"
}

# Check for changes outside of the front matter.
function has_body_changes() {
    local file="$1"
    local in_front_matter=1
    local triple_plus_count=0

    diff_output=$(git diff --unified=999 --cached --output-indicator-new='%' --output-indicator-old='&' "$file")

    while read -r line; do
        if [[ "$line" =~ ^\+\+\+$ ]]; then
            triple_plus_count=$((triple_plus_count + 1))
            if [[ $triple_plus_count -eq 2 ]]; then
                in_front_matter=0
            fi
        elif [[ $in_front_matter -eq 0 ]]; then
            if [[ "$line" =~ ^[\%\&] ]]; then
                return 0
            fi
        fi
    done <<< "$diff_output"
    return 1
}

# Function to update the social media card for a post or section.
function generate_and_commit_card {
    local file=$1
    social_media_card=$(./static/code/social-cards-zola -o static/img/social_cards -b http://127.0.0.1:1111 -u -p -i "$file") || {
        echo "Failed to update social media card for $file"
        exit 1
    }

    git add "$social_media_card" || {
        echo "Failed to add social media card $social_media_card"
        exit 1
    }

    git add "$file" || {
        echo "Failed to add $file"
        exit 1
    }
}

export -f generate_and_commit_card

# Get the modified .md to update the "updated" field in the front matter.
mapfile -t modified_md_files < <(git diff --cached --name-only --diff-filter=M | grep -Ei '\.md$' | grep -v '_index.md$')

# Loop through each modified .md file.
for file in "${modified_md_files[@]}"; do
    # If changes are only in the front matter, skip the file.
    if ! has_body_changes "$file"; then
        continue
    fi

    # Get the last modified date from the filesystem.
    last_modified_date=$(date -r "$file" +'%Y-%m-%d')

    # Extract the "date" field from the front matter.
    date_value=$(extract_date "$file" "date")

    # Skip the file if the last modified date is the same as the "date" field.
    if [[ "$last_modified_date" == "$date_value" ]]; then
        continue
    fi

    # Update the "updated" field with the last modified date.
    # If the "updated" field doesn't exist, create it below the "date" field.
    if ! awk -v date_line="$last_modified_date" 'BEGIN{FS=OFS=" = "; first = 1} {
        if (/^date =/ && first) {
            print; getline;
            if (!/^updated =/) print "updated" OFS date_line;
            first=0;
        }
        if (/^updated =/ && !first) gsub(/[^ ]*$/, date_line, $2);
        print;
    }' "$file" > "${file}.tmp"
    then
        error_exit "Failed to process $file with AWK"
    fi

    mv "${file}.tmp" "$file" || error_exit "Failed to overwrite $file with updated content"

    # Stage the changes.
    git add "$file"
done

# Check if oxipng is installed.
png_compressor=""
if command -v oxipng &> /dev/null; then
    png_compressor="oxipng -o max"
elif command -v optipng &> /dev/null; then
    png_compressor="optipng -o 7"
fi

##################################################################
# Compress PNG files with either oxipng or optipng if available. #
# Update the "updated" field in the front matter of .md files.   #
#          https://osc.garden/blog/zola-date-git-hook/           #
# Interrupt the commit if a draft .md file is being committed.   #
# Interrupt the commit if a file with "TODO" is being committed. #
# Create/update the social media card for posts and sections.    #
# For more info about the social media cards part, see:          #
#   https://osc.garden/blog/automating-social-media-cards-zola   #
##################################################################

# Get the newly added and modified files, but not deleted files.
mapfile -t all_changed_files < <(git diff --cached --name-only --diff-filter=d)

# Loop through each newly added or modified .md or .png file for PNG optimization and draft checking.
for file in "${all_changed_files[@]}"; do
    # Ignore this script.
    if [[ "$file" == "$0" ]]; then
        continue
    fi

    # If the file is a PNG and png_compressor is set, compress it and add it to the commit.
    if [[ "$file" =~ \.png$ ]] && [[ -n "$png_compressor" ]]; then
        $png_compressor "$file" || error_exit "Failed to compress PNG file $file"
        git add "$file" || error_exit "Failed to add compressed PNG file $file"
        continue
    fi

#    # If the file is an .md file and it's a draft, abort the commit.
#    if [[ "$file" == *.md ]]; then
#        if is_draft "$file"; then
#            error_exit "Draft file $file is being committed!"
#        fi
#    fi
done

# Use `social-cards-zola` to create/update the social media card for Markdown files.
# See https://osc.garden/blog/automating-social-media-cards-zola/ for context.
changed_md_files=$(echo "$all_changed_files" | grep '\.md$')
# Use parallel to create the social media cards in parallel and commit them.
 if [[ -n "$changed_md_files" ]]; then
     echo "$changed_md_files" | parallel -j 8 generate_and_commit_card
 fi
