<?php
/**
 * Helpers to discover and parse gettext translation files.
 *
 * @package WPR_Translate
 */

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

/**
 * Collect normalized string entries from a path.
 *
 * @param string $path       Directory to inspect.
 * @param string $context    Context identifier.
 * @param array  $locale_map Map of locales => language codes.
 * @param array  $code_map   Map of codes => codes.
 *
 * @return array
 */
function wpr_translate_admin_collect_strings_from_path( $path, $context, $locale_map, $code_map ) {
    // Initialize the collection map.
    $strings = array();

    // Bail if the path is missing or invalid.
    if ( '' === $path || ! is_string( $path ) ) {
        return $strings;
    }

    // Locate translation files under the provided path.
    $files = wpr_translate_admin_locate_translation_files( $path, $locale_map, $code_map );

    if ( empty( $files ) ) {
        return $strings;
    }

    foreach ( $files as $file_path ) {
        // Extract raw string entries from each translation file.
        $extracted = wpr_translate_admin_extract_strings_from_translation_file( $file_path );

        if ( empty( $extracted['strings'] ) || ! is_array( $extracted['strings'] ) ) {
            continue;
        }

        // Determine the language code for this file.
        $language_code = wpr_translate_admin_determine_language_code_from_file(
            $file_path,
            isset( $extracted['locale'] ) ? $extracted['locale'] : '',
            $locale_map,
            $code_map
        );

        foreach ( $extracted['strings'] as $entry ) {
            // Normalize each original string entry.
            $value = isset( $entry['original'] ) ? (string) $entry['original'] : '';

            if ( '' === $value ) {
                continue;
            }

            $hash = md5( $context . '|' . $value );

            // Initialize the string bucket if needed.
            if ( ! isset( $strings[ $hash ] ) ) {
                $strings[ $hash ] = array(
                    'context'      => $context,
                    'name'         => 'str_' . md5( $value ),
                    'value'        => $value,
                    'md5'          => md5( $value ),
                    'translations' => array(),
                );
            }

            // Store the translation when available.
            if ( '' !== $language_code && isset( $entry['translation'] ) ) {
                $translation = (string) $entry['translation'];

                if ( '' !== trim( $translation ) ) {
                    $strings[ $hash ]['translations'][ $language_code ] = $translation;
                }
            }
        }
    }

    return $strings;
}

/**
 * Locate translation files within a path.
 *
 * @param string $path       Directory to scan.
 * @param array  $locale_map Locale to language code map.
 * @param array  $code_map   Language code map.
 *
 * @return array
 */
function wpr_translate_admin_locate_translation_files( $path, $locale_map, $code_map ) {
    // Validate the base path before scanning.
    if ( ! is_dir( $path ) || ! is_readable( $path ) ) {
        return array();
    }

    // Default to the standard /languages directory.
    $languages_path = trailingslashit( $path ) . 'languages';

    if ( ! is_dir( $languages_path ) || ! is_readable( $languages_path ) ) {
        return array();
    }

    // Gather translation files from the languages directory.
    return wpr_translate_admin_gather_language_files( $languages_path, $locale_map, $code_map );
}

/**
 * Recursively gather language files.
 *
 * @param string $path       Languages directory.
 * @param array  $locale_map Locale to language code map.
 * @param array  $code_map   Language code map.
 *
 * @return array
 */
function wpr_translate_admin_gather_language_files( $path, $locale_map, $code_map ) {
    // Start with an empty file list.
    $files = array();

    // Abort when the directory is missing or unreadable.
    if ( ! is_dir( $path ) || ! is_readable( $path ) ) {
        return $files;
    }

    try {
        $directory = new RecursiveDirectoryIterator( $path, RecursiveDirectoryIterator::SKIP_DOTS );
        $iterator  = new RecursiveIteratorIterator( $directory );
    } catch ( Exception $exception ) {
        return $files;
    }

    foreach ( $iterator as $file ) {
        // Only process readable translation files.
        if ( ! $file instanceof SplFileInfo || ! $file->isFile() ) {
            continue;
        }

        $extension = strtolower( $file->getExtension() );

        if ( ! in_array( $extension, array( 'po', 'pot', 'mo' ), true ) || ! $file->isReadable() ) {
            continue;
        }

        $file_path = $file->getPathname();

        // Include POT files or files that match configured languages.
        if ( 'pot' === $extension || wpr_translate_admin_is_translation_file_relevant( $file_path, $locale_map, $code_map ) ) {
            $files[] = $file_path;
        }
    }

    return $files;
}

/**
 * Determine if a translation file matches configured languages.
 *
 * @param string $file_path File path.
 * @param array  $locale_map Locale to language code map.
 * @param array  $code_map   Language code map.
 *
 * @return bool
 */
function wpr_translate_admin_is_translation_file_relevant( $file_path, $locale_map, $code_map ) {
    // Allow all files when no language filtering is configured.
    if ( empty( $locale_map ) && empty( $code_map ) ) {
        return true;
    }

    $candidates = array();

    // Derive potential locale candidates from the filename.
    $derived_locale = wpr_translate_admin_derive_locale_from_filename( $file_path );

    if ( '' !== $derived_locale ) {
        $candidates[] = strtolower( $derived_locale );
    }

    $derived_code = wpr_translate_admin_derive_language_code_from_filename( $file_path );

    if ( '' !== $derived_code ) {
        $candidates[] = strtolower( $derived_code );
    }

    if ( empty( $candidates ) ) {
        return true;
    }

    foreach ( $candidates as $candidate ) {
        // Match full locale or language codes.
        if ( isset( $locale_map[ $candidate ] ) || isset( $code_map[ $candidate ] ) ) {
            return true;
        }

        $parts = explode( '_', $candidate );

        if ( ! empty( $parts ) ) {
            $short = $parts[0];

            if ( isset( $code_map[ $short ] ) ) {
                return true;
            }
        }
    }

    return false;
}

/**
 * Extract strings from a translation file.
 *
 * @param string $file_path File path.
 *
 * @return array
 */
function wpr_translate_admin_extract_strings_from_translation_file( $file_path ) {
    // Determine the file extension to choose the parser.
    $extension = strtolower( pathinfo( $file_path, PATHINFO_EXTENSION ) );

    if ( in_array( $extension, array( 'po', 'pot' ), true ) ) {
        return wpr_translate_admin_extract_strings_from_po_file( $file_path );
    }

    if ( 'mo' === $extension ) {
        return wpr_translate_admin_extract_strings_from_mo_file( $file_path );
    }

    // Return empty data for unsupported file types.
    return array(
        'locale'  => '',
        'strings' => array(),
    );
}

/**
 * Extract strings from a PO/POT file.
 *
 * @param string $file_path File path.
 *
 * @return array
 */
function wpr_translate_admin_extract_strings_from_po_file( $file_path ) {
    // Load the PO parser when necessary.
    if ( ! class_exists( 'PO' ) ) {
        require_once ABSPATH . WPINC . '/pomo/po.php';
    }

    $po = new PO();

    // Abort when the file cannot be parsed.
    if ( ! $po->import_from_file( $file_path ) ) {
        return array(
            'locale'  => '',
            'strings' => array(),
        );
    }

    // Read the locale from file headers.
    $locale = '';

    if ( isset( $po->headers['Language'] ) ) {
        $locale = wpr_translate_admin_normalize_locale_value( $po->headers['Language'] );
    }

    // Convert PO entries into simplified arrays.
    $strings = array();

    foreach ( $po->entries as $entry ) {
        if ( ! isset( $entry->singular ) ) {
            continue;
        }

        $original = (string) $entry->singular;

        if ( '' === $original ) {
            continue;
        }

        $translation = '';

        if ( ! empty( $entry->translations ) && isset( $entry->translations[0] ) ) {
            $translation = (string) $entry->translations[0];
        }

        $strings[] = array(
            'original'    => $original,
            'translation' => $translation,
        );
    }

    // Fallback to filename-based locale detection.
    if ( '' === $locale ) {
        $locale = wpr_translate_admin_derive_locale_from_filename( $file_path );
    }

    return array(
        'locale'  => $locale,
        'strings' => $strings,
    );
}

/**
 * Extract strings from an MO file.
 *
 * @param string $file_path File path.
 *
 * @return array
 */
function wpr_translate_admin_extract_strings_from_mo_file( $file_path ) {
    // Load the MO parser when necessary.
    if ( ! class_exists( 'MO' ) ) {
        require_once ABSPATH . WPINC . '/pomo/mo.php';
    }

    $mo = new MO();

    // Abort when the file cannot be parsed.
    if ( ! $mo->import_from_file( $file_path ) ) {
        return array(
            'locale'  => '',
            'strings' => array(),
        );
    }

    // Read the locale from file headers.
    $locale = '';

    if ( isset( $mo->headers['Language'] ) ) {
        $locale = wpr_translate_admin_normalize_locale_value( $mo->headers['Language'] );
    }

    // Convert MO entries into simplified arrays.
    $strings = array();

    foreach ( $mo->entries as $entry ) {
        if ( ! isset( $entry->singular ) ) {
            continue;
        }

        $original = (string) $entry->singular;

        if ( '' === $original ) {
            continue;
        }

        $translation = '';

        if ( ! empty( $entry->translations ) && isset( $entry->translations[0] ) ) {
            $translation = (string) $entry->translations[0];
        }

        $strings[] = array(
            'original'    => $original,
            'translation' => $translation,
        );
    }

    // Fallback to filename-based locale detection.
    if ( '' === $locale ) {
        $locale = wpr_translate_admin_derive_locale_from_filename( $file_path );
    }

    return array(
        'locale'  => $locale,
        'strings' => $strings,
    );
}

/**
 * Determine language code for a translation file.
 *
 * @param string $file_path File path.
 * @param string $locale    Locale extracted from file metadata.
 * @param array  $locale_map Map of locales => codes.
 * @param array  $code_map   Map of codes => codes.
 *
 * @return string
 */
function wpr_translate_admin_determine_language_code_from_file( $file_path, $locale, $locale_map, $code_map ) {
    // Build a list of possible locale/code candidates.
    $candidates = array();

    if ( '' !== $locale ) {
        $candidates[] = strtolower( $locale );
    }

    $filename_locale = wpr_translate_admin_derive_locale_from_filename( $file_path );

    if ( '' !== $filename_locale ) {
        $candidates[] = strtolower( $filename_locale );
    }

    $filename_code = wpr_translate_admin_derive_language_code_from_filename( $file_path );

    if ( '' !== $filename_code ) {
        $candidates[] = strtolower( $filename_code );
    }

    foreach ( $candidates as $candidate ) {
        // Prefer direct locale or code map matches.
        if ( isset( $locale_map[ $candidate ] ) && '' !== $locale_map[ $candidate ] ) {
            return $locale_map[ $candidate ];
        }

        if ( isset( $code_map[ $candidate ] ) ) {
            return $code_map[ $candidate ];
        }

        // Fallback to short language codes.
        $parts = explode( '_', $candidate );

        if ( ! empty( $parts ) ) {
            $short = $parts[0];

            if ( isset( $code_map[ $short ] ) ) {
                return $code_map[ $short ];
            }
        }
    }

    return '';
}

/**
 * Normalize locale header values.
 *
 * @param string $locale Locale value.
 *
 * @return string
 */
function wpr_translate_admin_normalize_locale_value( $locale ) {
    // Trim and normalize separators to underscores.
    $locale = trim( (string) $locale );

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

    $locale = str_replace( '-', '_', $locale );

    return strtolower( $locale );
}

/**
 * Derive locale from file name.
 *
 * @param string $file_path File path.
 *
 * @return string
 */
function wpr_translate_admin_derive_locale_from_filename( $file_path ) {
    // Strip extension and look for locale-like substrings.
    $basename = basename( $file_path );
    $basename = preg_replace( '/\.(mo|po|pot)$/i', '', $basename );

    if ( preg_match( '/([a-z]{2,3}[_-][a-z]{2,4})/i', $basename, $matches ) ) {
        return strtolower( str_replace( '-', '_', $matches[1] ) );
    }

    return '';
}

/**
 * Derive language code from file name.
 *
 * @param string $file_path File path.
 *
 * @return string
 */
function wpr_translate_admin_derive_language_code_from_filename( $file_path ) {
    // Strip extension and extract language code tokens.
    $basename = basename( $file_path );
    $basename = preg_replace( '/\.(mo|po|pot)$/i', '', $basename );

    if ( preg_match( '/(?:^|[_-])([a-z]{2,3})(?:[_-]|$)/i', $basename, $matches ) ) {
        return strtolower( $matches[1] );
    }

    return '';
}
