<?php
/**
 * SEO score column for the native post/page list tables (edit.php).
 *
 * Reads the stored on-page score from the plugin's own meta table
 * (wp_seoforge_meta), shows "— / not analyzed" when a post has never been
 * scored, and offers an inline "Analyze" button that computes + persists the
 * score without leaving the list. Scores for the current page are fetched in a
 * single query, not one per row.
 *
 * @package Brainwerk SEO Suite
 */

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

class Seoforge_Admin_Columns {

    const COLUMN = 'seoforge_score';

    /** @var array<int,array>|null Prefetched score rows for the current screen, keyed by post_id. */
    private static $cache = null;

    public static function register( Seoforge_Loader $loader ) {
        // Wire the column hooks only when a list table is actually loading, so
        // every post type is registered and we can read the current screen.
        $loader->add_action( 'load-edit.php', __CLASS__, 'setup_columns' );
    }

    public static function setup_columns() {
        $screen = get_current_screen();
        if ( ! $screen || empty( $screen->post_type ) || ! self::is_target_type( $screen->post_type ) ) {
            return;
        }
        $pt = $screen->post_type;
        add_filter( "manage_{$pt}_posts_columns", array( __CLASS__, 'add_column' ) );
        add_action( "manage_{$pt}_posts_custom_column", array( __CLASS__, 'render_column' ), 10, 2 );
    }

    /** Public, UI-visible post types get the column. */
    public static function is_target_type( $post_type ) {
        return in_array( $post_type, get_post_types( array( 'public' => true, 'show_ui' => true ) ), true );
    }

    /** Insert the SEO score column just before the Date column. */
    public static function add_column( $columns ) {
        $out = array();
        foreach ( $columns as $key => $label ) {
            if ( 'date' === $key ) {
                $out[ self::COLUMN ] = __( 'SEO', 'brainwerk-seo-suite' );
            }
            $out[ $key ] = $label;
        }
        if ( ! isset( $out[ self::COLUMN ] ) ) {
            $out[ self::COLUMN ] = __( 'SEO', 'brainwerk-seo-suite' );
        }
        return $out;
    }

    public static function render_column( $column, $post_id ) {
        if ( self::COLUMN !== $column ) {
            return;
        }
        $row      = self::score_for( (int) $post_id );
        $has_score = null !== $row && self::is_scored( $row );
        $score     = $has_score ? (int) $row['score'] : null;
        $cls       = $has_score ? self::score_class( $score ) : 'unscored';

        echo '<span class="seoforge-pill seoforge-pill-' . esc_attr( $cls ) . ' sf-score-badge">';
        echo null === $score
            ? esc_html__( '—', 'brainwerk-seo-suite' )
            : esc_html( (string) $score );
        echo '</span> ';
        printf(
            '<button type="button" class="button button-small sf-analyze-btn" data-post="%1$d" title="%2$s">%3$s</button>',
            (int) $post_id,
            esc_attr__( 'Compute and save the on-page SEO score', 'brainwerk-seo-suite' ),
            $has_score ? esc_html__( 'Re-analyze', 'brainwerk-seo-suite' ) : esc_html__( 'Analyze', 'brainwerk-seo-suite' )
        );
    }

    /**
     * A row counts as scored only if a real analysis ran. A bare row with
     * score 0 and no payload is treated as "not analyzed" (shown as "—").
     */
    private static function is_scored( array $row ) {
        if ( (int) $row['score'] > 0 ) {
            return true;
        }
        return ! empty( $row['score_payload'] );
    }

    private static function score_class( $score ) {
        if ( $score >= 80 ) {
            return 'good';
        }
        if ( $score >= 50 ) {
            return 'ok';
        }
        return 'bad';
    }

    /**
     * Return the cached score row for a post, prefetching every post on the
     * current screen in one query on first access.
     */
    private static function score_for( $post_id ) {
        if ( null === self::$cache ) {
            self::prefetch();
        }
        return isset( self::$cache[ $post_id ] ) ? self::$cache[ $post_id ] : null;
    }

    private static function prefetch() {
        self::$cache = array();
        $ids = array();
        if ( isset( $GLOBALS['wp_query'] ) && ! empty( $GLOBALS['wp_query']->posts ) ) {
            foreach ( $GLOBALS['wp_query']->posts as $p ) {
                $ids[] = is_object( $p ) ? (int) $p->ID : (int) $p;
            }
        }
        $ids = array_values( array_filter( array_unique( $ids ) ) );
        if ( empty( $ids ) ) {
            return;
        }
        global $wpdb;
        $placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
        // %i identifier for the table; the IN-list is the documented prepare()
        // exception (an IN() list cannot use %i) — every id is bound via prepare().
        // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        $rows = $wpdb->get_results( $wpdb->prepare(
            "SELECT post_id, score, score_payload FROM %i WHERE blog_id = %d AND post_id IN ( {$placeholders} )",
            array_merge(
                array( Seoforge_Utils::table( 'meta' ), Seoforge_Utils::current_blog_id() ),
                $ids
            )
        ), ARRAY_A );
        // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
        if ( $rows ) {
            foreach ( $rows as $r ) {
                self::$cache[ (int) $r['post_id'] ] = $r;
            }
        }
    }
}
