<?php

namespace Calio\Widgets;

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

/**
 * Lightweight helper methods extracted from EA Post Timeline dependencies.
 */
final class PostTimelineHelper
{
    private const ALLOWED_HTML_TAGS = [
        'article',
        'aside',
        'details',
        'dialog',
        'div',
        'figcaption',
        'figure',
        'footer',
        'h1',
        'h2',
        'h3',
        'h4',
        'h5',
        'h6',
        'header',
        'hgroup',
        'main',
        'nav',
        'p',
        'section',
        'span',
        'summary',
    ];

    /**
     * @return array<string, string>
     */
    public static function get_post_types(): array
    {
        $postTypes = get_post_types([
            'public' => true,
            'show_in_nav_menus' => true,
        ], 'objects');

        $postTypes = wp_list_pluck($postTypes, 'label', 'name');

        return array_diff_key($postTypes, [
            'elementor_library' => true,
            'attachment' => true,
        ]);
    }

    /**
     * @return array<int, string>
     */
    public static function get_post_list(string $postType = 'any'): array
    {
        return self::get_query_post_list($postType);
    }

    /**
     * @return array<int, string>
     */
    private static function get_query_post_list(string $postType = 'any', int $limit = 250, string $search = ''): array
    {
        $postType = sanitize_key($postType);
        if ($postType !== 'any') {
            $postTypeObject = get_post_type_object($postType);
            if (!$postTypeObject || !$postTypeObject->public || $postType === 'attachment') {
                $postType = 'post';
            }
        }

        $args = [
            'post_type' => $postType,
            'post_status' => 'publish',
            'posts_per_page' => max(1, min(250, $limit)),
            'orderby' => 'date',
            'order' => 'DESC',
            's' => sanitize_text_field($search),
            'fields' => 'ids',
            'no_found_rows' => true,
        ];

        if ($postType === 'any') {
            $args['post_type'] = array_values(array_diff(
                get_post_types(['exclude_from_search' => false]),
                ['attachment']
            ));
        }

        $postIds = get_posts($args);
        $options = [];

        foreach ($postIds as $postId) {
            $options[(int) $postId] = get_the_title((int) $postId);
        }

        return $options;
    }

    /**
     * @return array<string, string>
     */
    public static function get_post_orderby_options(): array
    {
        return [
            'ID' => __('Post ID', 'calio-addon'),
            'author' => __('Post Author', 'calio-addon'),
            'title' => __('Title', 'calio-addon'),
            'date' => __('Date', 'calio-addon'),
            'modified' => __('Last Modified Date', 'calio-addon'),
            'parent' => __('Parent Id', 'calio-addon'),
            'rand' => __('Random', 'calio-addon'),
            'comment_count' => __('Comment Count', 'calio-addon'),
            'most_viewed' => __('Most Viewed', 'calio-addon'),
            'menu_order' => __('Menu Order', 'calio-addon'),
            // Control option key; not a query argument at this point.
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
            'meta_value' => __('Custom Field', 'calio-addon'),
        ];
    }

    /**
     * @return array<int|string, string>
     */
    public static function get_terms_list(string $taxonomy = 'category', string $key = 'term_id'): array
    {
        $options = [];
        $terms = get_terms([
            'taxonomy' => $taxonomy,
            'hide_empty' => true,
        ]);

        if (!empty($terms) && !is_wp_error($terms)) {
            foreach ($terms as $term) {
                $termKey = $term->{$key};
                $options[is_numeric($termKey) ? (int) $termKey : (string) $termKey] = (string) $term->name;
            }
        }

        return $options;
    }

    /**
     * @return array<int, string>
     */
    public static function get_authors_list(): array
    {
        $args = [
            'capability' => ['edit_posts'],
            'has_published_posts' => true,
            'fields' => ['ID', 'display_name'],
        ];

        if (isset($GLOBALS['wp_version']) && version_compare((string) $GLOBALS['wp_version'], '5.9-alpha', '<')) {
            $args['who'] = 'authors';
            unset($args['capability']);
        }

        $users = get_users($args);

        if (!empty($users)) {
            return array_map('strval', wp_list_pluck($users, 'display_name', 'ID'));
        }

        return [];
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public static function fix_old_query(array $settings): array
    {
        foreach ($settings as $key => $value) {
            if (strpos((string) $key, 'eaeposts_') !== false) {
                $settings[str_replace('eaeposts_', '', (string) $key)] = $value;
            }
        }

        return $settings;
    }

    /**
     * @param array<string, mixed> $settings
     * @return array<string, mixed>
     */
    public static function get_query_args(array $settings = [], string $postType = 'post'): array
    {
        $settings = wp_parse_args($settings, [
            'post_type' => $postType,
            'posts_ids' => [],
            'by_id_order_mode' => 'manual',
            'orderby' => 'date',
            'order' => 'desc',
            'posts_per_page' => 4,
            'offset' => 0,
            // Elementor widget setting, not a direct WP_Query exclusion yet.
            // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
            'post__not_in' => [],
            'ignore_sticky_posts' => 'yes',
        ]);

        $orderby = sanitize_key((string) $settings['orderby']);
        if (!array_key_exists($orderby, self::get_post_orderby_options())) {
            $orderby = 'date';
        }

        $args = [
            'orderby' => $orderby,
            'order' => strtolower((string) $settings['order']) === 'asc' ? 'ASC' : 'DESC',
            'ignore_sticky_posts' => (($settings['ignore_sticky_posts'] ?? 'yes') === 'yes') ? 1 : 0,
            'post_status' => 'publish',
            'posts_per_page' => max(1, min(100, (int) $settings['posts_per_page'])),
            'offset' => max(0, (int) $settings['offset']),
        ];

        if ($settings['post_type'] === 'by_id') {
            $args['post_type'] = 'any';
            $postIn = is_array($settings['posts_ids']) ? array_map('absint', $settings['posts_ids']) : [];
            $args['post__in'] = empty($postIn) ? [0] : $postIn;
            if (($settings['by_id_order_mode'] ?? 'manual') === 'manual') {
                $args['orderby'] = 'post__in';
                $args['order'] = 'ASC';
            }
        } else {
            $resolvedPostType = $settings['post_type'] === 'source_dynamic' ? 'post' : sanitize_key((string) $settings['post_type']);
            $postTypeObject = get_post_type_object($resolvedPostType);
            if (!$postTypeObject || !$postTypeObject->public || $resolvedPostType === 'attachment') {
                $resolvedPostType = 'post';
            }
            $args['post_type'] = $resolvedPostType;
            // Built from user-selected Elementor taxonomy controls below.
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
            $args['tax_query'] = [];

            $taxonomies = get_object_taxonomies($resolvedPostType, 'objects');

            foreach ($taxonomies as $object) {
                if (!$object->public) {
                    continue;
                }

                $settingKey = $object->name . '_ids';
                if (!empty($settings[$settingKey]) && is_array($settings[$settingKey])) {
                    $args['tax_query'][] = [
                        'taxonomy' => sanitize_key($object->name),
                        'field' => 'term_id',
                        'terms' => array_values(array_filter(array_map('absint', $settings[$settingKey]))),
                    ];
                }
            }

            if (!empty($args['tax_query'])) {
                $relation = isset($settings['tax_query_relation']) ? strtoupper((string) $settings['tax_query_relation']) : 'AND';
                $args['tax_query']['relation'] = in_array($relation, ['AND', 'OR'], true) ? $relation : 'AND';
            }
        }

        if ($orderby === 'most_viewed') {
            $args['orderby'] = 'meta_value_num';
            // Optional Elementor ordering mode.
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
            $args['meta_key'] = '_eael_post_view_count';
        }

        if ($orderby === 'meta_value' && !empty($settings['meta_key'])) {
            // Optional Elementor custom-field ordering mode.
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
            $args['meta_key'] = sanitize_text_field((string) $settings['meta_key']);
            $metaType = !empty($settings['meta_type']) ? strtoupper((string) $settings['meta_type']) : 'CHAR';

            if ($metaType === 'NUMERIC') {
                $args['orderby'] = 'meta_value_num';
            } else {
                // Optional Elementor custom-field ordering mode.
                // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
                $args['orderby'] = 'meta_value';
                $args['meta_type'] = in_array($metaType, ['DATE', 'DATETIME', 'CHAR'], true) ? $metaType : 'CHAR';
            }
        }

        if (!empty($settings['authors']) && is_array($settings['authors'])) {
            $args['author__in'] = array_values(array_filter(array_map('absint', $settings['authors'])));
        }

        if (!empty($settings['post__not_in']) && is_array($settings['post__not_in'])) {
            // User-configured post exclusions for this Elementor query.
            // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
            $args['post__not_in'] = array_values(array_filter(array_map('absint', $settings['post__not_in'])));
        }

        return $args;
    }

    /**
     * @param array<string, mixed> $settings
     * @param array<string, mixed> $args
     * @return array<string, mixed>
     */
    public static function get_dynamic_args(array $settings, array $args): array
    {
        if (($settings['post_type'] ?? '') !== 'source_dynamic' || (!is_archive() && !is_search())) {
            return $args;
        }

        $data = get_queried_object();

        if (is_object($data) && isset($data->post_type)) {
            $postType = sanitize_key((string) $data->post_type);
            $postTypeObject = get_post_type_object($postType);
            $args['post_type'] = ($postTypeObject && $postTypeObject->public && $postType !== 'attachment') ? $postType : 'post';
            // Reset dynamic archive taxonomy query before rebuilding it below.
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
            $args['tax_query'] = [];
        } else {
            global $wp_query;
            if (isset($wp_query->query_vars['post_type'])) {
                $queryPostType = $wp_query->query_vars['post_type'];
                if (is_array($queryPostType)) {
                    $queryPostTypes = array_values(array_filter(array_map(static function ($postType): string {
                        $postType = sanitize_key((string) $postType);
                        $postTypeObject = get_post_type_object($postType);
                        return ($postTypeObject && $postTypeObject->public && $postType !== 'attachment') ? $postType : '';
                    }, $queryPostType)));
                    $args['post_type'] = !empty($queryPostTypes) ? $queryPostTypes : 'post';
                } else {
                    $postType = sanitize_key((string) $queryPostType);
                    $postTypeObject = get_post_type_object($postType);
                    $args['post_type'] = ($postTypeObject && $postTypeObject->public && $postType !== 'attachment') ? $postType : 'post';
                }
            }

            if (!empty($wp_query->query_vars['s'])) {
                $args['s'] = sanitize_text_field((string) $wp_query->query_vars['s']);
                $args['offset'] = 0;
            }
        }

        if (is_object($data) && isset($data->taxonomy, $data->term_id)) {
            $taxonomy = sanitize_key((string) $data->taxonomy);
            $taxonomyObject = get_taxonomy($taxonomy);
            if (!$taxonomyObject || !$taxonomyObject->public) {
                return $args;
            }

            // Mirrors the current archive taxonomy in dynamic source mode.
            // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
            $args['tax_query'][] = [
                'taxonomy' => $taxonomy,
                'field' => 'term_id',
                'terms' => [(int) $data->term_id],
            ];
        }

        if ((int) get_query_var('author') > 0) {
            $args['author__in'] = [(int) get_query_var('author')];
        }

        if ((string) get_query_var('s') !== '') {
            $args['s'] = sanitize_text_field((string) get_query_var('s'));
        }

        if (get_query_var('year') || get_query_var('monthnum') || get_query_var('day')) {
            $args['date_query'] = [
                'year' => (int) get_query_var('year'),
                'month' => (int) get_query_var('monthnum'),
                'day' => (int) get_query_var('day'),
            ];
        }

        if (!empty($args['tax_query']) && is_array($args['tax_query'])) {
            $args['tax_query']['relation'] = 'AND';
        }

        return $args;
    }

    public static function eael_validate_html_tag(string $tag): string
    {
        return in_array(strtolower($tag), self::ALLOWED_HTML_TAGS, true) ? $tag : 'div';
    }

    /**
     * @return array{label:string,datetime:string,mode:string}
     */
    public static function get_timeline_date_data(array $settings, int $postId = 0): array
    {
        $postId = $postId ?: (int) get_the_ID();
        if ($postId < 1) {
            return [];
        }

        $displayMode = (string) ($settings['timeline_date_display_mode'] ?? 'bubble');
        if ($displayMode === 'hide') {
            return [];
        }

        $source = (string) ($settings['timeline_date_source'] ?? 'published');
        $formatMode = (string) ($settings['timeline_date_format_mode'] ?? 'default');
        $customFormat = trim((string) ($settings['timeline_custom_date_format'] ?? ''));

        if ($source === 'modified') {
            $timestamp = (int) get_post_modified_time('U', false, $postId);
            $datetime = get_the_modified_date('c', $postId);
            $defaultLabel = get_the_modified_date('', $postId);
        } else {
            $timestamp = (int) get_post_time('U', false, $postId);
            $datetime = get_the_date('c', $postId);
            $defaultLabel = get_the_date('', $postId);
        }

        if ($timestamp < 1) {
            return [];
        }

        if ($formatMode === 'relative') {
            $label = sprintf(
                /* translators: %s: human-readable elapsed time. */
                __('%s ago', 'calio-addon'),
                human_time_diff($timestamp, current_time('timestamp'))
            );
        } elseif ($formatMode === 'custom' && $customFormat !== '') {
            $label = wp_date($customFormat, $timestamp);
        } else {
            $label = is_string($defaultLabel) ? $defaultLabel : '';
        }

        return [
            'label' => (string) $label,
            'datetime' => is_string($datetime) ? $datetime : '',
            'mode' => $displayMode,
        ];
    }

    public static function render_timeline_meta(array $settings, string $position = 'before_title', int $postId = 0): string
    {
        $postId = $postId ?: (int) get_the_ID();
        if ($postId < 1) {
            return '';
        }

        $resolvedPosition = (string) ($settings['meta_position'] ?? 'before_title');
        if ($resolvedPosition !== $position) {
            return '';
        }

        $items = [];
        $dateData = self::get_timeline_date_data($settings, $postId);
        if (($dateData['mode'] ?? '') === 'inline' && !empty($dateData['label'])) {
            $items[] = [
                'class' => 'is-date',
                'label' => (string) $dateData['label'],
            ];
        }

        if (($settings['eael_show_meta'] ?? 'yes') === 'yes') {
            if (($settings['eael_show_author'] ?? 'yes') === 'yes') {
                $items[] = [
                    'class' => 'is-author',
                    'label' => get_the_author_meta('display_name', (int) get_post_field('post_author', $postId)),
                ];
            }

            if (($settings['eael_show_categories'] ?? 'yes') === 'yes') {
                $termLabels = self::get_post_term_labels($postId);
                if (!empty($termLabels)) {
                    $taxonomySkin = sanitize_html_class((string) ($settings['taxonomy_skin'] ?? 'soft'));
                    foreach ($termLabels as $termLabel) {
                        $items[] = [
                            'class' => 'is-term is-skin-' . $taxonomySkin,
                            'label' => (string) $termLabel,
                        ];
                    }
                }
            }

            if (($settings['eael_show_comments'] ?? '') === 'yes') {
                $commentCount = (int) get_comments_number($postId);
                $items[] = [
                    'class' => 'is-comments',
                    'label' => sprintf(
                        /* translators: %s: number of comments. */
                        _n('%s Comment', '%s Comments', $commentCount, 'calio-addon'),
                        number_format_i18n($commentCount)
                    ),
                ];
            }
        }

        $items = array_values(array_filter($items, static function ($item): bool {
            return !empty($item['label']) && is_string($item['label']);
        }));

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

        $html = '<div class="calio-post-timeline-meta" data-position="' . esc_attr($position) . '">';
        foreach ($items as $item) {
            $html .= '<span class="calio-post-timeline-meta__item ' . esc_attr((string) $item['class']) . '">';
            $html .= esc_html((string) $item['label']);
            $html .= '</span>';
        }
        $html .= '</div>';

        return $html;
    }

    public static function render_timeline_badge(array $settings, int $postId = 0): string
    {
        if (($settings['show_badge'] ?? '') !== 'yes') {
            return '';
        }

        $postId = $postId ?: (int) get_the_ID();
        if ($postId < 1) {
            return '';
        }

        $source = sanitize_key((string) ($settings['badge_source'] ?? 'taxonomy'));
        if (!in_array($source, ['custom', 'post_type', 'taxonomy'], true)) {
            $source = 'taxonomy';
        }
        $label = '';

        switch ($source) {
            case 'custom':
                $label = trim((string) ($settings['badge_custom_text'] ?? ''));
                break;

            case 'post_type':
                $postTypeObject = get_post_type_object((string) get_post_type($postId));
                if ($postTypeObject && !empty($postTypeObject->labels->singular_name)) {
                    $label = (string) $postTypeObject->labels->singular_name;
                }
                break;

            case 'taxonomy':
            default:
                $termLabels = self::get_post_term_labels($postId, 1);
                $label = !empty($termLabels) ? (string) reset($termLabels) : '';
                break;
        }

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

        $skin = sanitize_html_class((string) ($settings['badge_skin'] ?? 'soft'));

        return '<span class="calio-post-timeline-badge is-source-' . esc_attr($source) . ' is-skin-' . esc_attr($skin) . '"><span class="calio-post-timeline-badge__label">' . esc_html($label) . '</span></span>';
    }

    public static function render_timeline_read_more(array $settings): string
    {
        if (($settings['show_read_more'] ?? '') !== 'yes') {
            return '';
        }

        $label = trim((string) ($settings['read_more_text'] ?? __('Read More', 'calio-addon')));
        if ($label === '') {
            return '';
        }

        $skin = sanitize_html_class((string) ($settings['read_more_skin'] ?? 'link'));
        $showArrow = (($settings['read_more_show_arrow'] ?? 'yes') === 'yes');

        $html = '<span class="calio-post-timeline-read-more is-skin-' . esc_attr($skin) . '">';
        $html .= '<span class="calio-post-timeline-read-more__label">' . esc_html($label) . '</span>';

        if ($showArrow) {
            $html .= '<span class="calio-post-timeline-read-more__icon" aria-hidden="true"><span class="calio-post-timeline-read-more__icon-line"></span></span>';
        }

        $html .= '</span>';

        return $html;
    }

    /**
     * @return array<int, string>
     */
    private static function get_post_term_labels(int $postId, int $limit = 2): array
    {
        $postType = get_post_type($postId);
        if (!is_string($postType) || $postType === '') {
            return [];
        }

        $taxonomies = get_object_taxonomies($postType, 'objects');
        if (isset($taxonomies['category'])) {
            $preferred = ['category'];
        } elseif (isset($taxonomies['product_cat'])) {
            $preferred = ['product_cat'];
        } else {
            $preferred = [];
        }

        foreach ($taxonomies as $taxonomy) {
            if (!$taxonomy->public || $taxonomy->name === 'post_format' || in_array($taxonomy->name, $preferred, true)) {
                continue;
            }
            $preferred[] = $taxonomy->name;
        }

        foreach ($preferred as $taxonomy) {
            $terms = get_the_terms($postId, $taxonomy);
            if (empty($terms) || is_wp_error($terms)) {
                continue;
            }

            $labels = array_map(static function ($term): string {
                return (string) $term->name;
            }, array_slice($terms, 0, $limit));

            if (!empty($labels)) {
                return $labels;
            }
        }

        return [];
    }

    /**
     * @param array<string, mixed> $extra
     * @return array<string, array<string, array<empty>|bool>>
     */
    public static function eael_allowed_tags(array $extra = []): array
    {
        $allowedTags = [
            'a' => [
                'href' => [],
                'title' => [],
                'class' => [],
                'rel' => [],
                'id' => [],
                'target' => [],
                'data-elementor-open-lightbox' => [],
            ],
            'time' => [
                'datetime' => [],
                'class' => [],
                'id' => [],
            ],
            'div' => [
                'class' => [],
                'hidden' => [],
                'id' => [],
            ],
            'span' => [
                'class' => [],
                'id' => [],
            ],
            'p' => [
                'class' => [],
                'id' => [],
            ],
            'img' => [
                'src' => [],
                'alt' => [],
                'title' => [],
                'height' => [],
                'width' => [],
                'class' => [],
                'id' => [],
                'loading' => [],
                'srcset' => [],
                'sizes' => [],
                'decoding' => [],
            ],
            'h1' => ['class' => [], 'id' => []],
            'h2' => ['class' => [], 'id' => []],
            'h3' => ['class' => [], 'id' => []],
            'h4' => ['class' => [], 'id' => []],
            'h5' => ['class' => [], 'id' => []],
            'h6' => ['class' => [], 'id' => []],
            'article' => ['class' => [], 'hidden' => [], 'id' => []],
        ];

        if (!empty($extra)) {
            return array_merge($allowedTags, $extra);
        }

        return $allowedTags;
    }
}
