<?php
defined( 'ABSPATH' ) || exit;

/**
 * Dynamic tag resolver for Jalpi Page Builder.
 *
 * Syntax:
 *   {{tag_name}}                 – basic tag
 *   {{tag_name:param}}           – tag with parameter (e.g. date format, image size, field name)
 *   {{tag_name|fallback}}        – tag with fallback value
 *   {{tag_name:param|fallback}}  – tag with parameter and fallback
 *
 * Supported tags:
 *   post_title, post_content, post_excerpt, post_url, post_id, post_type,
 *   post_date[:format], post_modified[:format],
 *   author_name, author_bio, author_url,
 *   featured_image[:size], featured_image_url[:size],
 *   post_categories[:separator], post_tags[:separator],
 *   site_name, site_url, current_year,
 *   meta:field_name, cf:field_name,
 *   acf:field_name  (requires Advanced Custom Fields),
 *   mb:field_name   (requires Meta Box),
 *   pods:field_name (requires Pods)
 */
class Jalpi_Dynamic_Tags {

    /** @var int The post being rendered. */
    private int $post_id;

    /** @var array<string,string> Per-render in-memory cache to avoid duplicate DB queries. */
    private array $cache = [];

    public function __construct( int $post_id ) {
        $this->post_id = $post_id;
    }

    // ── Public API ─────────────────────────────────────────────────────────────

    /** Return true if $str contains at least one {{tag}} placeholder. */
    public static function has_tags( string $str ): bool {
        return str_contains( $str, '{{' );
    }

    /**
     * Resolve all {{…}} tags in $str and return the result.
     * Non-tag text is passed through unchanged.
     */
    public function resolve( string $str ): string {
        if ( ! self::has_tags( $str ) ) {
            return $str;
        }
        return preg_replace_callback(
            '/\{\{([a-z_]+)(?::([^|}]*))?(?:\|([^}]*))?\}\}/i',
            [ $this, 'resolve_match' ],
            $str
        );
    }

    /** Resolve dynamic tags in a URL field; apply esc_url() on the result. */
    public function resolve_url( string $str ): string {
        return esc_url( self::has_tags( $str ) ? $this->resolve( $str ) : $str );
    }

    /** Resolve dynamic tags in an attribute string; apply esc_attr() on the result. */
    public function resolve_attr( string $str ): string {
        return esc_attr( self::has_tags( $str ) ? $this->resolve( $str ) : $str );
    }

    // ── Tag registry (for the editor picker) ───────────────────────────────────

    /**
     * Return the full list of available tags grouped by category.
     * Called server-side by the REST endpoint; result is JSON-encoded for the editor.
     *
     * @param  int   $post_id Post context (0 = skip custom-field detection).
     * @return array[]        Array of group arrays.
     */
    public static function get_available_tags( int $post_id = 0 ): array {
        $groups = [
            [
                'key'   => 'post',
                'label' => 'Post Data',
                'icon'  => 'file',
                'tags'  => [
                    [ 'tag' => 'post_title',    'label' => 'Post Title',    'example' => '{{post_title}}' ],
                    [ 'tag' => 'post_excerpt',  'label' => 'Post Excerpt',  'example' => '{{post_excerpt}}' ],
                    [ 'tag' => 'post_content',  'label' => 'Post Content',  'example' => '{{post_content}}' ],
                    [ 'tag' => 'post_url',      'label' => 'Post URL',      'example' => '{{post_url}}' ],
                    [ 'tag' => 'post_id',       'label' => 'Post ID',       'example' => '{{post_id}}' ],
                    [ 'tag' => 'post_type',     'label' => 'Post Type',     'example' => '{{post_type}}' ],
                    [ 'tag' => 'post_date',     'label' => 'Publish Date',  'example' => '{{post_date:F j, Y}}',     'hint' => 'param = PHP date format' ],
                    [ 'tag' => 'post_modified', 'label' => 'Modified Date', 'example' => '{{post_modified:F j, Y}}', 'hint' => 'param = PHP date format' ],
                ],
            ],
            [
                'key'   => 'author',
                'label' => 'Author',
                'icon'  => 'user',
                'tags'  => [
                    [ 'tag' => 'author_name', 'label' => 'Author Name', 'example' => '{{author_name}}' ],
                    [ 'tag' => 'author_bio',  'label' => 'Author Bio',  'example' => '{{author_bio}}' ],
                    [ 'tag' => 'author_url',  'label' => 'Author URL',  'example' => '{{author_url}}' ],
                ],
            ],
            [
                'key'   => 'image',
                'label' => 'Featured Image',
                'icon'  => 'image',
                'tags'  => [
                    [ 'tag' => 'featured_image_url', 'label' => 'Image URL',  'example' => '{{featured_image_url:large}}',  'hint' => 'param = size: thumbnail, medium, large, full' ],
                    [ 'tag' => 'featured_image',     'label' => 'Image HTML', 'example' => '{{featured_image:large}}',      'hint' => 'param = size: thumbnail, medium, large, full' ],
                ],
            ],
            [
                'key'   => 'taxonomy',
                'label' => 'Taxonomy',
                'icon'  => 'tag',
                'tags'  => [
                    [ 'tag' => 'post_categories', 'label' => 'Categories', 'example' => '{{post_categories}}',   'hint' => 'param = separator (default ", ")' ],
                    [ 'tag' => 'post_tags',       'label' => 'Tags',       'example' => '{{post_tags}}',         'hint' => 'param = separator (default ", ")' ],
                ],
            ],
            [
                'key'   => 'site',
                'label' => 'Site',
                'icon'  => 'globe',
                'tags'  => [
                    [ 'tag' => 'site_name',    'label' => 'Site Name',    'example' => '{{site_name}}' ],
                    [ 'tag' => 'site_url',     'label' => 'Site URL',     'example' => '{{site_url}}' ],
                    [ 'tag' => 'current_year', 'label' => 'Current Year', 'example' => '{{current_year}}' ],
                ],
            ],
            [
                'key'   => 'loop',
                'label' => 'Archive / Loop',
                'icon'  => 'file',
                'tags'  => [
                    [ 'tag' => 'the_loop',      'label' => 'Post Loop',    'example' => '{{the_loop}}',        'hint' => 'param = number of columns (e.g. 3)' ],
                    [ 'tag' => 'wp_pagination', 'label' => 'Pagination',   'example' => '{{wp_pagination}}' ],
                ],
            ],
        ];

        if ( $post_id > 0 ) {
            $groups = array_merge( $groups, self::dynamic_field_groups( $post_id ) );
        }

        return array_values( array_filter( $groups, fn( $g ) => ! empty( $g['tags'] ) ) );
    }

    // ── Private: resolve a single regex match ──────────────────────────────────

    private function resolve_match( array $m ): string {
        static $valid_tags = null;
        if ( null === $valid_tags ) {
            $valid_tags = [
                'post_title', 'post_content', 'post_excerpt', 'post_id', 'post_url',
                'post_date', 'post_modified', 'post_author', 'post_thumbnail_url',
                'site_name', 'site_url', 'site_tagline',
                'author_name', 'author_bio', 'author_url',
                'categories', 'tags',
                'meta', 'acf', 'pods', 'mb',
                'current_year', 'current_date',
                'term_name', 'term_description', 'term_url', 'term_count',
                'archive_title', 'archive_description',
                // Also allow tags that exist in resolve_tag() switch
                'post_type', 'post_url', 'featured_image', 'featured_image_url',
                'post_categories', 'post_tags', 'cf', 'meta_box',
                'the_loop', 'wp_loop', 'wp_pagination', 'pagination',
            ];
            $valid_tags = (array) apply_filters( 'jalpi_valid_dynamic_tags', $valid_tags );
        }
        $tag = strtolower( $m[1] ?? '' );
        if ( ! in_array( $tag, $valid_tags, true ) ) {
            return $m[0]; // Return the original tag text unchanged
        }

        $param    = trim( $m[2] ?? '' );
        $fallback = $m[3] ?? '';

        $cache_key = "{$tag}|{$param}";
        if ( array_key_exists( $cache_key, $this->cache ) ) {
            $v = $this->cache[ $cache_key ];
            return '' !== $v ? $v : $fallback;
        }

        $value = $this->resolve_tag( $tag, $param );
        $this->cache[ $cache_key ] = $value;

        return '' !== $value ? $value : $fallback;
    }

    private function resolve_tag( string $tag, string $param ): string {
        $post = get_post( $this->post_id );
        if ( ! $post ) {
            return '';
        }

        switch ( $tag ) {
            case 'post_title':
                return get_the_title( $post );

            case 'post_content':
                // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core WordPress filter, not a custom hook
                return apply_filters( 'the_content', $post->post_content );

            case 'post_excerpt':
                if ( has_excerpt( $post->ID ) ) {
                    return wp_strip_all_tags( get_the_excerpt( $post ) );
                }
                return wp_trim_words( wp_strip_all_tags( $post->post_content ), 55 );

            case 'post_url':
                return get_permalink( $post );

            case 'post_id':
                return (string) $post->ID;

            case 'post_type':
                return $post->post_type;

            case 'post_date':
                return get_the_date( $param ?: 'F j, Y', $post );

            case 'post_modified':
                return get_the_modified_date( $param ?: 'F j, Y', $post );

            case 'author_name':
                return get_the_author_meta( 'display_name', (int) $post->post_author );

            case 'author_bio':
                return get_the_author_meta( 'description', (int) $post->post_author );

            case 'author_url':
                return get_author_posts_url( (int) $post->post_author );

            case 'featured_image':
                return (string) get_the_post_thumbnail( $post->ID, $param ?: 'large' );

            case 'featured_image_url':
                return $this->get_thumbnail_url( $param ?: 'large', $post->ID );

            case 'post_categories':
                return $this->get_term_list( 'category', $param ?: ', ', $post->ID );

            case 'post_tags':
                return $this->get_term_list( 'post_tag', $param ?: ', ', $post->ID );

            case 'site_name':
                return get_bloginfo( 'name' );

            case 'site_url':
                return esc_url( home_url() );

            case 'current_year':
                return (string) (int) current_time( 'Y' );

            case 'the_loop':
            case 'wp_loop':
                return $this->get_the_loop( $param );

            case 'wp_pagination':
            case 'pagination':
                return $this->get_pagination();

            case 'meta':
            case 'cf':
                return $this->get_meta_value( $param, $post->ID );

            case 'acf':
                return $this->get_acf_value( $param, $post->ID );

            case 'mb':
            case 'meta_box':
                return $this->get_mb_value( $param, $post->ID );

            case 'pods':
                return $this->get_pods_value( $param, $post->ID );

            default:
                return '';
        }
    }

    // ── Private: field resolvers ────────────────────────────────────────────────

    private function get_thumbnail_url( string $size, int $post_id ): string {
        $thumb_id = get_post_thumbnail_id( $post_id );
        if ( ! $thumb_id ) {
            return '';
        }
        $src = wp_get_attachment_image_src( $thumb_id, $size );
        return $src ? (string) $src[0] : '';
    }

    private function get_term_list( string $taxonomy, string $sep, int $post_id ): string {
        $terms = get_the_terms( $post_id, $taxonomy );
        if ( ! $terms || is_wp_error( $terms ) ) {
            return '';
        }
        return implode( $sep, array_map( fn( $t ) => esc_html( $t->name ), $terms ) );
    }

    private function get_meta_value( string $key, int $post_id ): string {
        if ( '' === $key ) {
            return '';
        }
        $v = get_post_meta( $post_id, $key, true );
        if ( is_string( $v ) ) {
            return $v;
        }
        if ( is_numeric( $v ) ) {
            return (string) $v;
        }
        if ( is_array( $v ) ) {
            $parts = array_filter( $v, 'is_scalar' );
            return implode( ', ', $parts );
        }
        return '';
    }

    private function get_acf_value( string $field, int $post_id ): string {
        if ( ! function_exists( 'get_field' ) || '' === $field ) {
            return '';
        }
        $v = get_field( $field, $post_id );
        if ( is_string( $v ) ) {
            return $v;
        }
        if ( is_numeric( $v ) ) {
            return (string) $v;
        }
        if ( $v instanceof WP_Post ) {
            return $v->post_title;
        }
        if ( is_array( $v ) ) {
            $parts = array_filter( $v, 'is_scalar' );
            return implode( ', ', $parts );
        }
        return '';
    }

    private function get_mb_value( string $field, int $post_id ): string {
        if ( ! function_exists( 'rwmb_meta' ) || '' === $field ) {
            return '';
        }
        try {
            $v = rwmb_meta( $field, [], $post_id );
            if ( is_string( $v ) ) {
                return $v;
            }
            if ( is_numeric( $v ) ) {
                return (string) $v;
            }
        } catch ( \Throwable $e ) {
            // Meta Box not fully loaded or field missing — silently skip.
        }
        return '';
    }

    private function get_pods_value( string $field, int $post_id ): string {
        if ( ! function_exists( 'pods' ) || '' === $field ) {
            return '';
        }
        try {
            $post = get_post( $post_id );
            if ( ! $post ) {
                return '';
            }
            $pod = pods( $post->post_type, $post_id );
            if ( ! $pod || ! $pod->exists() ) {
                return '';
            }
            $v = $pod->field( $field );
            if ( is_string( $v ) ) {
                return $v;
            }
            if ( is_numeric( $v ) ) {
                return (string) $v;
            }
        } catch ( \Throwable $e ) {
            // Pods not fully loaded or field missing — silently skip.
        }
        return '';
    }

    // ── Private: field group discovery (for the editor picker) ─────────────────

    private static function dynamic_field_groups( int $post_id ): array {
        $groups = [];

        // Native WordPress custom fields
        $meta_keys = self::get_public_meta_keys( $post_id );
        if ( ! empty( $meta_keys ) ) {
            $groups[] = [
                'key'   => 'meta',
                'label' => 'Custom Fields',
                'icon'  => 'database',
                'tags'  => array_map( fn( $k ) => [
                    'tag'     => 'meta',
                    'label'   => $k,
                    'example' => "{{meta:{$k}}}",
                    'param'   => $k,
                ], $meta_keys ),
            ];
        }

        // ACF (Advanced Custom Fields)
        if ( function_exists( 'get_field_objects' ) ) {
            $acf_fields  = get_field_objects( $post_id );
            $scalar_types = [ 'text', 'textarea', 'number', 'url', 'email',
                              'date_picker', 'time_picker', 'date_time_picker',
                              'select', 'radio', 'true_false', 'range' ];
            $acf_tags = [];
            if ( is_array( $acf_fields ) ) {
                foreach ( $acf_fields as $f ) {
                    if ( in_array( $f['type'] ?? '', $scalar_types, true ) ) {
                        $acf_tags[] = [
                            'tag'     => 'acf',
                            'label'   => $f['label'] ?: $f['name'],
                            'example' => "{{acf:{$f['name']}}}",
                            'param'   => $f['name'],
                        ];
                    }
                }
            }
            if ( ! empty( $acf_tags ) ) {
                $groups[] = [ 'key' => 'acf', 'label' => 'ACF Fields', 'icon' => 'acf', 'tags' => $acf_tags ];
            }
        }

        // Meta Box
        $mb_group = self::get_meta_box_group();
        if ( $mb_group ) {
            $groups[] = $mb_group;
        }

        // Pods
        $pods_group = self::get_pods_group( $post_id );
        if ( $pods_group ) {
            $groups[] = $pods_group;
        }

        return $groups;
    }

    private static function get_public_meta_keys( int $post_id ): array {
        $all = get_post_meta( $post_id );
        if ( ! $all ) {
            return [];
        }
        return array_values( array_filter(
            array_keys( $all ),
            fn( $k ) => $k !== '' && $k[0] !== '_' && strlen( $k ) < 80
        ) );
    }

    private static function get_meta_box_group(): ?array {
        if ( ! function_exists( 'rwmb_get_registry' ) ) {
            return null;
        }
        try {
            $registry = rwmb_get_registry( 'field' );
            if ( ! method_exists( $registry, 'get_by_object_type' ) ) {
                return null;
            }
            $all = $registry->get_by_object_type( 'post' );
            $scalar_types = [ 'text', 'textarea', 'number', 'url', 'email', 'date', 'datetime', 'time', 'select', 'radio', 'range' ];
            $tags = [];
            foreach ( (array) $all as $group_fields ) {
                foreach ( (array) $group_fields as $f ) {
                    if ( in_array( $f['type'] ?? '', $scalar_types, true ) ) {
                        $tags[] = [
                            'tag'     => 'mb',
                            'label'   => ! empty( $f['name'] ) ? $f['name'] : $f['id'],
                            'example' => "{{mb:{$f['id']}}}",
                            'param'   => $f['id'],
                        ];
                    }
                }
            }
            return empty( $tags ) ? null : [ 'key' => 'mb', 'label' => 'Meta Box', 'icon' => 'mb', 'tags' => $tags ];
        } catch ( \Throwable $e ) {
            return null;
        }
    }

    private static function get_pods_group( int $post_id ): ?array {
        if ( ! class_exists( 'PodsInit' ) && ! function_exists( 'pods_api' ) ) {
            return null;
        }
        try {
            $post = get_post( $post_id );
            if ( ! $post ) {
                return null;
            }
            $api     = pods_api();
            $pod_def = $api->load_pod( [ 'name' => $post->post_type ] );
            if ( ! $pod_def || empty( $pod_def['fields'] ) ) {
                return null;
            }
            $scalar_types = [ 'text', 'paragraph', 'number', 'decimal', 'currency',
                              'email', 'phone', 'website', 'date', 'datetime', 'time' ];
            $tags = [];
            foreach ( $pod_def['fields'] as $f ) {
                if ( in_array( $f['type'] ?? '', $scalar_types, true ) ) {
                    $tags[] = [
                        'tag'     => 'pods',
                        'label'   => $f['label'] ?: $f['name'],
                        'example' => "{{pods:{$f['name']}}}",
                        'param'   => $f['name'],
                    ];
                }
            }
            return empty( $tags ) ? null : [ 'key' => 'pods', 'label' => 'Pods Fields', 'icon' => 'pods', 'tags' => $tags ];
        } catch ( \Throwable $e ) {
            return null;
        }
    }

    // ── Loop / pagination helpers (for archive/search theme templates) ─────────

    /**
     * Output the current WordPress query loop as HTML.
     * $columns = '2' | '3' | '4' — wraps items in a CSS grid row.
     */
    private function get_the_loop( string $columns = '' ): string {
        if ( ! have_posts() ) {
            return '<p class="jalpi-loop-no-results">' . esc_html__( 'No posts found.', 'jalpi-page-builder' ) . '</p>';
        }

        $cols = (int) ( $columns ?: 1 );
        if ( $cols < 1 ) $cols = 1;
        if ( $cols > 6 ) $cols = 6;

        $grid_css = $cols > 1
            ? 'display:grid;grid-template-columns:repeat(' . intval( $cols ) . ',1fr);gap:24px;'
            : 'display:flex;flex-direction:column;gap:20px;';

        ob_start();
        echo '<div class="jalpi-loop" style="' . esc_attr( $grid_css ) . '">';
        while ( have_posts() ) :
            the_post();
            $id      = get_the_ID();
            $thumb   = has_post_thumbnail( $id ) ? get_the_post_thumbnail( $id, 'medium' ) : '';
            $date    = get_the_date( 'M j, Y', $id );
            $excerpt = get_the_excerpt();
            ?>
            <article id="jalpi-loop-item-<?php echo esc_attr( $id ); ?>" <?php post_class( 'jalpi-loop-item' ); ?>>
                <?php if ( $thumb ) : ?>
                    <div class="jalpi-loop-item__thumb">
                        <a href="<?php the_permalink(); ?>"><?php echo wp_kses_post( $thumb ); ?></a>
                    </div>
                <?php endif; ?>
                <div class="jalpi-loop-item__body">
                    <span class="jalpi-loop-item__date"><?php echo esc_html( $date ); ?></span>
                    <h3 class="jalpi-loop-item__title">
                        <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
                    </h3>
                    <?php if ( $excerpt ) : ?>
                        <p class="jalpi-loop-item__excerpt"><?php echo esc_html( $excerpt ); ?></p>
                    <?php endif; ?>
                    <a href="<?php the_permalink(); ?>" class="jalpi-loop-item__more">
                        <?php esc_html_e( 'Read more', 'jalpi-page-builder' ); ?> →
                    </a>
                </div>
            </article>
            <?php
        endwhile;
        echo '</div>';
        return ob_get_clean();
    }

    private function get_pagination(): string {
        return (string) ( paginate_links( [
            'type'      => 'list',
            'prev_text' => '‹',
            'next_text' => '›',
        ] ) ?: '' );
    }
}
