<?php
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * SEO / GEO helpers shared by the scholar, group-feed and group-grid shortcode
 * renderers: schema.org JSON-LD builders, and the server-rendered HTML fallback
 * for the first page of results (the JS views replace this on load for normal
 * visitors; it exists so crawlers/AI agents that don't execute JS still see the
 * real content instead of an empty container).
 */
class ScholarSync_SEO {

    /**
     * Echo a JSON-LD <script> tag. No-op on empty data.
     */
    public static function render_json_ld( array $data ) {
        if ( empty( $data ) ) {
            return;
        }
        $json = wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
        if ( ! $json ) {
            return;
        }
        // Defend against a field value containing a literal "</script>".
        $json = str_replace( '</', '<\/', $json );
        echo '<script type="application/ld+json">' . $json . "</script>\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_json_encode() output, </ escaped above
    }

    /**
     * @param array|object $author Row with name/position/affiliations/thumbnail/website/interests/scholar_id/orcid_id.
     */
    public static function person_schema( $author ) {
        $get = static function ( $key ) use ( $author ) {
            if ( is_array( $author ) ) { return $author[ $key ] ?? ''; }
            return $author->$key ?? '';
        };

        $schema = array(
            '@type' => 'Person',
            'name'  => (string) $get( 'name' ),
        );

        $position = trim( (string) $get( 'position' ) );
        if ( $position !== '' ) {
            $schema['jobTitle'] = $position;
        }

        $affiliations = trim( (string) $get( 'affiliations' ) );
        if ( $affiliations !== '' ) {
            $schema['affiliation'] = array( '@type' => 'Organization', 'name' => $affiliations );
        }

        $thumbnail = trim( (string) $get( 'thumbnail' ) );
        if ( $thumbnail !== '' ) {
            $schema['image'] = $thumbnail;
        }

        $website = trim( (string) $get( 'website' ) );
        if ( $website !== '' && preg_match( '#^https?://#i', $website ) ) {
            $schema['url'] = $website;
        }

        $interests = trim( (string) $get( 'interests' ) );
        if ( $interests !== '' ) {
            $topics = array_values( array_filter( array_map( 'trim', explode( ',', $interests ) ) ) );
            if ( $topics ) {
                $schema['knowsAbout'] = $topics;
            }
        }

        $same_as    = array();
        $scholar_id = trim( (string) $get( 'scholar_id' ) );
        if ( $scholar_id !== '' ) {
            $same_as[] = 'https://scholar.google.com/citations?user=' . rawurlencode( $scholar_id );
        }
        $orcid = trim( (string) $get( 'orcid_id' ) );
        if ( $orcid !== '' ) {
            $same_as[] = 'https://orcid.org/' . rawurlencode( $orcid );
        }
        if ( $same_as ) {
            $schema['sameAs'] = $same_as;
        }

        return $schema;
    }

    /**
     * @param array $article Row with title/authors/publication/year/link/cited_by_value.
     */
    public static function scholarly_article_schema( array $article ) {
        $item = array(
            '@type' => 'ScholarlyArticle',
            'name'  => (string) ( $article['title'] ?? '' ),
        );

        $authors = trim( (string) ( $article['authors'] ?? '' ) );
        if ( $authors !== '' ) {
            $names = array_values( array_filter( array_map( 'trim', explode( ',', $authors ) ) ) );
            if ( $names ) {
                $item['author'] = array_map(
                    static function ( $name ) { return array( '@type' => 'Person', 'name' => $name ); },
                    $names
                );
            }
        }

        $publication = trim( (string) ( $article['publication'] ?? '' ) );
        if ( $publication !== '' ) {
            $item['isPartOf'] = array( '@type' => 'Periodical', 'name' => $publication );
        }

        $year = (int) ( $article['year'] ?? 0 );
        if ( $year > 0 ) {
            $item['datePublished'] = (string) $year;
        }

        $link = trim( (string) ( $article['link'] ?? '' ) );
        if ( $link !== '' && preg_match( '#^https?://#i', $link ) ) {
            $item['url'] = $link;
        }

        $cited = (int) ( $article['cited_by_value'] ?? 0 );
        if ( $cited > 0 ) {
            $item['interactionStatistic'] = array(
                '@type'                => 'InteractionCounter',
                'interactionType'      => 'https://schema.org/CiteAction',
                'userInteractionCount' => $cited,
            );
        }

        return $item;
    }

    /**
     * Wrap a list of schema.org items ($items already shaped, e.g. via
     * person_schema()/scholarly_article_schema()) into a top-level ItemList document.
     */
    public static function item_list_document( array $items, $name = '' ) {
        $list = array(
            '@context'        => 'https://schema.org',
            '@type'           => 'ItemList',
            'itemListElement' => array(),
        );
        if ( $name !== '' ) {
            $list['name'] = $name;
        }
        $position = 1;
        foreach ( $items as $item ) {
            $list['itemListElement'][] = array(
                '@type'    => 'ListItem',
                'position' => $position++,
                'item'     => $item,
            );
        }
        return $list;
    }

    /**
     * Server-rendered article card, mirroring scholarSyncRenderArticleCard() in
     * public/js/scholar-sync-article-card.js closely enough that the JS-driven
     * re-render (which replaces this markup on page load for JS-enabled visitors)
     * doesn't visibly jar. Co-author photos are intentionally omitted here — that
     * matching only lives in the REST layer — so authors render as plain text
     * chips, matching the site's existing "photos disabled" look.
     */
    public static function render_article_card_ssr( array $article ) {
        $title     = esc_html( (string) ( $article['title'] ?? '' ) );
        $link      = trim( (string) ( $article['link'] ?? '' ) );
        $safe_link = ( $link !== '' && preg_match( '#^https?://#i', $link ) ) ? $link : '';

        $meta = array();
        if ( ! empty( $article['year'] ) ) {
            $meta[] = esc_html( (string) $article['year'] );
        }
        if ( ! empty( $article['publication'] ) ) {
            $meta[] = esc_html( (string) $article['publication'] );
        }

        $title_html = $safe_link
            ? '<a class="scholar-sync-article-title" href="' . esc_url( $safe_link ) . '" target="_blank" rel="noopener noreferrer">' . $title . '</a>'
            : '<span class="scholar-sync-article-title">' . $title . '</span>';

        $authors_html = '';
        if ( ! empty( $article['authors'] ) ) {
            $names = array_values( array_filter( array_map( 'trim', explode( ',', (string) $article['authors'] ) ) ) );
            $chips = '';
            foreach ( $names as $name ) {
                $chips .= '<div class="scholar-sync-author-chip scholar-sync-author-chip-no-photo"><span>' . esc_html( $name ) . '</span></div>';
            }
            $authors_html = '<div class="scholar-sync-article-authors">' . $chips . '</div>';
        }

        $cited      = (int) ( $article['cited_by_value'] ?? 0 );
        $cited_html = $cited > 0
            ? '<div class="scholar-sync-cited-by">Cited by ' . esc_html( (string) $cited ) . '</div>'
            : '';

        return '<div class="scholar-sync-article-card">'
            . '<div class="scholar-sync-article-meta">' . implode( ' &bull; ', $meta ) . '</div>'
            . $title_html
            . $authors_html
            . '<div class="scholar-sync-article-actions"><div class="scholar-sync-actions-row">' . $cited_html . '</div></div>'
            . '</div>';
    }

    /**
     * Server-rendered researcher card for the Staff Directory (grid) view, mirroring
     * scholarSyncRenderGroupCard() in public/js/scholar-sync-group-public.js. Tag
     * overflow ("+N more") is not replicated — all tags render up front, which is
     * strictly more informative for a crawler and gets replaced by the JS version
     * (with its interactive toggle) on load for real visitors.
     */
    public static function render_group_card_ssr( array $author, array $settings, $target_page = '' ) {
        $image_shape = ! empty( $settings['image_shape'] ) ? sanitize_key( $settings['image_shape'] ) : 'circle';
        $shadow      = ! isset( $settings['shadow'] ) || $settings['shadow'] === true || $settings['shadow'] === '1' || $settings['shadow'] === 1;
        $img_class   = $image_shape . ( $shadow ? ' with-shadow' : '' );
        $img_url     = ! empty( $author['thumbnail'] ) ? $author['thumbnail'] : ( SCHOLAR_SYNC_PLUGIN_URL . 'includes/images/scholar_avatar.png' );

        $link = '';
        if ( $target_page !== '' && ! empty( $author['author_id'] ) ) {
            $link = add_query_arg( 'author-id', (int) $author['author_id'], $target_page );
        } elseif ( ! empty( $author['effective_link'] ) ) {
            $link = (string) $author['effective_link'];
        }

        $name      = esc_html( (string) ( $author['name'] ?? '' ) );
        $name_html = $link
            ? '<a class="scholar-sync-group-card-name" href="' . esc_url( $link ) . '">' . $name . '</a>'
            : '<div class="scholar-sync-group-card-name">' . $name . '</div>';

        $img_html = '<img class="scholar-sync-group-card-image ' . esc_attr( $img_class ) . '" src="' . esc_url( $img_url ) . '" alt="' . esc_attr( (string) ( $author['name'] ?? '' ) ) . '" loading="lazy">';
        if ( $link ) {
            $img_html = '<a class="scholar-sync-group-card-image-link" href="' . esc_url( $link ) . '">' . $img_html . '</a>';
        }

        $html = '<article class="scholar-sync-group-card"><div class="scholar-sync-group-card-main">' . $img_html
            . '<div class="scholar-sync-group-card-content">' . $name_html;

        if ( ( $settings['show_position'] ?? null ) !== false && ! empty( $author['position'] ) ) {
            $html .= '<div class="scholar-sync-group-card-position">' . esc_html( (string) $author['position'] ) . '</div>';
        }
        if ( ( $settings['show_institution'] ?? null ) !== false && ! empty( $author['affiliations'] ) ) {
            $html .= '<div class="scholar-sync-group-card-institution">' . esc_html( (string) $author['affiliations'] ) . '</div>';
        }
        if ( ( $settings['show_tags'] ?? null ) !== false && ! empty( $author['interests'] ) ) {
            $tags = array_values( array_filter( array_map( 'trim', explode( ',', (string) $author['interests'] ) ) ) );
            if ( $tags ) {
                $html .= '<div class="scholar-sync-group-card-tags">';
                foreach ( $tags as $tag ) {
                    $html .= '<span class="scholar-sync-group-card-tag">' . esc_html( $tag ) . '</span>';
                }
                $html .= '</div>';
            }
        }
        $html .= '</div></div>'; // .scholar-sync-group-card-content, .scholar-sync-group-card-main

        $show_pubs = ( $settings['show_publications'] ?? null ) !== false;
        $show_link = ( $settings['show_profile_link'] ?? null ) !== false;
        if ( $show_pubs || $show_link ) {
            $html .= '<div class="scholar-sync-group-card-footer">';
            if ( $show_pubs ) {
                $count = (int) ( $author['publication_count'] ?? 0 );
                $html .= '<span class="scholar-sync-group-card-publications">' . esc_html( (string) $count ) . ' ' . ( 1 === $count ? 'Publication' : 'Publications' ) . '</span>';
            } else {
                $html .= '<span></span>';
            }
            if ( $show_link && $link ) {
                $html .= '<a class="scholar-sync-group-card-profile-link" href="' . esc_url( $link ) . '">View Profile</a>';
            }
            $html .= '</div>';
        }
        $html .= '</article>';

        return $html;
    }
}
