#!/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.              #
# Runs subset_font if config.toml has been modified.                            #
# Updates the README if the line numbers for the language section have changed. #
# Syncs the [extra] section from config.toml to theme.toml.                     #
#                                                                               #
# Stops you from commiting:                                                     #
# - a draft .md file                                                            #
# - a file with a "TODO"                                                        #
# - a JS file without a minified version                                        #
# - a minified JS file that isn't as small as it can be                         #
#################################################################################

# 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"
    awk '/^\+\+\+$/,/^\+\+\+$/ { if(/draft = true/) exit 0 } END { exit 1 }' "$file"
}

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

function has_minified_version() {
    local file="$1"
    local extension="${file##*.}"
    local minified_file="${file%.*}.min.$extension"
    [ -f "$minified_file" ]
}

function is_minified() {
    local file="$1"
    
    # Original file size.
    local original_size=$(wc -c < "$file")
    
    # File size after compression with terser.
    local terser_size=$(terser --compress --mangle -- "$file" | wc -c)
    
    # File size after compression with uglifyjs.
    local uglifyjs_size=$(uglifyjs --compress --mangle -- "$file" | wc -c)

    # Check if the file is already as small as or smaller than both minified versions.
    if (( original_size <= terser_size && original_size <= uglifyjs_size )); then
        return 0
    fi
    
    # If the file isn't as small as it can be, suggest the better compressor in the error message
    if (( terser_size < uglifyjs_size )); then
        error_exit "Minified JS file $file isn't as small as it can be! Try using terser for better compression."
    else
        error_exit "Minified JS file $file isn't as small as it can be! Try using uglifyjs for better compression."
    fi
}


# 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

# 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.   #
##################################################################

# Get the newly added and modified files.
all_changed_files=$(git diff --cached --name-only --diff-filter=AM)

# Loop through all newly added or modified files.
for file in $all_changed_files; do
    # 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 --force "$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

    # If the file contains "TODO", abort the commit.
    if contains_todo "$file"; then
        error_exit "File $file contains TODO! Remove or complete the TODO before committing."
    fi

    # If the file is a JS file and it doesn't have a minified version, abort the commit.
    if [[ "$file" == *.js ]] && [[ "$file" != *.min.js ]] && ! has_minified_version "$file"; then
        error_exit "JS file $file doesn't have a minified version!"
    fi

    # If the file is a minified JS file and it isn't as small as it can be, abort the commit.
    # Error message shows which minifier is best for the file.
    if [[ "$file" == *.min.js ]]; then
        is_minified "$file"
    fi

    # Sync the [extra] section from config.toml to theme.toml.
    # Check if config.toml has been modified; use spaces to avoid matching a substring.
    if [[ "$file" == "config.toml" ]]; then
        # Get the line number where [extra] starts in config.toml.
        config_extra_line=$(grep -n "^\[extra\]$" "config.toml" | cut -d: -f1)

        # Extract content after [extra] from config.toml.
        config_extra_content=$(tail -n +"$config_extra_line" "config.toml")

        # Get the line number where [extra] starts in theme.toml.
        theme_extra_line=$(grep -n "^\[extra\]$" "theme.toml" | cut -d: -f1)

        # Replace content after [extra] in theme.toml with extracted content from config.toml.
        awk -v n="$theme_extra_line" 'NR<n' theme.toml > theme.tmp
        echo "$config_extra_content" >> theme.tmp
        mv theme.tmp theme.toml

        # Add theme.toml to the list of files to be committed.
        git add "theme.toml"
    fi
done

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

# Loop through each modified .md file.
for file in $modified_files; do
    # 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

#########################################################
# Run subset_font if config.toml has been modified.     #
# https://welpo.github.io/tabi/blog/custom-font-subset/ #
#########################################################
if git diff --cached --name-only | grep -q "config.toml"; then
    echo "config.toml modified. Running subset_font…"

    # Call the subset_font script.
    ~/bin/subset_font -c config.toml -f static/fonts/Inter4.woff2 -o static/

    # Add the generated subset.css file to the commit.
    git add static/custom_subset.css
fi

################################################################################
# Update the README if the line numbers for the language section have changed. #
################################################################################

# File paths and names.
config_file="config.toml"
config_readme="README.md"

# Ensure the required files are present.
[ ! -f "$config_file" ] && error_exit "$config_file not found!"
[ ! -f "$config_readme" ] && error_exit "$config_readme not found!"

# Determine the line numbers for relevant sections in config.toml.
lang_start_line=$(grep -n "^\[languages.es\]$" "$config_file" | cut -d: -f1)
extra_start_line=$(grep -n "^\[extra\]$" "$config_file" | cut -d: -f1)
lang_end_line=$((extra_start_line - 2))

# Extract currently documented line numbers from README.
documented_lines=$(grep -o 'https://github.com/welpo/tabi/blob/main/config.toml#L[0-9]*-L[0-9]*' "$config_readme" | grep -o 'L[0-9]*-L[0-9]*')
doc_start_line=$(echo "$documented_lines" | cut -d'-' -f1 | tr -d 'L')
doc_end_line=$(echo "$documented_lines" | cut -d'-' -f2 | tr -d 'L')

# Ensure that the variables are set and are numbers.
if [[ ! $lang_start_line =~ ^[0-9]+$ ]] || [[ ! $doc_start_line =~ ^[0-9]+$ ]] || [[ ! $lang_end_line =~ ^[0-9]+$ ]] || [[ ! $doc_end_line =~ ^[0-9]+$ ]]; then
    error_exit "Line number variables are not set correctly."
fi

# Update the README if there's a discrepancy in the line numbers.
if [ "$lang_start_line" -ne "$doc_start_line" ] || [ "$lang_end_line" -ne "$doc_end_line" ]; then
    if ! perl -pi -e "s|https://github.com/welpo/tabi/blob/main/config.toml#L[0-9]*-L[0-9]*|https://github.com/welpo/tabi/blob/main/config.toml#L$lang_start_line-L$lang_end_line|g" "$config_readme"; then
        error_exit "Perl processing failed for $config_readme"
    fi
    
    # Add updated README to commit.
    git add "$config_readme"
fi
