#!/usr/bin/env zsh

# qsgen3 - A Minimal Markdown Static Site Generator
# Version: 0.1.0
QSGEN_NAME="qsgen3"
VERSION="0.2.0"
# Author: Your Name/AI Assistant

# Strict mode
set -euo pipefail

# Locale and umask for robustness and security
LC_ALL=C
LANG=C
umask 0022

# --- Configuration --- 
# Associative array to hold configuration values
typeset -A QSG_CONFIG
typeset -a SITEMAP_URLS=() # Array to store URLs for sitemap


# --- Script Paths --- 
# Get the directory where the script is located - SCRIPT_DIR might still be useful for finding layouts if not overridden by config
SCRIPT_DIR="$(cd "$(dirname "${(%):-%x}")" && pwd)"
# Project root is the current working directory
PROJECT_ROOT="$PWD"
# Default config file name; full path will be resolved in main after option parsing
CONFIG_FILE_NAME="site.conf"
# Full path to the configuration file, to be determined in main
CONFIG_FILE=""

# --- Usage Information ---
_usage() {
    cat <<EOF
Usage: $QSGEN_NAME [options]

$QSGEN_NAME - A Minimal Markdown Static Site Generator (Version $VERSION)

Options:
  -c, --config <file>   Specify a custom configuration file.
                        (Default: site.conf in the project root)
  -V, --version         Show version information and exit.
  -h, --help            Show this help message and exit.
EOF
    exit 0
}

# --- Logging --- 
_log() {
    local level=$1
    shift
    local message=$*
    # ANSI color codes
    local color_normal="\033[0m"
    local color_red="\033[0;31m"
    local color_green="\033[0;32m"
    local color_yellow="\033[0;33m"
    local color_blue="\033[0;34m"
    local color_cyan="\033[0;36m"

    # QSG_DEBUG can be set to true (e.g. export QSG_DEBUG=true) to enable debug logs
    # By default, it's off unless explicitly set.
    : ${QSG_DEBUG:=false}

    # Prepare format string and arguments for printf
    # The message itself is %s to prevent it from being interpreted as a format string.
    local format_string="%s[%s]%s %s\n"
    local color_code=""
    local log_prefix=""
    local output_stream=1 # stdout by default

    case $level in
        DEBUG)
            if [[ "$QSG_DEBUG" != "true" ]]; then return 0; fi
            color_code="$color_cyan"
            log_prefix="DEBUG"
            ;;
        INFO)
            color_code="$color_blue"
            log_prefix="INFO"
            ;;
        SUCCESS)
            color_code="$color_green"
            log_prefix="SUCCESS"
            ;;
        WARNING)
            color_code="$color_yellow"
            log_prefix="WARNING"
            output_stream=2 # stderr
            ;;
        ERROR)
            color_code="$color_red"
            log_prefix="ERROR"
            output_stream=2 # stderr
            ;;
        *)
            color_code="$color_red"
            log_prefix="LOG_ERROR"
            message="Unknown log level: $level. Original Message: $message"
            output_stream=2 # stderr
            ;;
    esac

    if [[ $output_stream -eq 1 ]]; then
        printf "$format_string" "$color_code" "$log_prefix" "$color_normal" "$message"
    else
        printf "$format_string" "$color_code" "$log_prefix" "$color_normal" "$message" >&2
    fi
}

# --- YAML Helper Functions ---
_yaml_escape_val() {
    # Escape backslashes and double quotes for YAML string values
    printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
}

# --- Configuration Loading --- 
_load_config() {
    if [[ ! -f "$CONFIG_FILE" ]]; then
        _log ERROR "Configuration file not found: $CONFIG_FILE"
        exit 1
    fi

    _log INFO "Attempting to load configuration from: $CONFIG_FILE"
    local current_section=""
    local line_num=0

    # Check if config file is readable
    if [[ ! -r "$CONFIG_FILE" ]]; then
        _log ERROR "Configuration file is not readable: $CONFIG_FILE"
        exit 1
    else
        _log DEBUG "Configuration file is readable: $CONFIG_FILE"
    fi

    _log DEBUG "Attempting to enter config parsing loop. Disabling set -e temporarily."
    set +e # Temporarily disable exit on error
    # Read file line by line
    while IFS= read -r line; do
        ((line_num++))
        # _log DEBUG "Read raw line $line_num: '$line'" # Raw line logging can be noisy, comment for now

        # Remove leading/trailing whitespace
        line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
        # _log DEBUG "Trimmed line $line_num: '$line'"

        # Skip empty lines and comments (lines starting with # or ;)
        if [[ -z "$line" ]] || [[ "$line" == \#* ]] || [[ "$line" == \;* ]]; then
            # _log DEBUG "Skipping line $line_num (empty or comment)"
            continue
        fi

        # Check for section headers like [section_name]
        if [[ "$line" == \[[a-zA-Z0-9_]+]\] ]]; then
            current_section=$(echo "$line" | sed 's/\[//;s/\]//')
            # _log DEBUG "Switched to section: $current_section"
            QSG_CONFIG[${current_section}_exists]=true # Mark section as existing
            continue
        fi

        # Process key-value pairs within a section
        if [[ -n "$current_section" ]] && [[ "$line" == *=* ]]; then
            local key=$(echo "$line" | cut -d'=' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            # Extract key and value
            key="${line%%=*}"
            local raw_value="${line#*=}"

            # Strip trailing comments (anything after #). Ensure this is done BEFORE quote stripping.
            local value_no_comment="${raw_value%%#*}"

            # Remove potential quotes from value_no_comment
            local value_no_quotes="${value_no_comment#\"}" # Remove leading quote
            value_no_quotes="${value_no_quotes%\"}" # Remove trailing quote

            # Trim leading/trailing whitespace from value_no_quotes
            # Using standard parameter expansion for trimming whitespace
            local temp_value="${value_no_quotes##[[:space:]]}" # Remove leading whitespace
            value="${temp_value%%[[:space:]]}" # Remove trailing whitespace
            local full_key="${current_section}_${key}"
            QSG_CONFIG[$full_key]="$value"
            # _log DEBUG "Loaded config: $full_key = $value"
        elif [[ -z "$current_section" ]] && [[ "$line" == *=* ]]; then
            # Handle global key-value pairs (not inside a section)
            key="${line%%=*}" # Extract key
            local raw_value="${line#*=}" # Extract raw value

            # 1. Strip trailing comments
            local value_no_comment="${raw_value%%#*}"

            # 2. Trim leading/trailing whitespace from the comment-stripped value
            local temp_trimmed_value="${value_no_comment##[[:space:]]}" # Remove leading whitespace
            local value_trimmed="${temp_trimmed_value%%[[:space:]]}"    # Remove trailing whitespace

            # 3. Remove potential quotes from the trimmed, comment-stripped value
            if [[ "${value_trimmed:0:1}" == "\"" && "${value_trimmed: -1}" == "\"" ]]; then
                # It's double-quoted
                value="${value_trimmed:1:-1}"
            elif [[ "${value_trimmed:0:1}" == "'" && "${value_trimmed: -1}" == "'" ]]; then
                # It's single-quoted
                value="${value_trimmed:1:-1}"
            else
                # Not quoted or improperly quoted, use as is
                value="$value_trimmed"
            fi

            # For specific path keys, ensure they are absolute
            case "$key" in
                paths_content_dir|paths_output_dir|paths_layouts_dir|paths_static_dir)
                    if [[ "$value" != /* && -n "$value" ]]; then # If not absolute and not empty
                        value="$PROJECT_ROOT/$value"
                    fi
                    ;;
            esac
            QSG_CONFIG[$key]="$value"
            # _log DEBUG "Loaded global config: $key = $value"
        else
            if [[ -n "$line" ]]; then # Only log if line is not empty (already handled above)
                _log WARNING "Ignoring malformed line $line_num in $CONFIG_FILE: $line"
            fi
        fi
    done < "$CONFIG_FILE"
    local read_status=$?
    set -e # Re-enable exit on error
    _log DEBUG "Exited config parsing loop."
    _log DEBUG "Exited config parsing loop. Read status: $read_status. Re-enabled set -e."

    if [[ $read_status -ne 0 && $read_status -ne 1 ]]; then # read returns 1 on EOF, which is fine
        _log WARNING "Potential issue reading configuration file. Read command exited with status: $read_status"
    fi

    # Validate required configuration
    local required_config_keys=(
        "paths_content_dir"
        "paths_output_dir"
        "paths_layouts_dir"
        "paths_static_dir"
        "site_name"
        "site_url"
        "site_theme"
    )
    local missing_configs=false

    for key in "${required_config_keys[@]}"; do
        if [[ -z "${QSG_CONFIG[$key]:-}" ]]; then
            _log ERROR "Required configuration '$key' is missing from $CONFIG_FILE."
            missing_configs=true
        else
            # Resolve path-related keys to absolute paths if not already absolute
            if [[ "$key" == paths_* ]] && [[ "${QSG_CONFIG[$key]}" != /* ]]; then
                QSG_CONFIG[$key]="$PROJECT_ROOT/${QSG_CONFIG[$key]}"
            fi
            _log DEBUG "Validated config: $key = ${QSG_CONFIG[$key]}"
        fi
    done

    # Default build options if not set
    : ${QSG_CONFIG[build_options_process_drafts]:=false}
    : ${QSG_CONFIG[build_options_generate_rss]:=true}
    : ${QSG_CONFIG[build_options_minify_html]:=false}
    : ${QSG_CONFIG[build_options_minify_css]:=false}
    : ${QSG_CONFIG[build_options_minify_xml]:=false}
    # : ${QSG_CONFIG[build_options_generate_sitemap]:=true} # Example if sitemap is added

    _log DEBUG "build_options_process_drafts set to: ${QSG_CONFIG[build_options_process_drafts]}"
    _log DEBUG "build_options_generate_rss set to: ${QSG_CONFIG[build_options_generate_rss]}"
    _log DEBUG "build_options_minify_html set to: ${QSG_CONFIG[build_options_minify_html]}"
    _log DEBUG "build_options_minify_css set to: ${QSG_CONFIG[build_options_minify_css]}"
    _log DEBUG "build_options_minify_xml set to: ${QSG_CONFIG[build_options_minify_xml]}"
    # _log DEBUG "build_options_generate_sitemap set to: ${QSG_CONFIG[build_options_generate_sitemap]}"

    if [[ "$missing_configs" == true ]]; then
        _log ERROR "One or more required configurations are missing. Please check $CONFIG_FILE."
        exit 1
    fi

    _log INFO "Configuration loaded successfully."
    return 0
}

# --- Dependency Checking --- 
_check_dependencies() {
    _log INFO "Checking dependencies..."
    if ! command -v pandoc &> /dev/null; then
        _log ERROR "Pandoc is not installed. Please install pandoc to continue."
        _log ERROR "See: https://pandoc.org/installing.html"
        exit 1
    fi
    _log INFO "All critical dependencies found."
}

# --- Sitemap Generation ---
_generate_sitemap() {
    _log DEBUG "Entered _generate_sitemap"
    if [[ "${QSG_CONFIG[build_options_generate_sitemap]}" != "true" ]]; then
        _log INFO "Sitemap generation is disabled in configuration. Skipping."
        return 0
    fi

    if [[ -z "${QSG_CONFIG[site_url]}" ]]; then
        _log WARNING "'site_url' is not set in configuration. Cannot generate sitemap."
        return 1 # Indicate an issue
    fi

    if [[ ${#SITEMAP_URLS[@]} -eq 0 ]]; then
        _log INFO "No URLs collected for sitemap. Skipping sitemap generation."
        return 0
    fi

    _log INFO "Generating sitemap..."
    local sitemap_file="${QSG_CONFIG[paths_output_dir]}/sitemap.xml"
    local site_url="${QSG_CONFIG[site_url]}"
    # Ensure site_url does not end with a slash for clean concatenation
    site_url="${site_url%/}"

    local last_mod_date
    last_mod_date=$(date +%Y-%m-%d) # Current date as last modified

    # Start XML structure
    # Using a subshell for cleaner output redirection to the file
    ( 
        echo '<?xml version="1.0" encoding="UTF-8"?>'
        echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'

        local rel_url
        for rel_url in "${SITEMAP_URLS[@]}"; do
            # Ensure rel_url doesn't start with a slash if site_url already provides it
            # However, our collected URLs (e.g., posts/file.html, index.html) are fine.
            echo '  <url>'
            echo "    <loc>${site_url}/${rel_url}</loc>"
            echo "    <lastmod>${last_mod_date}</lastmod>"
            # Optional: <changefreq> and <priority>
            # echo "    <changefreq>weekly</changefreq>"
            # echo "    <priority>0.8</priority>"
            echo '  </url>'
        done

        echo '</urlset>'
    ) > "$sitemap_file"

    if [[ $? -eq 0 ]]; then
        _log SUCCESS "Sitemap generated successfully: $sitemap_file"
    else
        _log ERROR "Failed to write sitemap to $sitemap_file"
        return 1 # Indicate an issue
    fi
    return 0
}

# --- Minification Functions ---
_minify_html() {
    local input_file="$1"
    local output_file="$2"
    
    if [[ ! -f "$input_file" ]]; then
        _log ERROR "Input file for HTML minification not found: $input_file"
        return 1
    fi
    
    _log DEBUG "Minifying HTML: $input_file -> $output_file"
    
    # HTML minification using sed and tr
    # Remove comments, extra whitespace, and empty lines while preserving content
    sed -e '
        # Remove HTML comments (but preserve conditional comments)
        /<!--\[if/!{
            /<!--.*-->/d
            /<!--/{
                :comment
                /-->/!{
                    N
                    b comment
                }
                d
            }
        }
        # Remove leading and trailing whitespace from lines
        s/^[[:space:]]*//
        s/[[:space:]]*$//
        # Remove empty lines
        /^$/d
        # Compress multiple spaces between tags to single space
        s/>[[:space:]]\+</></g
        # Remove spaces around = in attributes
        s/[[:space:]]*=[[:space:]]*/=/g
    ' "$input_file" | tr -d '\n' | sed '
        # Add newlines after certain closing tags for readability
        s|</head>|</head>\n|g
        s|</body>|</body>\n|g
        s|</html>|</html>\n|g
    ' > "$output_file"
    
    if [[ $? -eq 0 ]]; then
        _log DEBUG "HTML minification completed successfully"
        return 0
    else
        _log ERROR "HTML minification failed"
        return 1
    fi
}

_minify_css() {
    local input_file="$1"
    local output_file="$2"
    
    if [[ ! -f "$input_file" ]]; then
        _log ERROR "Input file for CSS minification not found: $input_file"
        return 1
    fi
    
    _log DEBUG "Minifying CSS: $input_file -> $output_file"
    
    # CSS minification using sed
    sed -e '
        # Remove CSS comments
        /\/\*/{
            :comment
            /\*\//!{
                N
                b comment
            }
            s|/\*.*\*/||g
        }
        # Remove leading and trailing whitespace
        s/^[[:space:]]*//
        s/[[:space:]]*$//
        # Remove empty lines
        /^$/d
        # Remove spaces around { } : ; ,
        s/[[:space:]]*{[[:space:]]*/{/g
        s/[[:space:]]*}[[:space:]]*/}/g
        s/[[:space:]]*:[[:space:]]*/:/g
        s/[[:space:]]*;[[:space:]]*/;/g
        s/[[:space:]]*,[[:space:]]*/,/g
        # Remove trailing semicolon before }
        s/;}/}/g
        # Compress multiple spaces to single space
        s/[[:space:]]\+/ /g
    ' "$input_file" | tr -d '\n' > "$output_file"
    
    if [[ $? -eq 0 ]]; then
        _log DEBUG "CSS minification completed successfully"
        return 0
    else
        _log ERROR "CSS minification failed"
        return 1
    fi
}

_minify_xml() {
    local input_file="$1"
    local output_file="$2"
    
    if [[ ! -f "$input_file" ]]; then
        _log ERROR "Input file for XML minification not found: $input_file"
        return 1
    fi
    
    _log DEBUG "Minifying XML: $input_file -> $output_file"
    
    # XML minification using sed
    sed -e '
        # Remove XML comments
        /<!--/{
            :comment
            /-->/!{
                N
                b comment
            }
            s|<!--.*-->||g
        }
        # Remove leading and trailing whitespace
        s/^[[:space:]]*//
        s/[[:space:]]*$//
        # Remove empty lines
        /^$/d
        # Compress whitespace between tags
        s/>[[:space:]]\+</></g
        # Compress multiple spaces to single space
        s/[[:space:]]\+/ /g
    ' "$input_file" | tr -d '\n' | sed '
        # Add newline after XML declaration and root closing tag
        s|?>|?>\n|g
        s|</rss>|</rss>\n|g
        s|</urlset>|</urlset>\n|g
    ' > "$output_file"
    
    if [[ $? -eq 0 ]]; then
        _log DEBUG "XML minification completed successfully"
        return 0
    else
        _log ERROR "XML minification failed"
        return 1
    fi
}

_minify_file() {
    local file_path="$1"
    
    if [[ ! -f "$file_path" ]]; then
        _log ERROR "File for minification not found: $file_path"
        return 1
    fi
    
    local file_extension="${file_path##*.}"
    local temp_file="${file_path}.tmp"
    local should_minify=false
    
    case "$file_extension" in
        html|htm)
            if [[ "${QSG_CONFIG[build_options_minify_html]}" == "true" ]]; then
                should_minify=true
                _minify_html "$file_path" "$temp_file"
            fi
            ;;
        css)
            if [[ "${QSG_CONFIG[build_options_minify_css]}" == "true" ]]; then
                should_minify=true
                _minify_css "$file_path" "$temp_file"
            fi
            ;;
        xml)
            if [[ "${QSG_CONFIG[build_options_minify_xml]}" == "true" ]]; then
                should_minify=true
                _minify_xml "$file_path" "$temp_file"
            fi
            ;;
        *)
            _log DEBUG "No minification available for file type: $file_extension ($file_path)"
            return 0
            ;;
    esac
    
    if [[ "$should_minify" == "true" ]]; then
        if [[ $? -eq 0 && -f "$temp_file" ]]; then
            # Calculate size reduction
            local original_size=$(wc -c < "$file_path")
            local minified_size=$(wc -c < "$temp_file")
            local reduction=$((original_size - minified_size))
            local percentage=0
            if [[ $original_size -gt 0 ]]; then
                percentage=$(( (reduction * 100) / original_size ))
            fi
            
            mv "$temp_file" "$file_path"
            _log INFO "Minified $file_path: ${original_size} -> ${minified_size} bytes (${percentage}% reduction)"
        else
            _log ERROR "Minification failed for $file_path"
            [[ -f "$temp_file" ]] && rm -f "$temp_file"
            return 1
        fi
    fi
    
    return 0
}

_minify_output_directory() {
    local output_dir="${QSG_CONFIG[paths_output_dir]}"
    
    if [[ ! -d "$output_dir" ]]; then
        _log WARNING "Output directory not found for minification: $output_dir"
        return 0
    fi
    
    _log INFO "Starting minification of generated files..."
    
    # Find and minify all relevant files
    local files_processed=0
    local files_minified=0
    
    while IFS= read -r -d '' file; do
        files_processed=$((files_processed + 1))
        local file_extension="${file##*.}"
        local should_process=false
        
        case "$file_extension" in
            html|htm)
                [[ "${QSG_CONFIG[build_options_minify_html]}" == "true" ]] && should_process=true
                ;;
            css)
                [[ "${QSG_CONFIG[build_options_minify_css]}" == "true" ]] && should_process=true
                ;;
            xml)
                [[ "${QSG_CONFIG[build_options_minify_xml]}" == "true" ]] && should_process=true
                ;;
        esac
        
        if [[ "$should_process" == "true" ]]; then
            if _minify_file "$file"; then
                files_minified=$((files_minified + 1))
            fi
        fi
    done < <(find "$output_dir" -type f \( -name "*.html" -o -name "*.htm" -o -name "*.css" -o -name "*.xml" \) -print0)
    
    _log INFO "Minification complete: ${files_minified}/${files_processed} files minified"
    return 0
}

# --- Core Functions --- 
_clean_output_dir() {
    _log INFO "Cleaning output directory: ${QSG_CONFIG[paths_output_dir]}"
    
    local output_dir="${QSG_CONFIG[paths_output_dir]}"
    local preserve_file="$PROJECT_ROOT/.qsgen3_preserve"
    
    # If output directory doesn't exist, just create it
    if [[ ! -d "$output_dir" ]]; then
        mkdir -p "$output_dir"
        if [[ $? -eq 0 ]]; then
            _log DEBUG "Created output directory: $output_dir"
        else
            _log ERROR "Failed to create output directory: $output_dir"
            return 1
        fi
        return 0
    fi
    
    # Check if preserve file exists
    if [[ -f "$preserve_file" ]]; then
        _log INFO "Found preserve file: $preserve_file"
        _log DEBUG "Performing selective cleaning with file preservation"
        
        # Use in-memory arrays to store file paths and content
        local files_to_preserve=()
        local preserved_file_paths=()
        local preserved_file_contents=()
        local files_preserved=0
        
        # Read preserve patterns and collect matching files
        while IFS= read -r pattern || [[ -n "$pattern" ]]; do
            # Skip empty lines and comments
            [[ -z "$pattern" || "$pattern" == \#* ]] && continue
            
            # Remove leading/trailing whitespace
            pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            [[ -z "$pattern" ]] && continue
            
            _log DEBUG "Processing preserve pattern: $pattern"
            
            # Find files matching the pattern in output directory
            while IFS= read -r -d '' file; do
                if [[ -f "$file" ]]; then
                    # Get relative path from output directory
                    local rel_path="${file#$output_dir/}"
                    
                    # Read file content into memory
                    local file_content
                    if file_content=$(<"$file"); then
                        preserved_file_paths+=("$rel_path")
                        preserved_file_contents+=("$file_content")
                        files_preserved=$((files_preserved + 1))
                        _log DEBUG "Preserved file in memory: $rel_path"
                    else
                        _log WARNING "Failed to read file for preservation: $rel_path"
                    fi
                fi
            done < <(find "$output_dir" -name "$pattern" -type f -print0 2>/dev/null)
            
        done < "$preserve_file"
        
        # Remove the output directory
        _log DEBUG "Removing output directory contents (preserving $files_preserved files in memory)"
        rm -rf "$output_dir"
        
        # Recreate output directory
        mkdir -p "$output_dir"
        if [[ $? -ne 0 ]]; then
            _log ERROR "Failed to recreate output directory: $output_dir"
            return 1
        fi
        
        # Restore preserved files from memory
        if [[ $files_preserved -gt 0 ]]; then
            _log DEBUG "Restoring $files_preserved preserved files from memory"
            for ((i=0; i<${#preserved_file_paths[@]}; i++)); do
                local rel_path="${preserved_file_paths[i]}"
                local file_content="${preserved_file_contents[i]}"
                local full_path="$output_dir/$rel_path"
                
                # Create directory structure if needed
                mkdir -p "$(dirname "$full_path")"
                
                # Write file content back
                if printf '%s' "$file_content" > "$full_path"; then
                    _log DEBUG "Restored file from memory: $rel_path"
                else
                    _log WARNING "Failed to restore file from memory: $rel_path"
                fi
            done
            _log INFO "Restored $files_preserved preserved files from memory"
        fi
        
    else
        # No preserve file - do complete cleaning as before
        _log DEBUG "No preserve file found - performing complete cleaning"
        rm -rf "$output_dir"
        mkdir -p "$output_dir"
        if [[ $? -ne 0 ]]; then
            _log ERROR "Failed to recreate output directory: $output_dir"
            return 1
        fi
    fi
    
    _log DEBUG "Output directory cleaning completed successfully"
    return 0
}

_copy_static_files() {
    local root_static_source_dir_rel="${QSG_CONFIG[paths_static_dir]:-}" # Relative path like "static"
    local root_static_source_dir_abs=""
    if [[ -n "$root_static_source_dir_rel" ]]; then
        root_static_source_dir_abs="$PROJECT_ROOT/$root_static_source_dir_rel"
    fi
    
    local theme_static_source_dir_abs="${QSG_CONFIG[theme_static_source_dir]:-}" # Absolute path, set in main

    # Target directory for all static files in the output.
    local output_dir_from_config_csf="${QSG_CONFIG[paths_output_dir]:-output}" # Default to "output" if not set in _copy_static_files
    local base_output_dir_abs_csf=""
    if [[ "$output_dir_from_config_csf" == /* ]]; then
        base_output_dir_abs_csf="$output_dir_from_config_csf"
    else
        base_output_dir_abs_csf="$PROJECT_ROOT/$output_dir_from_config_csf"
    fi
    local overall_target_static_dir_abs="$base_output_dir_abs_csf/static"

    _log INFO "Preparing to copy static files to $overall_target_static_dir_abs"
    if ! mkdir -p "$overall_target_static_dir_abs"; then 
        _log ERROR "Failed to create target static directory: $overall_target_static_dir_abs"
        return 1
    fi

    local rsync_common_opts=(-a --delete --exclude '.gitkeep' --exclude '.DS_Store')
    local cp_common_opts=(-R -p) # -p to preserve mode, ownership, timestamps
    local copy_cmd=""
    local cmd_opts=()

    if command -v rsync &> /dev/null; then
        copy_cmd="rsync"
        cmd_opts=("${rsync_common_opts[@]}")
    else
        copy_cmd="cp"
        # For cp, source needs trailing /., target needs to exist or be a dir name
        cmd_opts=("${cp_common_opts[@]}")
        _log WARNING "rsync not found. Falling back to 'cp'. Some features like --delete might not be available."
    fi

    local root_copy_ok=true
    local theme_copy_ok=true
    local any_files_copied=false

    # 1. Copy from root static directory (if it exists)
    if [[ -n "$root_static_source_dir_abs" && -d "$root_static_source_dir_abs" ]]; then
        _log DEBUG "Copying from root static directory: $root_static_source_dir_abs using $copy_cmd"
        local source_path_for_cmd="$root_static_source_dir_abs/"
        local target_path_for_cmd="$overall_target_static_dir_abs/"
        if [[ "$copy_cmd" == "cp" ]]; then source_path_for_cmd="$root_static_source_dir_abs/."; fi
        
        if "$copy_cmd" "${cmd_opts[@]}" "$source_path_for_cmd" "$target_path_for_cmd"; then
            _log DEBUG "Successfully copied files from $root_static_source_dir_abs"
            any_files_copied=true
        else
            _log ERROR "Failed to copy files from $root_static_source_dir_abs to $overall_target_static_dir_abs using $copy_cmd"
            root_copy_ok=false
        fi
    else
        _log DEBUG "Root static directory '$root_static_source_dir_abs' not found or not specified. Skipping."
    fi

    # 2. Copy from theme static directory (if it exists), overwriting root static files
    if [[ -n "$theme_static_source_dir_abs" && -d "$theme_static_source_dir_abs" ]]; then
        _log DEBUG "Copying from theme static directory (will overwrite): $theme_static_source_dir_abs using $copy_cmd"
        local source_path_for_cmd="$theme_static_source_dir_abs/"
        local target_path_for_cmd="$overall_target_static_dir_abs/"
        if [[ "$copy_cmd" == "cp" ]]; then source_path_for_cmd="$theme_static_source_dir_abs/."; fi

        if "$copy_cmd" "${cmd_opts[@]}" "$source_path_for_cmd" "$target_path_for_cmd"; then
            _log DEBUG "Successfully copied files from $theme_static_source_dir_abs"
            any_files_copied=true
        else
            _log ERROR "Failed to copy files from $theme_static_source_dir_abs to $overall_target_static_dir_abs using $copy_cmd"
            theme_copy_ok=false
        fi
    else
        _log DEBUG "Theme static source directory '$theme_static_source_dir_abs' not found or not specified. Skipping."
    fi

    if ! $root_copy_ok || ! $theme_copy_ok; then
        _log ERROR "One or more static file copy operations failed."
        return 1
    fi

    if ! $any_files_copied; then
        _log INFO "No static files found to copy (neither root nor theme static directories were specified, existed, or contained files)."
    else
        _log INFO "Static files copied successfully to $overall_target_static_dir_abs"
    fi
    
    return 0
}

_generate_index_page() {
    _log DEBUG "Entered _generate_index_page"

    _log INFO "Generating index page..."
    local content_posts_dir="${QSG_CONFIG[paths_content_dir]}/posts"
    local output_index_file="${QSG_CONFIG[paths_output_dir]}/index.html"
    local layout_index_file="${QSG_CONFIG[paths_layouts_dir]}/index.html"

    if [[ ! -d "$content_posts_dir" ]]; then
        _log WARNING "Posts directory not found: $content_posts_dir. Skipping index page generation."
        return 0
    fi

    if [[ ! -f "$layout_index_file" ]]; then
        _log ERROR "Index layout file not found: $layout_index_file. Cannot generate index page."
        return 1
    fi

    local post_details=()

    # Collect post details
    # This is a simplified approach. Robust YAML parsing is complex in pure shell.
    # We'll extract title, date, and construct a URL.
    # We assume date format is YYYY-MM-DD for sorting.
    # A 'summary' field in frontmatter would also be good.

    local md_file
    for md_file in $(find "$content_posts_dir" -name '*.md' -type f); do
        _log DEBUG "Processing metadata for $md_file"

        # Extract frontmatter (lines between --- and ---)
        # Use sed to print lines between the first '---' and the next '---'
        # Then grep for title, date, summary, draft
        local frontmatter=$(sed -n '/^---$/,/^---$/{/^---$/d;p;}' "$md_file")

        if [[ -z "$frontmatter" ]]; then
            _log WARNING "No YAML frontmatter found or frontmatter is empty in $md_file. Skipping."
            continue
        fi

        # Extract title, date, summary, and draft from frontmatter
        # Basic extraction: assumes 'key: value' format, strips quotes
        local title=$(echo "$frontmatter" | grep -m1 -iE '^title:' | sed -E 's/^title:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
        local date=$(echo "$frontmatter" | grep -m1 -iE '^date:' | sed -E 's/^date:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
        local summary=$(echo "$frontmatter" | grep -m1 -iE '^summary:' | sed -E 's/^summary:[[:space:]]*//i; s/^["\x27](.*)["\x27]$/\1/')
        local draft=$(echo "$frontmatter" | grep -m1 -iE '^draft:' | sed -E 's/^draft:[[:space:]]*//i' | tr '[:upper:]' '[:lower:]')

        _log DEBUG "Extracted Title: [$title], Date: [$date], Summary: [$summary], Draft: [$draft] for $md_file"

        if [[ "$draft" == "true" && "${QSG_CONFIG[build_options_process_drafts]}" != "true" ]]; then
            _log DEBUG "Skipping draft post for index: $md_file (draft: $draft)"
            continue
        fi

        if [[ -z "$title" ]]; then
            _log WARNING "Post '$md_file' is missing a title in frontmatter. Skipping for index."
            continue
        fi

        # Construct URL relative to site root
        local relative_path="${md_file#${QSG_CONFIG[paths_content_dir]}/}" # e.g., posts/hello-world.md
        local post_url="${QSG_CONFIG[site_url]}/${relative_path%.md}.html"

        # Use date for sorting, default to a very old date if not found
        local sort_key="${date:-0000-00-00}"
        post_details+=("${sort_key}|${title}|${post_url}|${date:-N/A}|${summary:-}")
    done

    # Sort posts by date (descending)
    # IFS=$'\n' sorted_posts=($(printf "%s\n" "${post_details[@]}" | sort -t'|' -k1,1r -k2,2))
    # Using a loop for sorting to avoid IFS issues and handle empty array better
    local sorted_posts=()
    if [[ ${#post_details[@]} -gt 0 ]]; then
      OIFS="$IFS"
      IFS=$'\n'
      sorted_posts=($(printf "%s\n" "${post_details[@]}" | sort -t'|' -k1,1r))
      IFS="$OIFS"
    fi
    
    # Generate comprehensive YAML string including site-wide metadata and posts
    local site_title_for_yaml="${QSG_CONFIG[site_title]:-${QSG_CONFIG[site_name]}}"

    # Helper for YAML string escaping (handles backslashes and double quotes)
    local yaml_lines=()
    yaml_lines+=( "$(printf 'current_year: "%s"' "$(date +%Y)")" )
    yaml_lines+=( "$(printf 'site_name: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_name]}")")" )
    yaml_lines+=( "$(printf 'site_tagline: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_tagline]}")")" )
    yaml_lines+=( "$(printf 'site_url: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_url]}")")" )
    yaml_lines+=( "$(printf 'title: "%s"' "$(_yaml_escape_val "$site_title_for_yaml")")" )
    yaml_lines+=( "$(printf 'posts:')" )

    if [[ ${#sorted_posts[@]} -eq 0 ]]; then
        _log DEBUG "No posts found to add to index YAML."
        yaml_lines+=( "$(printf '  [] # Explicit empty list for posts')" )
    else
        for post_line in "${sorted_posts[@]}"; do
            # Parse the post_line: sort_key|title|url|date|summary
            IFS='|' read -r sort_key title url date summary <<< "$post_line"
            
            yaml_lines+=( "$(printf '  - post_title: "%s"' "$(_yaml_escape_val "$title")")" )
            yaml_lines+=( "$(printf '    post_url: "%s"' "$(_yaml_escape_val "$url")")" )
            yaml_lines+=( "$(printf '    post_date: "%s"' "$(_yaml_escape_val "$date")")" )
            yaml_lines+=( "$(printf '    post_summary: "%s"' "$(_yaml_escape_val "$summary")")" )
        done
    fi

    # Join YAML lines into a single string
    local yaml_content
    OIFS="$IFS"; IFS=$'\n'
    yaml_content="${yaml_lines[*]}"
    IFS="$OIFS"

    _log DEBUG "Generated YAML content for index (first 300 chars): ${yaml_content:0:300}..."

    # Build Pandoc command array
    local pandoc_cmd_index=("pandoc")

    # Add CSS if theme CSS is available
    if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]}" ]]; then
        local expected_css_file="${QSG_CONFIG[paths_output_dir]}${QSG_CONFIG[pandoc_css_path_arg]}"
        if [[ -f "$expected_css_file" ]]; then
            pandoc_cmd_index+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}")
            _log DEBUG "Added CSS to index page: ${QSG_CONFIG[pandoc_css_path_arg]}"
        else
            _log WARNING "Expected CSS file not found: $expected_css_file. CSS will not be linked in index page."
        fi
    else
        _log DEBUG "No theme CSS path configured. Index page will not include CSS."
    fi

    # Add metadata via process substitution instead of temporary file
    pandoc_cmd_index+=("--metadata-file" "/dev/stdin")
    pandoc_cmd_index+=("--template=${layout_index_file}")
    pandoc_cmd_index+=("--output=${output_index_file}")
    pandoc_cmd_index+=("--standalone")

    _log DEBUG "Pandoc command for index page will be constructed as follows:"
    _log DEBUG "Base command: pandoc"
    _log DEBUG "CSS arg if theme applies: --css ${QSG_CONFIG[pandoc_css_path_arg]:-(not set or expected CSS file not found in output)}"
    _log DEBUG "Metadata file arg: --metadata-file /dev/stdin"
    _log DEBUG "Template arg: --template=${layout_index_file}"
    _log DEBUG "Output arg: --output=${output_index_file}"
    _log DEBUG "Other args: --standalone"
    _log DEBUG "Final pandoc_cmd_index array: ${(q+)pandoc_cmd_index}"

    local pandoc_exit_code=0
    local stderr_content=""
    
    # Execute Pandoc with YAML content piped to stdin, capturing stderr in memory
    _log DEBUG "Executing Pandoc command for index page with set -x..."
    set -x # Enable command tracing
    
    # Use process substitution to capture stderr without temporary files
    exec 3>&1 # Save stdout to fd 3
    stderr_content=$( { printf '%s\n' "$yaml_content" | "${pandoc_cmd_index[@]}" 1>&3; } 2>&1 )
    pandoc_exit_code=$?
    exec 3>&- # Close fd 3
    
    set +x # Disable command tracing
    _log DEBUG "Pandoc execution finished. Original exit status from Pandoc: $pandoc_exit_code"

    if [[ $pandoc_exit_code -ne 0 ]]; then
        _log ERROR "Pandoc command failed for '$output_index_file' with exit code: $pandoc_exit_code."
        if [[ -n "$stderr_content" ]]; then
            _log ERROR "Pandoc stderr:\n$stderr_content"
        fi
        _log ERROR "YAML content passed to Pandoc (first 500 chars):\n${yaml_content:0:500}"
        return $pandoc_exit_code # Return Pandoc's non-zero exit code
    elif [[ -n "$stderr_content" ]]; then
        # Pandoc exited 0 but wrote to stderr. Check for known fatal error patterns.
        if echo "$stderr_content" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
            _log ERROR "Pandoc reported a critical error for '$output_index_file' (exit code 0). Treating as failure."
            _log ERROR "Pandoc stderr:\n$stderr_content"
            _log ERROR "YAML content passed to Pandoc (first 500 chars):\n${yaml_content:0:500}"
            return 1 # Force a failure status
        else
            _log WARNING "Pandoc succeeded for '$output_index_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content"
            # Continue, as it might be a non-fatal warning from Pandoc.
        fi    
    else
        _log DEBUG "Successfully generated $output_index_file"
    fi
    
    return 0 # If we reached here, it's success or a warning we decided to ignore.
}

_process_markdown_files() {
    _log DEBUG "Entered _process_markdown_files"
    _log INFO "Processing Markdown files..."

    local content_dir="${QSG_CONFIG[paths_content_dir]}"
    local output_dir="${QSG_CONFIG[paths_output_dir]}"
    local layouts_dir="${QSG_CONFIG[paths_layouts_dir]}"
    local default_post_template="$layouts_dir/post.html"
    local default_page_template="$layouts_dir/page.html"

    if [[ ! -f "$default_post_template" ]]; then
        _log WARNING "Default post template not found: $default_post_template. Posts may not render correctly."
    fi
    if [[ ! -f "$default_page_template" ]]; then
        _log WARNING "Default page template not found: $default_page_template. Pages may not render correctly."
    fi

    # Find all markdown files and process them
    while IFS= read -r source_file; do
        if [[ -z "$source_file" ]]; then continue; fi
        _log DEBUG "Processing Markdown file: $source_file"

        local relative_path_from_content_root="${source_file#${QSG_CONFIG[paths_content_dir]}/}"
        if [[ "$content_dir" == "$source_file" ]]; then 
            relative_path_from_content_root=$(basename "$source_file")
        elif [[ "$content_dir" == "/" && "$source_file" == /* ]]; then
             relative_path_from_content_root="${source_file#/}"
        elif [[ "$content_dir" == "." && "$source_file" == ./* ]]; then
             relative_path_from_content_root="${source_file#./}"
        fi

        local dir_part=""
        local filename_md_part=""

        if [[ "$relative_path_from_content_root" == */* ]]; then
            dir_part="${relative_path_from_content_root%/*}/"
            filename_md_part="${relative_path_from_content_root##*/}"
        else
            dir_part=""
            filename_md_part="$relative_path_from_content_root"
        fi

        local filename_base_orig="${filename_md_part%.md}"

        # Sanitize filename_base_orig
        local sanitized_filename_base="${filename_base_orig:l}" # Lowercase
        sanitized_filename_base=${sanitized_filename_base//[[:space:],;\']/'-'} # Replace spaces, commas, semicolons, apostrophes
        
        # Consolidate multiple hyphens into one
        sanitized_filename_base=$(echo "$sanitized_filename_base" | tr -s '-')
        
        # Remove leading hyphens
        while [[ "$sanitized_filename_base" == -* ]]; do
            sanitized_filename_base="${sanitized_filename_base#-}"
        done
        # Remove trailing hyphens
        while [[ "$sanitized_filename_base" == *- ]]; do
            sanitized_filename_base="${sanitized_filename_base%-}"
        done

        if [[ -z "$sanitized_filename_base" ]]; then
            _log WARNING "Original filename base '$filename_base_orig' from '$source_file' resulted in empty sanitized name. Using 'untitled' as fallback."
            sanitized_filename_base="untitled"
        fi

        local frontmatter=$(sed -n '/^---$/,/^---$/{/^---$/d;p;}' "$source_file")
        local title=$(echo "$frontmatter" | grep -m1 -iE '^title:' | sed -E 's/^title:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')
        local date=$(echo "$frontmatter" | grep -m1 -iE '^date:' | sed -E 's/^date:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')
        local draft=$(echo "$frontmatter" | grep -m1 -iE '^draft:' | sed -E 's/^draft:[[:space:]]*//i' | tr '[:upper:]' '[:lower:]')
        local custom_layout_fm=$(echo "$frontmatter" | grep -m1 -iE '^layout:' | sed -E 's/^layout:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')

        if [[ "$draft" == "true" && "${QSG_CONFIG[build_options_process_drafts]}" != "true" ]]; then
            _log DEBUG "Skipping draft file: $source_file"
            continue
        fi

        if [[ -z "$title" ]]; then
            _log WARNING "Markdown file '$source_file' is missing a title. Using filename_base_orig ('$filename_base_orig') as fallback."
            title="$filename_base_orig"
        fi

        # Determine output URL directory structure
        local output_url_dir_part=""
        # The variable 'relative_path_from_content_root' holds the path relative to 'content_dir' (e.g., posts/my-post.md)
        # The variable 'date' is extracted from frontmatter earlier
        # The variable 'dir_part' holds the original source directory (e.g., "posts/" or "pages/")

        if [[ "$relative_path_from_content_root" == posts/* ]]; then # Check if it's a blog post from content/posts/
            if [[ -n "$date" ]]; then # Date was extracted earlier from frontmatter
                # Assuming date is YYYY-MM-DD
                local parsed_year=$(echo "$date" | awk -F'-' '{print $1}')
                local parsed_month=$(echo "$date" | awk -F'-' '{print $2}')
                local parsed_day=$(echo "$date" | awk -F'-' '{print $3}')

                local valid_date_parts=true
                if ! [[ "$parsed_year" =~ ^[0-9]{4}$ ]]; then valid_date_parts=false; fi
                if ! [[ "$parsed_month" =~ ^(0[1-9]|1[0-2])$ ]]; then valid_date_parts=false; fi # Validates MM is 01-12
                if ! [[ "$parsed_day" =~ ^(0[1-9]|[12][0-9]|3[01])$ ]]; then valid_date_parts=false; fi # Validates DD is 01-31

                if [[ "$valid_date_parts" == true ]]; then
                    output_url_dir_part="blog/$parsed_year/$parsed_month/$parsed_day/"
                else
                    _log WARNING "Blog post '$source_file' (date: '$date') has invalid date components. Required: YYYY-MM-DD. URL will be 'blog/${sanitized_filename_base}.html'."
                    output_url_dir_part="blog/"
                fi
            else
                _log WARNING "Blog post '$source_file' is missing a date. URL will be 'blog/${sanitized_filename_base}.html'."
                output_url_dir_part="blog/"
            fi
        else # It's a regular page, not a blog post from content/posts/
            output_url_dir_part="$dir_part" # Use its original directory structure (e.g., "pages/" or "" for root files)
        fi

        local output_file_html_part="${output_url_dir_part}${sanitized_filename_base}.html"
        local output_file_abs="$output_dir/$output_file_html_part"

        _log DEBUG "Source: $source_file -> Output URL Path: $output_file_html_part (Original base: '$filename_base_orig', Sanitized: '$sanitized_filename_base')"

        mkdir -p "$(dirname "$output_file_abs")"

        local template_to_use=""
        if [[ -n "$custom_layout_fm" ]]; then
            if [[ "$custom_layout_fm" != *.html && "$custom_layout_fm" != *.xml ]]; then custom_layout_fm+=".html"; fi
            template_to_use="$layouts_dir/$custom_layout_fm"
            if [[ ! -f "$template_to_use" ]]; then
                _log WARNING "Custom layout '$custom_layout_fm' from '$source_file' not found at '$template_to_use'. Falling back."
                template_to_use=""
            fi
        fi

        if [[ -z "$template_to_use" ]]; then
            if [[ "$relative_path_from_content_root" == posts/* && -f "$default_post_template" ]]; then
                template_to_use="$default_post_template"
            elif [[ -f "$default_page_template" ]]; then
                template_to_use="$default_page_template"
            elif [[ -f "$default_post_template" ]]; then
                 _log DEBUG "Default page template missing, falling back to default post template for $source_file"
                 template_to_use="$default_post_template"
            else
                _log ERROR "No suitable default template found (post.html or page.html) for '$source_file'. Skipping."
                continue
            fi
        fi
        
        if [[ ! -f "$template_to_use" ]]; then
            _log ERROR "Template '$template_to_use' for '$source_file' is not a valid file. Skipping."
            continue
        fi

        local pandoc_cmd=(
            pandoc "$source_file"
            --to html5
            --output "$output_file_abs"
            --standalone
            --template "$template_to_use"
        )

        pandoc_cmd+=(--metadata "title=$title")
        if [[ -n "$date" ]]; then
            pandoc_cmd+=(--metadata "date=$date")
        fi
        for key val in "${(@kv)QSG_CONFIG}"; do
            if [[ "$key" == site_* || "$key" == author_* ]]; then
                pandoc_cmd+=(--metadata "$key=$val")
            fi
        done
        pandoc_cmd+=(--metadata "current_year=$(date +%Y)")

        # Add CSS if specified (pandoc_css_path_arg is derived in main)
        if [[ -n "${QSG_CONFIG[pandoc_css_path_arg]:-}" ]]; then
            pandoc_cmd+=("--css" "${QSG_CONFIG[pandoc_css_path_arg]}")
            _log DEBUG "Using CSS for post $source_file: ${QSG_CONFIG[pandoc_css_path_arg]}"
        else
            _log DEBUG "No CSS specified for post $source_file."
        fi

        _log DEBUG "Executing Pandoc for $source_file. Command constructed with individual arguments (see array definition)."
        "${pandoc_cmd[@]}"
        local pandoc_exit_code=$?

        if [[ $pandoc_exit_code -eq 0 ]]; then
            _log DEBUG "Successfully processed $source_file to $output_file_abs"
            # Add to sitemap URLs
            if [[ "${QSG_CONFIG[build_options_generate_sitemap]}" == "true" ]]; then
                SITEMAP_URLS+=("$output_file_html_part")
                _log DEBUG "Added to sitemap URLs: $output_file_html_part"
            fi
        else
            _log ERROR "Pandoc failed for $source_file (exit code: $pandoc_exit_code). Output: $output_file_abs"
            # Optionally, decide if one failure should stop all, or just skip this file
            # For now, we continue processing other files
            continue
        fi
        
    done < <(find "$content_dir" -type f -name '*.md' -print0 | xargs -0 -r realpath)

    _log INFO "Finished processing Markdown files."
    return 0
}

_generate_rss_feed() {
    _log DEBUG "Entered _generate_rss_feed"

    if [[ "${QSG_CONFIG[build_options_generate_rss]}" != "true" ]]; then
        _log INFO "RSS feed generation is disabled in configuration. Skipping."
        return 0
    fi

    _log INFO "Generating RSS feed..."
    local content_posts_dir="${QSG_CONFIG[paths_content_dir]}/posts"
    local output_rss_file="${QSG_CONFIG[paths_output_dir]}/rss.xml"
    local layout_rss_file="${QSG_CONFIG[paths_layouts_dir]}/rss.xml"

    if [[ ! -d "$content_posts_dir" ]]; then
        _log WARNING "Posts directory for RSS ('$content_posts_dir') not found. Cannot generate RSS feed."
        return 0
    fi

    if [[ ! -f "$layout_rss_file" ]]; then
        _log ERROR "RSS layout file not found: $layout_rss_file. Cannot generate RSS feed."
        return 1
    fi

    local post_details=()
    local md_file
    # Use find with xargs and realpath for robust path handling, then loop
    while IFS= read -r md_file; do
        if [[ -z "$md_file" ]]; then continue; fi
        _log DEBUG "Processing $md_file for RSS feed."

        local frontmatter=$(sed -n '/^---$/,/^---$/{/^---$/d;p;}' "$md_file")
        if [[ -z "$frontmatter" ]]; then
            _log DEBUG "No YAML frontmatter found in $md_file for RSS. Skipping."
            continue
        fi

        local title=$(echo "$frontmatter" | grep -m1 -iE '^title:' | sed -E 's/^title:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')
        local date_iso=$(echo "$frontmatter" | grep -m1 -iE '^date:' | sed -E 's/^date:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')
        local summary=$(echo "$frontmatter" | grep -m1 -iE '^summary:' | sed -E 's/^summary:[[:space:]]*//i; s/^[\"\x27](.*)[\"\x27]$/\1/')
        local draft=$(echo "$frontmatter" | grep -m1 -iE '^draft:' | sed -E 's/^draft:[[:space:]]*//i' | tr '[:upper:]' '[:lower:]')

        if [[ "$draft" == "true" && "${QSG_CONFIG[build_options_process_drafts]}" != "true" ]]; then
            _log DEBUG "Skipping draft post for RSS: $md_file"
            continue
        fi
        if [[ -z "$title" ]]; then
            _log WARNING "Post '$md_file' is missing a title. Skipping for RSS."
            continue
        fi
        if [[ -z "$date_iso" ]]; then
            _log WARNING "Post '$md_file' is missing a date. Skipping for RSS."
            continue
        fi

        local relative_path_from_content_root="${md_file#${QSG_CONFIG[paths_content_dir]}/}"
        local post_url="${QSG_CONFIG[site_url]}/${relative_path_from_content_root%.md}.html"
        
        local rfc822_date
        # Attempt to convert date to RFC-822 format for RSS
        # GNU date syntax for -d and -R
        if rfc822_date=$(date -d "$date_iso" +'%a, %d %b %Y %H:%M:%S %z' 2>/dev/null); then
            _log DEBUG "Converted date '$date_iso' to RFC-822 '$rfc822_date' for $md_file"
        else
            _log WARNING "Could not convert date '$date_iso' to RFC-822 for $md_file. Using original."
            rfc822_date="$date_iso" # Fallback to original if conversion fails
        fi

        local sort_key="${date_iso:-0000-00-00}" # Use ISO date for sorting
        post_details+=("${sort_key}|${title}|${post_url}|${rfc822_date}|${summary:-No summary available.}")
    done < <(find "$content_posts_dir" -type f -name '*.md' -print0 | xargs -0 -r realpath)

    local sorted_posts=()
    if [[ ${#post_details[@]} -gt 0 ]]; then
      OIFS="$IFS"; IFS=$'\n'
      # Sort by ISO date (first field), descending
      sorted_posts=($(printf "%s\n" "${post_details[@]}" | sort -t'|' -k1,1r))
      IFS="$OIFS"
    fi

    local yaml_lines=()
    local site_title_for_feed="${QSG_CONFIG[site_title]:-${QSG_CONFIG[site_name]}}"
    local feed_url="${QSG_CONFIG[site_url]}/rss.xml"
    local current_rfc822_date=$(date -R)

    yaml_lines+=( "$(printf 'site_name: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_name]}")")" )
    yaml_lines+=( "$(printf 'site_tagline: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_tagline]}")")" )
    yaml_lines+=( "$(printf 'site_url: "%s"' "$(_yaml_escape_val "${QSG_CONFIG[site_url]}")")" )
    yaml_lines+=( "$(printf 'feed_url: "%s"' "$(_yaml_escape_val "$feed_url")")" )
    yaml_lines+=( "$(printf 'feed_title: "%s"' "$(_yaml_escape_val "$site_title_for_feed Feed")")" ) # For <title> in RSS
    yaml_lines+=( "$(printf 'current_rfc822_date: "%s"' "$(_yaml_escape_val "$current_rfc822_date")")" ) # For <pubDate> of the feed itself
    yaml_lines+=( "$(printf 'current_year: "%s"' "$(date +%Y)")" )
    yaml_lines+=( "$(printf 'posts:')" )

    if [[ ${#sorted_posts[@]} -eq 0 ]]; then
        _log DEBUG "No posts found to add to RSS YAML."
        yaml_lines+=( "$(printf '  [] # Explicit empty list for posts')" )
    else
        for post_line in "${sorted_posts[@]}"; do
            # Parse the post_line: sort_key|title|url|rfc822_date|summary
            IFS='|' read -r sort_key title url rfc822_date summary <<< "$post_line"
            
            yaml_lines+=( "$(printf '  - post_title: "%s"' "$(_yaml_escape_val "$title")")" )
            yaml_lines+=( "$(printf '    post_url: "%s"' "$(_yaml_escape_val "$url")")" )
            yaml_lines+=( "$(printf '    post_date_rfc822: "%s"' "$(_yaml_escape_val "$rfc822_date")")" )
            yaml_lines+=( "$(printf '    post_summary: "%s"' "$(_yaml_escape_val "$summary")")" )
        done
    fi

    # Join YAML lines into a single string
    local yaml_content
    OIFS="$IFS"; IFS=$'\n'
    yaml_content="${yaml_lines[*]}"
    IFS="$OIFS"

    _log DEBUG "Generated YAML content for RSS (first 300 chars): ${yaml_content:0:300}..."

    # Build Pandoc command array for RSS
    local pandoc_cmd_rss=(
        "pandoc"
        "--to" "html5"
        "--metadata" "pagetitle=RSS Feed"
        "--metadata-file" "/dev/stdin"
        "--template=${layout_rss_file}"
        "--output=${output_rss_file}"
        "--standalone"
    )

    _log DEBUG "Pandoc command for RSS feed will be constructed as follows:"
    _log DEBUG "Base command: pandoc --to html5 --metadata pagetitle=\"RSS Feed\""
    _log DEBUG "Metadata file arg: --metadata-file /dev/stdin"
    _log DEBUG "Template arg: --template=${layout_rss_file}"
    _log DEBUG "Output arg: --output=${output_rss_file}"
    _log DEBUG "Other args: --standalone"
    _log DEBUG "Final pandoc_cmd_rss array: ${(q+)pandoc_cmd_rss}"

    local pandoc_exit_code_rss=0
    local stderr_content_rss=""
    
    # Execute Pandoc with YAML content piped to stdin, capturing stderr in memory
    _log DEBUG "Executing Pandoc command for RSS feed with set -x..."
    set -x # Enable command tracing
    
    # Use process substitution to capture stderr without temporary files
    exec 3>&1 # Save stdout to fd 3
    stderr_content_rss=$( { printf '%s\n' "$yaml_content" | "${pandoc_cmd_rss[@]}" 1>&3; } 2>&1 )
    pandoc_exit_code_rss=$?
    exec 3>&- # Close fd 3
    
    set +x # Disable command tracing
    _log DEBUG "Pandoc execution finished for RSS. Original exit status from Pandoc: $pandoc_exit_code_rss"

    if [[ $pandoc_exit_code_rss -ne 0 ]]; then
        _log ERROR "Pandoc command failed for '$output_rss_file' with exit code: $pandoc_exit_code_rss."
        if [[ -n "$stderr_content_rss" ]]; then
            _log ERROR "Pandoc stderr:\n$stderr_content_rss"
        fi
        _log ERROR "YAML content passed to Pandoc for RSS (first 500 chars):\n${yaml_content:0:500}"
        return $pandoc_exit_code_rss # Return Pandoc's non-zero exit code
    elif [[ -n "$stderr_content_rss" ]]; then
        if echo "$stderr_content_rss" | grep -q -iE 'YAML parse exception|template error|could not find|error reading file'; then
            _log ERROR "Pandoc reported a critical error for '$output_rss_file' (exit code 0). Treating as failure."
            _log ERROR "Pandoc stderr:\n$stderr_content_rss"
            _log ERROR "YAML content passed to Pandoc for RSS (first 500 chars):\n${yaml_content:0:500}"
            return 1 # Force a failure status
        else
            _log WARNING "Pandoc succeeded for '$output_rss_file' (exit code 0) but produced stderr (non-critical):\n$stderr_content_rss"
        fi    
    else
        _log DEBUG "Successfully generated $output_rss_file"
    fi

    return 0 # If we reached here, it's success or a warning we decided to ignore.
}

main() {
    local custom_config_file_arg=""

    # Option parsing
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -h|--help)
                _usage
                ;;
            -V|--version)
                echo "$QSGEN_NAME Version: $VERSION"
                exit 0
                ;;
            -c|--config)
                if [[ -n "$2" ]]; then
                    custom_config_file_arg="$2"
                    shift 2
                else
                    _log ERROR "Option --config requires an argument."
                    _usage # Will exit
                fi
                ;;
            --)
                shift
                break
                ;;
            -*)
                _log ERROR "Unknown option: $1"
                _usage # Will exit
                ;;
            *)
                break
                ;;
        esac
    done

    # Resolve CONFIG_FILE path
    # PROJECT_ROOT is already set globally to $PWD
    if [[ -n "$custom_config_file_arg" ]]; then
        if [[ "$custom_config_file_arg" == /* ]]; then # Absolute path
            CONFIG_FILE="$custom_config_file_arg"
        else # Relative path, resolve against PROJECT_ROOT
            CONFIG_FILE="$PROJECT_ROOT/$custom_config_file_arg"
        fi
    else
        # Use default CONFIG_FILE_NAME in PROJECT_ROOT
        CONFIG_FILE="$PROJECT_ROOT/$CONFIG_FILE_NAME"
    fi

    _log INFO "Using Project Root: $PROJECT_ROOT"
    _log INFO "Effective Configuration File: $CONFIG_FILE"

    _check_dependencies
    # set -x # Enable command tracing (use external zsh -x if needed)
    _load_config

    # --- Theme Configuration --- 
    _log DEBUG "Processing theme configuration..."
    local theme_name="${QSG_CONFIG[site_theme]:-}"
    QSG_CONFIG[theme_static_source_dir]="" # Initialize. Source for theme's static files.
    # QSG_CONFIG[paths_layouts_dir] is loaded from site.conf. It will be overridden if theme provides layouts.
    # QSG_CONFIG[paths_static_dir] is loaded from site.conf (e.g. "static"). This is the root static dir.

    if [[ -n "$theme_name" ]]; then
        _log INFO "Site theme specified: '$theme_name'"
        local theme_base_path="$PROJECT_ROOT/themes/$theme_name"

        if [[ ! -d "$theme_base_path" ]]; then
            _log WARNING "Theme directory '$theme_base_path' not found for theme '$theme_name'. Theme will not be applied."
        else
            _log DEBUG "Theme directory found: '$theme_base_path'"

            # 1. Theme Layouts: Override paths_layouts_dir if theme provides layouts
            local theme_layouts_path="$theme_base_path/layouts"
            if [[ -d "$theme_layouts_path" ]]; then
                _log INFO "Using layouts from theme '$theme_name': $theme_layouts_path"
                QSG_CONFIG[paths_layouts_dir]="$theme_layouts_path" # This now points to theme layouts
            else
                _log DEBUG "No 'layouts' directory in theme '$theme_name'. Using layouts from '${QSG_CONFIG[paths_layouts_dir]}'."
            fi

            # 2. Theme Static Files Source: Set theme_static_source_dir
            local theme_static_standard_path="$theme_base_path/static"
            if [[ -d "$theme_static_standard_path" ]]; then
                _log INFO "Theme '$theme_name' provides static files from standard path: $theme_static_standard_path"
                QSG_CONFIG[theme_static_source_dir]="$theme_static_standard_path"
            elif [[ -d "$theme_base_path" ]]; then
                # If themes/theme_name/static doesn't exist,
                # check if themes/theme_name itself can serve as a base for static assets (e.g. themes/theme_name/css)
                _log INFO "Theme '$theme_name' provides static files from theme base path: $theme_base_path"
                QSG_CONFIG[theme_static_source_dir]="$theme_base_path"
            else
                _log DEBUG "No 'static' directory found at '$theme_static_standard_path' and theme base path '$theme_base_path' is not a valid fallback."
                # QSG_CONFIG[theme_static_source_dir] remains as initialized (empty)
            fi
        fi
    else
        _log DEBUG "No site_theme specified. Using default project paths for layouts and static files."
    fi


    # For debugging: print loaded config
    # for key val in "${(@kv)QSG_CONFIG}"; do
    #    _log DEBUG "Config: $key = $val"
    # done

    _clean_output_dir
    if [[ $? -ne 0 ]]; then _log ERROR "Cleaning output directory failed."; exit 1; fi

    _copy_static_files
    if [[ $? -ne 0 ]]; then _log ERROR "Copying static files failed."; exit 1; fi

    # --- Determine Final CSS path for Pandoc --- (Moved here to check after files are copied)
    QSG_CONFIG[pandoc_css_path_arg]="" 
    local site_css_file_spec="${QSG_CONFIG[site_theme_css_file]:-}"

    if [[ -n "$site_css_file_spec" ]]; then
        # site_css_file_spec is the name/path of the CSS file as specified in site.conf,
        # e.g., "minimaltemplate-v1.css" or "css/custom.css".
        # This path is relative to what _copy_static_files copies into output/static/.
        local css_path_in_output_static="$site_css_file_spec"
        local output_dir_from_config_main="${QSG_CONFIG[paths_output_dir]:-output}" # Default to "output" if not set in main
        local base_output_dir_abs_main=""
        if [[ "$output_dir_from_config_main" == /* ]]; then
            base_output_dir_abs_main="$output_dir_from_config_main"
        else
            base_output_dir_abs_main="$PROJECT_ROOT/$output_dir_from_config_main"
        fi
        local expected_css_file_abs_path="$base_output_dir_abs_main/static/$css_path_in_output_static"

        if [[ -f "$expected_css_file_abs_path" ]]; then
            QSG_CONFIG[pandoc_css_path_arg]="/static/$css_path_in_output_static"
            _log DEBUG "Pandoc CSS path set to: ${QSG_CONFIG[pandoc_css_path_arg]} (file exists: $expected_css_file_abs_path)"
        else
            _log WARNING "Specified 'site_theme_css_file' ('$site_css_file_spec') not found at '$expected_css_file_abs_path' after copying static files. CSS may not be applied via this setting."
        fi
    fi

    # Fallback to old site_theme_css_path if pandoc_css_path_arg is still empty
    if [[ -z "${QSG_CONFIG[pandoc_css_path_arg]}" ]]; then
        local old_direct_css_path="${QSG_CONFIG[site_theme_css_path]:-}"
        if [[ -n "$old_direct_css_path" ]]; then
            if [[ "$old_direct_css_path" == static/* || "$old_direct_css_path" == /static/* ]]; then
                 QSG_CONFIG[pandoc_css_path_arg]="$(echo "$old_direct_css_path" | sed 's|^/*static|/static|')"
            else 
                 QSG_CONFIG[pandoc_css_path_arg]="/$old_direct_css_path"
            fi
            _log DEBUG "Using fallback 'site_theme_css_path' for Pandoc CSS argument: '${QSG_CONFIG[pandoc_css_path_arg]}'"
            _log WARNING "Consider migrating to 'site_theme_css_file' in site.conf for clearer CSS management."
        elif [[ -n "$site_css_file_spec" ]]; then
            # This case: site_theme_css_file was specified, but the file was not found, and no old_direct_css_path fallback.
            _log DEBUG "Specified 'site_theme_css_file' ('$site_css_file_spec') was not found, and no fallback 'site_theme_css_path' provided. No --css flag for Pandoc."
        else
            # This case: neither site_theme_css_file nor old_direct_css_path were specified.
            _log DEBUG "No CSS file specified via 'site_theme_css_file' or 'site_theme_css_path'. No --css flag for Pandoc."
        fi
    fi

    _process_markdown_files
    if [[ $? -ne 0 ]]; then _log ERROR "Processing Markdown files failed."; exit 1; fi

    _generate_index_page
    if [[ $? -ne 0 ]]; then _log ERROR "Index page generation failed."; exit 1; fi

    _generate_rss_feed
    if [[ $? -ne 0 ]]; then _log ERROR "RSS feed generation failed."; exit 1; fi

    _minify_output_directory
    if [[ $? -ne 0 ]]; then _log WARNING "Minification encountered issues."; fi # Non-fatal, just a warning

    _generate_sitemap
    if [[ $? -ne 0 ]]; then _log WARNING "Sitemap generation encountered issues."; fi # Non-fatal, just a warning

    _log INFO "Final state of output directory (${QSG_CONFIG[paths_output_dir]}):
$(ls -R "${QSG_CONFIG[paths_output_dir]}" 2>&1)"
    _log SUCCESS "Site generation complete! Output: ${QSG_CONFIG[paths_output_dir]}"
    # set +x # Disable command tracing
}

# Run main function
main "$@"
