<?php
/**
 * Dashboard News Shorts service.
 *
 * Provides a small, self-contained feed layer for the Dashboard Shorts modal.
 * It fetches Google News RSS feeds through WordPress HTTP APIs, caches compact
 * results, and returns incremental slices so the Dashboard UI can lazy-load the
 * stream without affecting the rest of the module.
 *
 * @package Noesia_Smart_Waiter
 */

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

if ( ! class_exists( 'Noesia_SWA_Dashboard_News_Shorts_Service' ) ) {
    /**
     * Dashboard News Shorts service.
     */
    class Noesia_SWA_Dashboard_News_Shorts_Service {

        /**
         * Maximum number of cached stories kept per category.
         *
         * @var int
         */
        private $max_cached_items = 100;

        /**
         * Get category definitions.
         *
         * @param array<string,mixed> $options Plugin options.
         * @return array<string,array<string,string>>
         */
        public function get_categories( $options = array() ) {
            $local_query = $this->get_local_query_seed( $options );

            return array(
                'top' => array(
                    'label' => __( 'Top', 'noesia-smart-waiter' ),
                    'topic' => '',
                    'query' => '',
                ),
                'world' => array(
                    'label' => __( 'World', 'noesia-smart-waiter' ),
                    'topic' => 'WORLD',
                    'query' => '',
                ),
                'business' => array(
                    'label' => __( 'Business', 'noesia-smart-waiter' ),
                    'topic' => 'BUSINESS',
                    'query' => '',
                ),
                'technology' => array(
                    'label' => __( 'Technology', 'noesia-smart-waiter' ),
                    'topic' => 'TECHNOLOGY',
                    'query' => '',
                ),
                'hospitality' => array(
                    'label' => __( 'Hospitality', 'noesia-smart-waiter' ),
                    'topic' => '',
                    'query' => 'hospitality OR restaurant OR hotel OR food service',
                ),
                'health' => array(
                    'label' => __( 'Health', 'noesia-smart-waiter' ),
                    'topic' => 'HEALTH',
                    'query' => '',
                ),
                'travel' => array(
                    'label' => __( 'Travel', 'noesia-smart-waiter' ),
                    'topic' => '',
                    'query' => 'travel OR tourism OR flights OR destinations',
                ),
                'local' => array(
                    'label' => __( 'Local', 'noesia-smart-waiter' ),
                    'topic' => '',
                    'query' => $local_query,
                ),
            );
        }

        /**
         * Get one incremental feed slice for the modal.
         *
         * @param array<string,mixed> $options Plugin options.
         * @param string              $category Requested category.
         * @param int                 $offset Slice offset.
         * @param int                 $limit Slice size.
         * @return array<string,mixed>
         */
        public function get_feed_slice( $options, $category = 'top', $offset = 0, $limit = 12 ) {
            $options    = is_array( $options ) ? $options : array();
            $categories = $this->get_categories( $options );
            $category   = sanitize_key( (string) $category );
            $offset     = max( 0, absint( $offset ) );
            $limit      = max( 1, min( 24, absint( $limit ) ) );

            if ( ! isset( $categories[ $category ] ) ) {
                $category = 'top';
            }

            $items = $this->get_cached_category_items( $options, $category, $categories[ $category ] );
            $total = count( $items );

            return array(
                'category'   => $category,
                'label'      => isset( $categories[ $category ]['label'] ) ? (string) $categories[ $category ]['label'] : ucfirst( $category ),
                'offset'     => $offset,
                'limit'      => $limit,
                'total'      => $total,
                'has_more'   => ( $offset + $limit ) < $total,
                'items'      => array_values( array_slice( $items, $offset, $limit ) ),
                'categories' => $this->get_public_category_labels( $categories ),
            );
        }

        /**
         * Get a label-only map for the modal UI.
         *
         * @param array<string,array<string,string>> $categories Category definitions.
         * @return array<string,string>
         */
        private function get_public_category_labels( $categories ) {
            $labels = array();

            if ( ! is_array( $categories ) ) {
                return $labels;
            }

            foreach ( $categories as $key => $config ) {
                $key = sanitize_key( (string) $key );

                if ( '' === $key ) {
                    continue;
                }

                $labels[ $key ] = isset( $config['label'] ) ? sanitize_text_field( (string) $config['label'] ) : ucfirst( $key );
            }

            return $labels;
        }

        /**
         * Get one category cache bucket.
         *
         * @param array<string,mixed>  $options Plugin options.
         * @param string               $category Category key.
         * @param array<string,string> $config Category config.
         * @return array<int,array<string,mixed>>
         */
        private function get_cached_category_items( $options, $category, $config ) {
            $context   = $this->get_feed_context( $options );
            $cache_key = 'noesia_swa_dashboard_news_shorts_' . md5( strtolower( wp_json_encode( array( $category, $context, $config ) ) ) );
            $cached    = get_transient( $cache_key );

            if ( is_array( $cached ) ) {
                return $cached;
            }

            $url   = $this->build_feed_url( $context, $config );
            $items = $this->fetch_feed_items( $url );

            if ( count( $items ) > $this->max_cached_items ) {
                $items = array_slice( $items, 0, $this->max_cached_items );
            }

            set_transient( $cache_key, $items, 10 * MINUTE_IN_SECONDS );

            return $items;
        }

        /**
         * Resolve feed context.
         *
         * @param array<string,mixed> $options Plugin options.
         * @return array<string,string>
         */
        private function get_feed_context( $options ) {
            $lang    = 'en';
            $country = 'US';

            if ( class_exists( 'Noesia_SWA_Dashboard_Data' ) ) {
                $data_service = new Noesia_SWA_Dashboard_Data();
                $lang         = $data_service->get_dashboard_language( $options );
                $country      = $data_service->get_news_country( $lang, $options );
            } else {
                $lang = isset( $options['news_language'] ) ? sanitize_key( (string) $options['news_language'] ) : 'en';
            }

            $lang    = preg_replace( '/[^a-z\-]/', '', strtolower( (string) $lang ) );
            $country = strtoupper( preg_replace( '/[^A-Z]/', '', (string) $country ) );
            $country = substr( $country, 0, 2 );

            if ( '' === $lang ) {
                $lang = 'en';
            }

            if ( '' === $country ) {
                $country = 'US';
            }

            return array(
                'lang'    => $lang,
                'country' => $country,
            );
        }

        /**
         * Build a Google News RSS URL.
         *
         * @param array<string,string> $context Feed context.
         * @param array<string,string> $config Category config.
         * @return string
         */
        private function build_feed_url( $context, $config ) {
            $lang    = isset( $context['lang'] ) ? (string) $context['lang'] : 'en';
            $country = isset( $context['country'] ) ? (string) $context['country'] : 'US';
            $topic   = isset( $config['topic'] ) ? strtoupper( sanitize_key( (string) $config['topic'] ) ) : '';
            $query   = isset( $config['query'] ) ? trim( preg_replace( '/\s+/', ' ', sanitize_text_field( (string) $config['query'] ) ) ) : '';

            $lang_for_google = ( 'en' === $lang ) ? 'en-US' : $lang;
            $args            = array(
                'hl'   => $lang_for_google,
                'gl'   => $country,
                'ceid' => $country . ':' . $lang,
            );

            if ( '' !== $topic ) {
                return add_query_arg( $args, 'https://news.google.com/rss/headlines/section/topic/' . rawurlencode( $topic ) );
            }

            if ( '' !== $query ) {
                $args['q'] = $query;
                return add_query_arg( $args, 'https://news.google.com/rss/search' );
            }

            return add_query_arg( $args, 'https://news.google.com/rss' );
        }

        /**
         * Fetch and parse one RSS feed.
         *
         * @param string $url Feed URL.
         * @return array<int,array<string,mixed>>
         */
        private function fetch_feed_items( $url ) {
            $items    = array();
            $response = wp_safe_remote_get(
                esc_url_raw( $url ),
                array(
                    'timeout'    => 15,
                    'sslverify'  => true,
                    'user-agent' => 'Noesia Smart Waiter/' . ( defined( 'NOESIA_SWA_VERSION' ) ? NOESIA_SWA_VERSION : '1.0.0' ) . '; ' . home_url( '/' ),
                    'headers'    => array(
                        'Accept' => 'application/rss+xml, application/xml, text/xml;q=0.9, */*;q=0.8',
                    ),
                )
            );

            if ( is_wp_error( $response ) ) {
                return $items;
            }

            $body = trim( (string) wp_remote_retrieve_body( $response ) );

            if ( '' === $body ) {
                return $items;
            }

            $previous_state = libxml_use_internal_errors( true );
            $xml            = simplexml_load_string( $body );
            libxml_clear_errors();
            libxml_use_internal_errors( $previous_state );

            if ( ! $xml || empty( $xml->channel->item ) ) {
                return $items;
            }

            foreach ( $xml->channel->item as $item ) {
                $title       = isset( $item->title ) ? sanitize_text_field( (string) $item->title ) : '';
                $link        = isset( $item->link ) ? esc_url_raw( (string) $item->link ) : '';
                $description = isset( $item->description ) ? (string) $item->description : '';
                $summary     = $this->sanitize_summary( $description );
                $source      = '';
                $pub_date    = isset( $item->pubDate ) ? strtotime( (string) $item->pubDate ) : false;
                $pub_date_ms = $pub_date ? ( (int) $pub_date * 1000 ) : 0;

                if ( isset( $item->source ) ) {
                    $source = sanitize_text_field( (string) $item->source );
                }

                if ( '' === $title || '' === $link ) {
                    continue;
                }

                $items[] = array(
                    'title'       => $title,
                    'summary'     => $summary,
                    'link'        => $link,
                    'source'      => $source,
                    'pubDateMs'   => $pub_date_ms,
                    'publishedAt' => $pub_date ? gmdate( 'c', (int) $pub_date ) : '',
                );
            }

            $items = $this->deduplicate_items( $items );

            usort(
                $items,
                static function ( $left, $right ) {
                    $left_ms  = isset( $left['pubDateMs'] ) ? (int) $left['pubDateMs'] : 0;
                    $right_ms = isset( $right['pubDateMs'] ) ? (int) $right['pubDateMs'] : 0;

                    return $right_ms <=> $left_ms;
                }
            );

            return array_values( $items );
        }

        /**
         * Deduplicate parsed items.
         *
         * @param array<int,array<string,mixed>> $items Items.
         * @return array<int,array<string,mixed>>
         */
        private function deduplicate_items( $items ) {
            $unique = array();
            $keys   = array();

            foreach ( $items as $item ) {
                $title = isset( $item['title'] ) ? (string) $item['title'] : '';
                $link  = isset( $item['link'] ) ? (string) $item['link'] : '';
                $key   = md5( strtolower( $link . '|' . $title ) );

                if ( isset( $keys[ $key ] ) ) {
                    continue;
                }

                $keys[ $key ] = true;
                $unique[]     = $item;
            }

            return $unique;
        }

        /**
         * Build the local search seed.
         *
         * @param array<string,mixed> $options Plugin options.
         * @return string
         */
        private function get_local_query_seed( $options ) {
            $options       = is_array( $options ) ? $options : array();
            $business_name = isset( $options['business_name'] ) ? sanitize_text_field( (string) $options['business_name'] ) : '';
            $weather_city  = isset( $options['weather_city'] ) ? sanitize_text_field( (string) $options['weather_city'] ) : '';
            $site_name     = sanitize_text_field( wp_strip_all_tags( (string) get_bloginfo( 'name' ) ) );
            $host          = wp_parse_url( home_url( '/' ), PHP_URL_HOST );
            $host          = is_string( $host ) ? preg_replace( '/^www\./i', '', $host ) : '';

            if ( '' !== $weather_city ) {
                return $weather_city . ' local news';
            }

            if ( '' !== $business_name ) {
                return $business_name . ' local news';
            }

            if ( '' !== $site_name ) {
                return $site_name . ' local news';
            }

            if ( '' !== $host ) {
                return $host . ' local news';
            }

            return 'local news';
        }

        /**
         * Sanitize one summary string.
         *
         * @param string $raw Raw summary.
         * @return string
         */
        private function sanitize_summary( $raw ) {
            $raw = html_entity_decode( (string) $raw, ENT_QUOTES, 'UTF-8' );
            $raw = wp_strip_all_tags( $raw, true );
            $raw = preg_replace( '/\s+/', ' ', trim( (string) $raw ) );

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

            if ( function_exists( 'mb_substr' ) && function_exists( 'mb_strlen' ) ) {
                if ( mb_strlen( $raw ) > 220 ) {
                    $raw = rtrim( mb_substr( $raw, 0, 217 ) ) . '…';
                }
            } elseif ( strlen( $raw ) > 220 ) {
                $raw = rtrim( substr( $raw, 0, 217 ) ) . '…';
            }

            return sanitize_text_field( $raw );
        }
    }
}
