<?php
/**
 * Helpers for locating and preparing custom translation files.
 *
 * @package WPR_Translate
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

require_once WPR_TRANSLATE_PATH . 'includes/translation-directories.php';

/**
 * Normalize a translation domain for use within file names.
 *
 * @param string $domain Translation domain.
 *
 * @return string
 */
function wpr_translate_normalize_translation_domain( $domain ) {
    // Normalize to lowercase and trim whitespace.
    $domain = strtolower( trim( (string) $domain ) );

    if ( '' === $domain ) {
        // Empty domain is invalid.
        return '';
    }

    // Replace unsupported characters with dashes.
    $domain = preg_replace( '/[^a-z0-9_\-.]+/i', '-', $domain );
    $domain = trim( $domain, '-' );

    return $domain;
}

/**
 * Normalize a locale value for use within file names.
 *
 * @param string $locale Locale identifier.
 *
 * @return string
 */
function wpr_translate_normalize_translation_locale( $locale ) {
    // Normalize and trim the locale string.
    $locale = trim( (string) $locale );

    if ( '' === $locale ) {
        // Empty locale cannot be normalized.
        return '';
    }

    // Replace dashes with underscores for consistency.
    $locale = str_replace( '-', '_', $locale );
    $parts  = explode( '_', $locale );

    if ( empty( $parts ) ) {
        return strtolower( $locale );
    }

    $normalized = array();

    foreach ( $parts as $index => $part ) {
        $part = trim( $part );

        if ( '' === $part ) {
            // Skip empty parts from malformed locales.
            continue;
        }

        if ( 0 === $index ) {
            // Lowercase the language portion.
            $normalized[] = strtolower( $part );
            continue;
        }

        // Uppercase region/script portions.
        $normalized[] = strtoupper( $part );
    }

    if ( empty( $normalized ) ) {
        return '';
    }

    return implode( '_', $normalized );
}

/**
 * Build an absolute path to a generated MO file.
 *
 * The plugin stores compiled files inside the directory returned by
 * {@see wpr_translate_get_custom_translations_directory()}. Filenames follow the
 * `<domain>-<locale>.mo` convention after both fragments have been normalised.
 *
 * @param string $domain Translation domain.
 * @param string $locale Locale identifier.
 *
 * @return string Empty string when the path cannot be built.
 */
function wpr_translate_build_custom_mofile_path( $domain, $locale ) {
    // Resolve the directory and normalize identifiers.
    $directory = wpr_translate_get_custom_translations_directory();
    $domain    = wpr_translate_normalize_translation_domain( $domain );
    $locale    = wpr_translate_normalize_translation_locale( $locale );

    // Without a directory, domain, or locale, there is no valid location.
    if ( '' === $directory || '' === $domain || '' === $locale ) {
        return '';
    }

    // Build the final MO file path.
    return trailingslashit( $directory ) . $domain . '-' . $locale . '.mo';
}

/**
 * Locate a generated MO file for a specific domain and locale.
 *
 * This simply wraps {@see wpr_translate_build_custom_mofile_path()} to ensure the
 * resulting file exists and can be read from the filesystem.
 *
 * @param string $domain Translation domain.
 * @param string $locale Locale identifier.
 *
 * @return string Empty string when the file does not exist.
 */
function wpr_translate_locate_custom_mofile( $domain, $locale ) {
    // Build the expected MO file path.
    $mofile = wpr_translate_build_custom_mofile_path( $domain, $locale );

    if ( '' === $mofile ) {
        return '';
    }

    // Only allow paths that point to readable files to avoid load failures.
    if ( file_exists( $mofile ) && is_readable( $mofile ) ) {
        return $mofile;
    }

    return '';
}

/**
 * Store information about a MO file that WordPress attempted to load.
 *
 * The plugin uses a global registry so later runtime checks can determine
 * whether custom archives were loaded and where they originated from.
 *
 * @param string $domain Text domain for the translation file.
 * @param string $locale Locale identifier associated with the file.
 * @param string $path   Absolute path to the MO file.
 * @param string $source Indicator describing where the file originated.
 */
function wpr_translate_record_loaded_mofile( $domain, $locale, $path, $source = 'wordpress' ) {
    // Normalize inputs for consistent registry keys.
    $original_domain = (string) $domain;
    $domain          = wpr_translate_normalize_translation_domain( $original_domain );
    $locale          = wpr_translate_normalize_translation_locale( $locale );
    $path            = '' !== $path ? wp_normalize_path( (string) $path ) : '';
    $source          = (string) $source;

    // Without a path there is nothing meaningful to store.
    if ( '' === $path ) {
        return;
    }

    if ( '' === $locale ) {
        // Try to infer locale from the file name if missing.
        $extracted = wpr_translate_extract_locale_from_mofile( $path, $domain );

        if ( '' !== $extracted ) {
            $locale = wpr_translate_normalize_translation_locale( $extracted );
        }
    }

    // Attempt to infer whether the file belongs to the plugin even when it was
    // reported as coming from WordPress.
    if ( 'custom' !== $source ) {
        // Detect whether the file lives in the custom translations directory.
        $custom_directory = wpr_translate_get_custom_translations_directory();

        if ( '' !== $custom_directory ) {
            $custom_directory = wp_normalize_path( trailingslashit( $custom_directory ) );

            if ( '' !== $custom_directory && 0 === strpos( $path, $custom_directory ) ) {
                $source = 'custom';
            }
        }
    }

    // Initialise the registry if it has not yet been set up during this request.
    if ( ! isset( $GLOBALS['wpr_translate_loaded_mofiles'] ) || ! is_array( $GLOBALS['wpr_translate_loaded_mofiles'] ) ) {
        // Initialize the registry on first use.
        $GLOBALS['wpr_translate_loaded_mofiles'] = array();
    }

    // Store the registry entry keyed by domain and locale.
    $key = strtolower( $domain ) . '|' . strtolower( $locale );

    $GLOBALS['wpr_translate_loaded_mofiles'][ $key ] = array(
        'domain' => '' !== $domain ? $domain : $original_domain,
        'locale' => $locale,
        'path'   => $path,
        'source' => $source,
    );
}

/**
 * Attempt to extract a locale identifier from a MO file path.
 *
 * WordPress often embeds the locale at the end of the filename. This helper
 * understands the `<domain>-<locale>.mo` convention and a few fallbacks so the
 * loader can infer the correct language when explicit metadata is missing.
 *
 * @param string $mofile File path provided to load_textdomain().
 * @param string $domain Translation domain.
 *
 * @return string Locale identifier when available.
 */
function wpr_translate_extract_locale_from_mofile( $mofile, $domain = '' ) {
    // Read the filename so the locale can be parsed.
    $filename = basename( (string) $mofile );

    if ( '' === $filename || '.mo' !== strtolower( substr( $filename, -3 ) ) ) {
        // Only parse .mo files.
        return '';
    }

    // Remove the file extension before parsing.
    $filename = substr( $filename, 0, -3 );
    $domain   = wpr_translate_normalize_translation_domain( $domain );

    // Strip the domain prefix when present, e.g. `domain-fr_FR` → `fr_FR`.
    if ( '' !== $domain && 0 === strpos( $filename, $domain . '-' ) ) {
        return substr( $filename, strlen( $domain ) + 1 );
    }

    $last_dash = strrpos( $filename, '-' );

    if ( false !== $last_dash ) {
        $potential = substr( $filename, $last_dash + 1 );

        if ( '' !== $potential ) {
            return $potential;
        }
    }

    // Handle filenames that contain only the locale code such as `fr_FR.mo`.
    if ( preg_match( '/^[a-z]{2,3}(?:[_-][a-z0-9]{2,4}){0,2}$/i', $filename ) ) {
        return $filename;
    }

    return '';
}
