<?php

if (! defined('ABSPATH')) {
    exit;
} final class PufferGo_Page_Translator
{
    public const STATUS_NONE = 'none';

    public const STATUS_TRANSLATED = 'translated';

    public const STATUS_STALE = 'stale';

    public const STATUS_MANUAL = 'manual';

    public const STATUS_EDITED = 'edited';

    public const META_SOURCE = '_puffergo_translated_from';

    public const META_TRANSLATED = '_puffergo_translated_at';

    public const META_SRC_MODIFIED = '_puffergo_translated_src_modified';

    public const SEO_FIELDS = array('seo_title' => array('rank_math_title', '_yoast_wpseo_title', 'meta'), 'seo_desc' => array('rank_math_description', '_yoast_wpseo_metadesc', 'meta'), );

    private static function is_trashed(int $post_id): bool
    {
        return $post_id > 0 && 'trash' === get_post_status($post_id);
    }

    public static function status_for(int $source_id, string $lang): array
    {
        $target_id = PufferGo_Polylang::get_translation($source_id, $lang);

        if ($target_id <= 0 || $target_id === $source_id || self::is_trashed($target_id)) {
            return array('status' => self::STATUS_NONE, 'targetId' => 0, 'editUrl' => '', 'viewUrl' => '', );
        } $has_source = '' !== (string) get_post_meta($target_id, self::META_SOURCE, true);
        $has_translated = '' !== (string) get_post_meta($target_id, self::META_TRANSLATED, true);

        if (! $has_source && ! $has_translated) {
            $status = self::STATUS_MANUAL;
        } elseif (! $has_translated) {
            $status = self::STATUS_NONE;
        } else {
            $status = self::STATUS_TRANSLATED;
            $translated_at_gmt = (string) get_post_meta($target_id, self::META_TRANSLATED, true);
            $target_modified = (string) get_post_field('post_modified_gmt', $target_id);

            if ('' !== $translated_at_gmt && '' !== $target_modified && strtotime($target_modified) > strtotime($translated_at_gmt)) {
                $status = self::STATUS_EDITED;
            } else {
                $recorded_src = (int) get_post_meta($target_id, self::META_SOURCE, true);
                $src_modified = $recorded_src > 0 ? (string) get_post_field('post_modified_gmt', $recorded_src) : '';
                $translated_at = (string) get_post_meta($target_id, self::META_SRC_MODIFIED, true);

                if ('' !== $translated_at && '' !== $src_modified && strtotime($src_modified) > strtotime($translated_at)) {
                    $status = self::STATUS_STALE;
                }
            }
        }

return array('status' => $status, 'targetId' => $target_id, 'editUrl' => (string) get_edit_post_link($target_id, 'raw'), 'viewUrl' => (string) get_permalink($target_id), );
    }

    public static function ensure_translation(int $source_id, string $lang): int
    {
        $existing = PufferGo_Polylang::get_translation($source_id, $lang);

        if ($existing > 0 && $existing !== $source_id) {
            if (self::is_trashed($existing)) {
                wp_untrash_post($existing);
            } self::clone_source_meta($source_id, $existing);

            return $existing;
        } $source = get_post($source_id);

        if (! $source instanceof WP_Post) {
            throw new Exception(esc_html__('Source page not found.', 'puffergo'));
        } $target_id = PufferGo_Shared_Slug::scope_language($lang, static function () use ($source) { return wp_insert_post(array('post_type' => $source->post_type, 'post_title' => $source->post_title, 'post_name' => $source->post_name, 'post_content' => wp_slash($source->post_content), 'post_excerpt' => $source->post_excerpt, 'post_status' => 'draft', 'post_author' => get_current_user_id(), ), true); });

        if (is_wp_error($target_id)) {
            throw new Exception($target_id->get_error_message());
        } $target_id = (int) $target_id;
        update_post_meta($target_id, '_wp_page_template', get_post_meta($source_id, '_wp_page_template', true));
        update_post_meta($target_id, '_puffergo_managed', true);
        update_post_meta($target_id, self::META_SOURCE, $source_id);
        PufferGo_Polylang::set_post_language($target_id, $lang);
        $map = self::translation_group($source_id);
        $map[ $lang ] = $target_id;
        PufferGo_Polylang::save_translations($map);
        self::clone_source_meta($source_id, $target_id);

        return $target_id;
    }

    public const META_CLONE_SKIP = array(self::META_SOURCE, self::META_TRANSLATED, self::META_SRC_MODIFIED, '_edit_lock', '_edit_last', '_wp_old_slug', '_puffergo_sample_data', );

    private static function clone_source_meta(int $source_id, int $target_id): void
    {
        $keys = array_keys(get_post_meta($source_id));

        foreach ($keys as $key) {
            if (in_array($key, self::META_CLONE_SKIP, true) || 0 === strpos($key, '_pll')) {
                continue;
            }

if (metadata_exists('post', $target_id, $key)) {
                continue;
            }

foreach (get_post_meta($source_id, $key, false) as $value) {
                add_post_meta($target_id, $key, wp_slash($value));
            }
        }
    }

    public static function ensure_manual_draft(int $source_id, string $lang): array
    {
        $target_id = self::ensure_translation($source_id, $lang);
        delete_post_meta($target_id, self::META_SOURCE);

        return self::status_for($source_id, $lang);
    }

    private static function translation_group(int $source_id): array
    {
        $map = array();

        foreach (PufferGo_Polylang::languages() as $language) {
            $slug = $language['slug'];
            $id = PufferGo_Polylang::get_translation($source_id, $slug);

            if ($id > 0) {
                $map[ $slug ] = $id;
            }
        }

return $map;
    }

    public static function translate_post(int $source_id, string $lang, bool $publish = false): array
    {
        if (! PufferGo_Polylang::is_active()) {
            throw new Exception(esc_html__('Polylang is not active.', 'puffergo'));
        } $source = get_post($source_id);

        if (! $source instanceof WP_Post) {
            throw new Exception(esc_html__('Source page not found.', 'puffergo'));
        } $source_lang = PufferGo_Polylang::get_post_language($source_id);

        if ($lang === $source_lang) {
            throw new Exception(esc_html__('Cannot translate a page into its own language.', 'puffergo'));
        } $block_reason = (string) apply_filters('puffergo_translate_precheck', '', $source, $lang);

        if ('' !== $block_reason) {
            throw new Exception(esc_html($block_reason));
        } $target_id = self::ensure_translation($source_id, $lang);
        $target_locale = PufferGo_Polylang::locale_for($lang);
        $source_locale = PufferGo_Polylang::locale_for($source_lang);
        $blocks = parse_blocks((string) $source->post_content);
        $block_translatable = new PufferGo_Block_Translatable($blocks);
        $items = $block_translatable->collect_items('');

        if ('' !== trim($source->post_title)) {
            $items[] = array('id' => 'title', 'text' => $source->post_title, 'contentType' => 'title', );
        }

if ('' !== trim((string) $source->post_excerpt)) {
            $items[] = array('id' => 'excerpt', 'text' => $source->post_excerpt, 'contentType' => 'plain', );
        } $meta_specs = self::translatable_meta_for($source->post_type);
        $meta_translatable = new PufferGo_Meta_Translatable($source, $meta_specs);
        $items = array_merge($items, $meta_translatable->collect_items(''));
        $seo_source = array();

        foreach (self::SEO_FIELDS as $item_id => $field) {
            list($rankmath_key, $yoast_key, $content_type) = $field;
            $value = (string) get_post_meta($source_id, $rankmath_key, true);

            if ('' === $value) {
                $value = (string) get_post_meta($source_id, $yoast_key, true);
            }

if ('' !== $value) {
                $seo_source[ $item_id ] = $field;
                $items[] = array('id' => $item_id, 'text' => $value, 'contentType' => $content_type, );
            }
        } $term_source = array();

        foreach (self::source_terms($source) as $term) {
            $term_source[ $term->term_id ]['taxonomy'] = $term->taxonomy;

            if (! self::term_needs_fill($term, $lang)) {
                continue;
            } $items[] = array('id' => 'term_' . $term->term_id . '_name', 'text' => $term->name, 'contentType' => 'title', );

            if ('' !== trim((string) $term->description)) {
                $items[] = array('id' => 'term_' . $term->term_id . '_desc', 'text' => $term->description, 'contentType' => 'plain', );
            }
        } $result = PufferGo_Translation_Client::translate($items, $target_locale, $source_locale);
        $translations = $result['translations'];
        $new_content = $block_translatable->apply($translations, '');
        $translated_title = isset($translations['title']) && '' !== $translations['title'] ? (string) $translations['title'] : $source->post_title;
        $translated_excerpt = isset($translations['excerpt']) && '' !== $translations['excerpt'] ? (string) $translations['excerpt'] : $source->post_excerpt;
        $updated = wp_update_post(array('ID' => $target_id, 'post_title' => $translated_title, 'post_excerpt' => wp_slash($translated_excerpt), 'post_content' => wp_slash($new_content), ), true);

        if (is_wp_error($updated)) {
            throw new Exception($updated->get_error_message());
        }

if ($publish && 'draft' === get_post_status($target_id)) {
            wp_update_post(array('ID' => $target_id, 'post_status' => 'publish'));
        }

foreach ($seo_source as $item_id => $field) {
            if (empty($translations[ $item_id ])) {
                continue;
            } list($rankmath_key, $yoast_key) = $field;
            update_post_meta($target_id, $rankmath_key, (string) $translations[ $item_id ]);
            update_post_meta($target_id, $yoast_key, (string) $translations[ $item_id ]);
        }

foreach ($meta_translatable->apply($translations, '') as $meta_key => $meta_value) {
            update_post_meta($target_id, $meta_key, wp_slash($meta_value));
        } self::apply_term_translations($term_source, $translations, $source_lang, $lang, $target_id);
        update_post_meta($target_id, self::META_SOURCE, $source_id);
        update_post_meta($target_id, self::META_TRANSLATED, current_time('mysql', true));
        update_post_meta($target_id, self::META_SRC_MODIFIED, (string) get_post_field('post_modified_gmt', $source_id));
        $status = self::status_for($source_id, $lang);

        return array('targetId' => $target_id, 'status' => $status['status'], 'editUrl' => $status['editUrl'], 'viewUrl' => $status['viewUrl'], 'creditsRemaining' => $result['creditsRemaining'], 'itemCount' => count($items), );
    }

    public const RESERVED_TAXONOMIES = array('language', 'post_translations', 'term_language', 'term_translations', );

    private static function translatable_meta_for(string $post_type): array
    {
        $map = apply_filters('puffergo_translatable_meta', array());

        return (is_array($map) && isset($map[ $post_type ]) && is_array($map[ $post_type ])) ? $map[ $post_type ] : array();
    }

    private static function source_terms(WP_Post $source): array
    {
        $terms = array();

        foreach (get_object_taxonomies($source->post_type, 'names') as $taxonomy) {
            if (in_array($taxonomy, self::RESERVED_TAXONOMIES, true)) {
                continue;
            } $found = wp_get_object_terms($source->ID, $taxonomy, array('lang' => ''));

            if (is_array($found)) {
                $terms = array_merge($terms, $found);
            }
        }

return $terms;
    }

    private static function term_needs_fill(WP_Term $term, string $lang): bool
    {
        $existing = PufferGo_Polylang::get_term_translation((int) $term->term_id, $lang);

        if ($existing <= 0 || $existing === (int) $term->term_id) {
            return true;
        } $target = get_term($existing, $term->taxonomy);

        return ! $target instanceof WP_Term || $target->name === $term->name;
    }

    public const NAME_COLLISION_GUARD = "\u{200B}";

    private static function apply_term_translations(array $term_source, array $translations, string $source_lang, string $lang, int $target_id): void
    {
        $assign = array();
        $retag = array();

        foreach ($term_source as $term_id => $info) {
            $term_id = (int) $term_id;
            $taxonomy = $info['taxonomy'];
            $source_term = get_term($term_id, $taxonomy);

            if (! $source_term instanceof WP_Term) {
                continue;
            }

if (PufferGo_Polylang::get_term_language_fresh($term_id) !== $source_lang) {
                $retag[ $term_id ] = $source_lang;
            } $target_term_id = self::resolve_target_term($source_term, $translations, $lang);

            if ($target_term_id > 0 && ! self::safe_to_tag($target_term_id, $lang)) {
                error_log(sprintf('PufferGo: refused to tag term %d as %s for %s — already a different language', $target_term_id, $lang, $taxonomy));
                $target_term_id = 0;
            }

if ($target_term_id <= 0) {
                continue;
            } PufferGo_Polylang::set_term_language($target_term_id, $lang);
            PufferGo_Polylang::merge_term_translations(array($source_lang => $term_id, $lang => $target_term_id, ));
            $assign[ $taxonomy ][] = $target_term_id;
        }

foreach ($assign as $taxonomy => $term_ids) {
            wp_set_object_terms($target_id, $term_ids, $taxonomy);
        }

foreach ($retag as $term_id => $slug) {
            PufferGo_Polylang::set_term_language($term_id, $slug);
        }
    }

    private static function safe_to_tag(int $term_id, string $lang): bool
    {
        $current = PufferGo_Polylang::get_term_language_fresh($term_id);

        return '' === $current || $lang === $current;
    }

    private static function resolve_target_term(WP_Term $source_term, array $translations, string $lang): int
    {
        $term_id = (int) $source_term->term_id;
        $taxonomy = $source_term->taxonomy;
        $existing = PufferGo_Polylang::get_term_translation_fresh($term_id, $lang);
        $existing = $existing === $term_id ? 0 : $existing;

        if ($existing > 0 && ! self::term_needs_fill($source_term, $lang)) {
            return $existing;
        } $name = isset($translations[ 'term_' . $term_id . '_name' ]) && '' !== $translations[ 'term_' . $term_id . '_name' ] ? (string) $translations[ 'term_' . $term_id . '_name' ] : $source_term->name;
        $desc = isset($translations[ 'term_' . $term_id . '_desc' ]) ? (string) $translations[ 'term_' . $term_id . '_desc' ] : (string) $source_term->description;

        if ($existing > 0) {
            $updated = wp_update_term($existing, $taxonomy, array('name' => $name, 'description' => $desc));

            if (! is_wp_error($updated)) {
                return $existing;
            } $adopted = self::accept_collision((int) $updated->get_error_data('term_exists'), $term_id, $lang);

            if ($adopted > 0) {
                return $adopted;
            }
        } $sibling = self::find_sibling_term($source_term, $lang);

        if ($sibling > 0) {
            return $sibling;
        }

return self::create_translated_term($source_term, $name, $desc, $lang);
    }

    private static function create_translated_term(WP_Term $source_term, string $name, string $desc, string $lang): int
    {
        $taxonomy = $source_term->taxonomy;
        for ($guard_len = 0; $guard_len <= 6; $guard_len++) {
            $candidate = $name . str_repeat(self::NAME_COLLISION_GUARD, $guard_len);
            $created = PufferGo_Shared_Slug::scope_language($lang, static function () use ($candidate, $taxonomy, $desc, $source_term) { return wp_insert_term($candidate, $taxonomy, array('description' => $desc, 'slug' => $source_term->slug, )); }, $source_term->slug);

            if (! is_wp_error($created)) {
                return (int) $created['term_id'];
            } $collided = self::accept_collision((int) $created->get_error_data('term_exists'), (int) $source_term->term_id, $lang);

            if ($collided > 0) {
                return $collided;
            }
        } error_log(sprintf('PufferGo: failed to create %s term "%s" for language %s', $taxonomy, $name, $lang));

        return 0;
    }

    private static function accept_collision(int $collided_id, int $source_term_id, string $lang): int
    {
        if ($collided_id <= 0 || $collided_id === $source_term_id) {
            return 0;
        }

return $lang === PufferGo_Polylang::get_term_language_fresh($collided_id) ? $collided_id : 0;
    }

    private static function find_sibling_term(WP_Term $source, string $lang): int
    {
        $found = get_terms(array('taxonomy' => $source->taxonomy, 'slug' => $source->slug, 'hide_empty' => false, 'lang' => '', ));

        if (! is_array($found)) {
            return 0;
        }

foreach ($found as $term) {
            if ((int) $term->term_id === (int) $source->term_id) {
                continue;
            }

if ($lang === PufferGo_Polylang::get_term_language_fresh((int) $term->term_id)) {
                return (int) $term->term_id;
            }
        }

return 0;
    }
}
