scripts/utils/cache.sh
#!/bin/sh
# Always sourced file - Fake bang path to help editors
#
#   Copyright (c) 2022-2025: Jacob.Lundqvist@gmail.com
#   License: MIT
#
#   Part of https://github.com/jaclu/tmux-menus
#
#  Cache related stuff
#

#---------------------------------------------------------------
#
#   cache handling
#
#---------------------------------------------------------------

cache_create_folder() {
    # Returns 0 if already present or could be created, 1 for failure to create
    # log_it "cache_create_folder($1) - folder: $d_cache"

    # shellcheck disable=SC2154 # variable defined in settings
    $cfg_use_cache || error_msg "cache_create_folder() - called when cache is disabled"
    [ -z "$d_cache" ] && {
        error_msg "cache_create_folder() - variable d_cache undefined"
    }
    [ -d "$d_cache" ] && return 0 # already created
    mkdir -p "$d_cache" || {
        error_msg "cache_create_folder() - Failed to create cache folder: $d_cache"
    }
    return 0
}

cache_prepare() {
    #
    #  Creates cache folder if absent, call this before any cache write
    #
    #  Aborts with error if it couldn't be created
    #
    # log_it "cache_prepare() - $1"
    $cfg_use_cache || error_msg "cache_prepare() - called when not using cache"
    cache_create_folder "cache_prepare()"
}

cache_clear() {
    #
    #  Clear cache related files, since this only removes files if found
    #  it can be called also when cache is disabled.
    #

    log_it "cache_clear() $1"
    $cfg_use_cache || error_msg "cache_clear() - called when not using cache"
    [ -z "$d_cache" ] && error_msg "cache_clear() - called when d_cache is undefined"
    safe_remove "$d_cache" "cache_clear()"
    cache_prepare "cache_clear()"

    # Invalidate what might have already been sourced
    cached_ok_tmux_versions=""
    cached_bad_tmux_versions=""
}

cache_add_ok_vers() {
    #
    #  Add param to list of good versions (<=running tmux vers),
    #  if it wasn't cached already
    #
    # log_it "cache_add_ok_vers($1)"
    [ -z "$1" ] && error_msg "cache_add_ok_vers() - no param"

    case "$cached_ok_tmux_versions" in
    *"$1 "*) ;;
    *)
        # log_it "Adding ok tmux vers: $1"
        cached_ok_tmux_versions="${cached_ok_tmux_versions}$1 "
        [ "$cfg_use_cache" = true ] && cache_save_known_tmux_versions
        ;;
    esac
    return 0
}

cache_add_bad_vers() {
    #
    #  Add param to list of bad versions (>running tmux vers),
    #  if it wasn't cached already
    #
    # log_it "cache_add_bad_vers($1)"
    [ -z "$1" ] && error_msg "cache_add_bad_vers() - no param"

    case "$cached_bad_tmux_versions" in
    *"$1"*) ;;
    *)
        # log_it "Adding bad tmux vers: $1"
        cached_bad_tmux_versions="${cached_bad_tmux_versions}$1 "
        [ "$cfg_use_cache" = true ] && cache_save_known_tmux_versions
        ;;
    esac
    return 1
}

cache_save_known_tmux_versions() { # tmux stuff
    #
    #  The order the versions are saved doesn't matter,
    #  since they are checked with a case to speed things up
    #
    # log_it "cache_save_known_tmux_versions()"
    [ "$cfg_use_cache" = true ] || {
        error_msg "cache_save_known_tmux_versions() - called when not using cache"
    }
    [ -d "$d_cache" ] || {
        #
        # Not an error, the script still remembers the good/bad version. This is just
        # reminder that d_cache does not exist yet.
        # It is perfectly normal to happen once during plugin init
        #
        # log_it "WARNING: cache_save_known_tmux_versions() aborting, no cache folder: $d_cache"
        return 1
    }

    cache_prepare "cache_save_known_tmux_versions()"
    #region known tmux versions
    # shellcheck disable=SC2154 # f_cache_known_tmux_vers defined in helpers_minimal.sh
    printf '%s\n' "\
#!/bin/sh
# Autogenerated always sourced file - Fake bang path to help editors/linters

#
#  This is a list of known good/bad tmux versions, to speed up version checks
#
cached_ok_tmux_versions=\"$cached_ok_tmux_versions\"
cached_bad_tmux_versions=\"$cached_bad_tmux_versions\"" >"$f_cache_known_tmux_vers" || {
        error_msg "Failed to save known versions: $f_cache_known_tmux_vers"
    }
    #endregion
    return 0
}

cache_escape_special_chars() {
    # Escape backslashes, backticks, double quotes, and dollar signs
    printf '%s\n' "$1" | sed 's/[\\`"$]/\\&/g'
}

examine_code_base() {
    # examins state of code base, to ensure cache is cleared if anything has been
    # changed
    #
    # Public variables:
    # repo_last_changed - time stamp for rep
    # last_local_edit - timestamp and filename for last local change
    #
    # log_it "examine_code_base()"

    command -v git >/dev/null || {
        # this check depends on git being available
        log_it "examine_code_base() - no git found!"
        _m="no git installed"
        new_repo_last_changed="$_m"
        new_last_local_edit="$_m"
        return
    }

    # need to be in repo base dir for the git chcecks below
    # shellcheck disable=SC2154 # defined by $0 script
    cd "$D_TM_BASE_PATH" || {
        error_msg "examine_code_base() - Failed to cd into $D_TM_BASE_PATH"
    }

    #
    # Log last repo change and if & when latest local changes were done
    # to ensure any code changes will trigger a cache reset
    #

    # Timestamp for latest change of repo that hs been pulled
    new_repo_last_changed="$(git log -1 --format="%ad" --date=iso 2>/dev/null)"

    # Timestamp and file name for latest locally changed file
    # will be "" if no local edits have been done
    # the xargs stderr redirect is to avoid errors about removed files
    if [ "$(uname -s)" = "Darwin" ]; then
        new_last_local_edit="$(git ls-files -m 2>/dev/null |
            xargs -r stat -f '%m %N' 2>/dev/null | sort -nr | head -1)"
    else
        new_last_local_edit="$(git ls-files -m 2>/dev/null |
            xargs -r stat -c '%Y %n' 2>/dev/null | sort -nr | head -1)"
    fi
}

cache_write_plugin_params() {
    #
    #  Writes all config params to file
    #  if it differed with previous params, clear cache
    #
    # log_it "cache_write_plugin_params()"

    $cfg_use_cache || error_msg "cache_write_plugin_params() - called when not using cache"

    # cloud node .22 jacmac .25 jacpad 2.5 jacdoid 1
    check_speed_cutoff 0.5
    examine_code_base

    _f_params_tmp=$(mktemp) || {
        error_msg "cache_write_plugin_params() - Failed to create tmp config file"
    }

    #region param cache file
    # SC2154: cfg_trigger_key assigned dynamically by tmux_get_option using eval
    #         in tmux_get_plugin_options()
    # shellcheck disable=SC2154
    printf '%s\n' "\
#!/bin/sh
# Autogenerated always sourced file - Fake bang path to help editors/linters

#  This is a cache of configs for the plugin.
#  By sourcing this instead of gathering it each time, tons of time
#  is saved when generating menus.

cfg_trigger_key=\"$(cache_escape_special_chars "$cfg_trigger_key")\"
cfg_no_prefix=$cfg_no_prefix
cfg_main_menu=\"$cfg_main_menu\"

cfg_use_cache=$cfg_use_cache
" >"$_f_params_tmp"

    # SC2154: cfg_use_whiptail assigned dynamically by tmux_get_option using eval
    #         in tmux_get_plugin_options()
    # shellcheck disable=SC2154
    if $cfg_use_whiptail; then
        printf '%s\n' "\
cfg_display_cmds=$cfg_display_cmds
wt_pasting=\"$wt_pasting\"
" >>"$_f_params_tmp"
    else
        # Only use these settings if not using whiptail/dialog
        printf '%s\n' "\
cfg_mnu_loc_x=\"$cfg_mnu_loc_x\"
cfg_mnu_loc_y=\"$cfg_mnu_loc_y\"

cfg_format_title=\"$(cache_escape_special_chars "$cfg_format_title")\"" >>"$_f_params_tmp"

        tmux_vers_check 3.4 && {
            printf '%s' "\
cfg_border_type=\"$cfg_border_type\"
cfg_simple_style_selected=\"$(cache_escape_special_chars "$cfg_simple_style_selected")\"
cfg_simple_style=\"$(cache_escape_special_chars "$cfg_simple_style")\"
cfg_simple_style_border=\"$(cache_escape_special_chars "$cfg_simple_style_border")\"
" >>"$_f_params_tmp"
        }

        printf '%s\n' "cfg_display_cmds=$cfg_display_cmds" >>"$_f_params_tmp"

        $cfg_display_cmds && printf '%s\n' \
            "cfg_display_cmds_cols=\"$cfg_display_cmds_cols\"" >>"$_f_params_tmp"
    fi

    # SC2154: the cfg_ variables are assigned dynamically by tmux_get_option using eval
    #         in tmux_get_plugin_options()
    # shellcheck disable=SC2154
    printf '\n%s' "\
cfg_nav_next=\"$(cache_escape_special_chars "$cfg_nav_next")\"
cfg_nav_prev=\"$(cache_escape_special_chars "$cfg_nav_prev")\"
cfg_nav_home=\"$(cache_escape_special_chars "$cfg_nav_home")\"

cfg_use_hint_overlays=$cfg_use_hint_overlays
cfg_show_key_hints=$cfg_show_key_hints

cfg_use_whiptail=$cfg_use_whiptail
cfg_alt_menu_handler=\"$cfg_alt_menu_handler\"

cfg_tmux_conf=\"$cfg_tmux_conf\"
cfg_log_file=\"$cfg_log_file\"

cfg_use_notes=$cfg_use_notes

#
#  If tmux version has changed, the entie cache is invalidated
#
current_tmux_vers=\"$current_tmux_vers\"
current_tmux_vers_i=\"$current_tmux_vers_i\"
current_tmux_vers_suffix=\"$current_tmux_vers_suffix\"

#
# Get time stamps for repo and local file changes,
# This ensures cache is cleared any time the code has changed.
#
repo_last_changed=\"$new_repo_last_changed\"
last_local_edit=\"$new_last_local_edit\"

#
# If menu is displayed shorter than this, assume it was due to not fitting
# the screen
#
t_minimal_display_time=\"$t_minimal_display_time\"
" >>"$_f_params_tmp"
    #endregion
    # shellcheck disable=SC2154 # defined in helpers_minimal.sh
    if [ -f "$f_cache_params" ]; then
        if ! diff -q "$_f_params_tmp" "$f_cache_params" >/dev/null 2>&1; then
            # diff reports success if files don't differ, hence the !
            # If any params have changed, invalidate cache

            # error_msg "><> verify existence of: $f_cached_tmux_options" 1 dont_display

            cache_clear "=======   Environment changed:  $(
                diff "$_f_params_tmp" "$f_cache_params"
            )"
            mv "$_f_params_tmp" "$f_cache_params"
            [ -n "$cfg_log_file" ] && [ -f "$cfg_log_file" ] && {
                # not strictly needed, the cache files will be created
                # when needed, doing it here is mostly to ensure they are always
                # available for debug checks
                cache_save_options_defined_in_tmux
            }
        else
            # log_it " config unchanged - param cache not cleared"
            log_it "><> config unchanged"
            rm "$_f_params_tmp"
        fi
    else
        log_it "><> param cache created"
        mv "$_f_params_tmp" "$f_cache_params"
    fi
}

verify_tmux_vers_unchanged() {
    # Compare current tmux vers with param cache, dropping param cache fully
    # if they differ

    actual_current_tmux_vers="$current_tmux_vers"
    #

    # Source the param cache in a subshell, and only keep current_tmux_vers,
    # in order to make sure no other variables gets leaked into the env
    previous_current_tmux_vers="$(
        # source the cached params in a subshell, in order to only extract
        # current_tmux_vers for comparison

        # SC2030: Disable logging in this subshell, in order to avoid getting
        #         redundant logging about sourcing cached params
        # shellcheck disable=SC2030
        cfg_log_file=""

        # SC2030: If the subshell can't source this, make sure it doesn't inherit
        #         the current state
        # shellcheck disable=SC2030
        current_tmux_vers="unknown"

        source_cached_params
        echo "$current_tmux_vers"
    )"
    current_tmux_vers="$actual_current_tmux_vers"
    [ "$actual_current_tmux_vers" != "$previous_current_tmux_vers" ] && {
        # If tmux version has changed, the entire cache should be dropped
        _s2="tmux version change: $previous_current_tmux_vers -> $actual_current_tmux_vers"
        log_it "$_s2 - dropping entire cache"

        safe_remove "$d_cache" "verify_tmux_vers_unchanged()"

        # Clear in memory cache of good/bad tmux versions, that might have been
        # read from invalid cache
        cached_ok_tmux_versions=""
        cached_bad_tmux_versions=""
    }
}

create_param_cache() {
    # via config_setup() it is already established that @menus_use_cache was true
    # log_it "create_param_cache()"

    # most likely already called by the check for min version, but it is quick
    # enough to call again here to ensure tmux version is known
    tpt_retrieve_running_tmux_vers

    # shellcheck disable=SC2154 # initialize_plugin defined in plugin_init.sh
    [ "$initialize_plugin" = 1 ] && [ -d "$d_cache" ] && verify_tmux_vers_unchanged

    cache_prepare
    source_cached_params # get additional env config if available
    # cfg_use_whiptail=false
    tmux_get_plugin_options
    cache_write_plugin_params
}

#===============================================================
#
#   Main
#
#===============================================================

# log_it "===  Completed: scripts/utils/cache.sh  =="
