<?php

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

require_once AIEO_DIR . 'includes/seo/class-aieo-seo-store.php';
require_once AIEO_DIR . 'includes/class-aieo-content-types.php';

class AIEO_SEO_Score {

	const CURSOR_OPTION = 'aieo_seo_score_cursor';

	
	private static $anchor_cache = null;

	
	
	

	
	public static function enabled() {
		return class_exists( 'AIEO_DMM_Settings' ) && AIEO_DMM_Settings::get( 'seo.enabled', false );
	}

	
	private static $profile_cache = null;

	
	public static function site_profile() {
		if ( null !== self::$profile_cache ) {
			return self::$profile_cache;
		}
		$set = class_exists( 'AIEO_DMM_Settings' ) ? (string) AIEO_DMM_Settings::get( 'seo.site_profile', 'auto' ) : 'auto';
		if ( in_array( $set, array( 'shop', 'catalog', 'content' ), true ) ) {
			self::$profile_cache = $set;
			return self::$profile_cache;
		}
		self::$profile_cache = self::detect_site_profile();
		return self::$profile_cache;
	}

	
	public static function detect_site_profile() {
		if ( ! class_exists( 'WooCommerce' ) && ! post_type_exists( 'product' ) ) {
			return 'content';
		}
		global $wpdb;

		$table_exists = static function ( $name ) use ( $wpdb ) {
			return (string) $wpdb->get_var( $wpdb->prepare(
				'SELECT table_name FROM information_schema.TABLES WHERE table_schema = DATABASE() AND table_name = %s',
				$name
			) ) === $name;
		};

		
		$sp = $wpdb->prefix . 'aieo_sales_predictor';
		if ( $table_exists( $sp ) ) {
			
			if ( '' !== (string) $wpdb->get_var( "SELECT MAX(prediction_date) FROM `{$sp}` WHERE grain = 'parent'" ) ) {
				return 'shop';
			}
		}

		
		
		$vt = $wpdb->prefix . 'aieo_core_product_vitals';
		if ( $table_exists( $vt ) ) {
			
			if ( null !== $wpdb->get_var( "SELECT 1 FROM `{$vt}` WHERE product_latest_sale_date IS NOT NULL LIMIT 1" ) ) {
				return 'shop';
			}
		}

		
		$hpos = $wpdb->prefix . 'wc_orders';
		if ( $table_exists( $hpos ) ) {
			
			if ( null !== $wpdb->get_var( "SELECT 1 FROM `{$hpos}` WHERE type = 'shop_order' LIMIT 1" ) ) {
				return 'shop';
			}
		}
		if ( null !== $wpdb->get_var( "SELECT 1 FROM {$wpdb->posts} WHERE post_type = 'shop_order' LIMIT 1" ) ) {
			return 'shop';
		}

		return 'catalog';
	}

	
	public static function public_post_types() {
		return (array) apply_filters( 'aieo_seo_public_post_types', AIEO_Content_Types::post_types() );
	}

	
	public static function public_taxonomies() {
		return (array) apply_filters( 'aieo_seo_public_taxonomies', AIEO_Content_Types::taxonomies() );
	}

	
	public static function compute( $object_type, $object_id ) {
		$object_type = (string) $object_type;
		$object_id   = (int) $object_id;
		$row         = class_exists( 'AIEO_SEO_Store' ) ? (array) AIEO_SEO_Store::get( $object_type, $object_id ) : array();

		$checks    = self::run_checks( $object_type, $object_id, $row );
		$score     = self::weighted_score( $checks );
		$fix_count = 0;
		foreach ( $checks as $c ) {
			if ( 'pass' !== $c['status'] ) {
				$fix_count++;
			}
		}
		return array(
			'score'     => $score,
			'breakdown' => $checks,
			'fix_count' => $fix_count,
			'demand'    => self::demand_score( $object_type, $object_id ),
		);
	}

	
	public static function build( $object_type, $object_id ) {
		$r = self::compute( $object_type, $object_id );
		self::save( $object_type, $object_id, $r );
		return $r;
	}

	
	public static function get( $object_type, $object_id ) {
		global $wpdb;
		$t = $wpdb->prefix . 'aieo_seo_scores';
		
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM `{$t}` WHERE object_type = %s AND object_id = %d", (string) $object_type, (int) $object_id ), ARRAY_A );
		if ( $row && isset( $row['breakdown'] ) ) {
			$row['breakdown'] = json_decode( (string) $row['breakdown'], true );
		}
		return $row ?: null;
	}

	
	
	

	
	private static function run_checks( $object_type, $object_id, $row ) {
		$is_post = post_type_exists( $object_type );
		$post    = $is_post ? get_post( $object_id ) : null;

		
		
		
		$title    = trim( (string) ( $row['title'] ?? '' ) );
		$has_title_override = ( '' !== $title );
		if ( '' === $title ) {
			$title = $post ? (string) get_the_title( $post ) : '';
		}
		$desc     = trim( (string) ( $row['description'] ?? '' ) );
		$keyphrase = trim( (string) ( $row['focus_keyphrase'] ?? '' ) );

		$tlen  = function_exists( 'mb_strlen' ) ? mb_strlen( $title ) : strlen( $title );
		$dlen  = function_exists( 'mb_strlen' ) ? mb_strlen( $desc ) : strlen( $desc );
		$kp_lc = self::lc( $keyphrase );

		$checks = array();

		
		$checks[] = array(
			'id' => 'title_len', 'label' => __( 'SEO title length', 'ai-eshop-optimizer' ), 'weight' => 20,
			'status' => ( $tlen >= 30 && $tlen <= 60 ) ? 'pass' : ( ( $tlen >= 20 && $tlen <= 70 ) ? 'warn' : 'fail' ),
			'hint'   => sprintf( __( '%d chars (aim 30–60).', 'ai-eshop-optimizer' ), $tlen ) . ( $has_title_override ? '' : ' ' . __( '(using default format)', 'ai-eshop-optimizer' ) ),
		);

		
		$checks[] = array(
			'id' => 'desc_len', 'label' => __( 'Meta description', 'ai-eshop-optimizer' ), 'weight' => 15,
			'status' => ( '' === $desc ) ? 'warn' : ( ( $dlen >= 120 && $dlen <= 160 ) ? 'pass' : 'warn' ),
			'hint'   => ( '' === $desc ) ? __( 'No custom description — add one for better CTR.', 'ai-eshop-optimizer' ) : sprintf( __( '%d chars (aim 120–160).', 'ai-eshop-optimizer' ), $dlen ),
		);

		$kp_set = ( '' !== $keyphrase );

		
		$checks[] = array(
			'id' => 'keyphrase', 'label' => __( 'Focus keyphrase', 'ai-eshop-optimizer' ), 'weight' => 10,
			'status' => $kp_set ? 'pass' : 'warn',
			'hint'   => $kp_set ? $keyphrase : __( 'Set the converting search term (AI Assist can suggest it).', 'ai-eshop-optimizer' ),
		);

		
		
		
		$set_first = __( 'Set a focus keyphrase first.', 'ai-eshop-optimizer' );
		$checks[]  = array(
			'id' => 'kp_in_title', 'label' => __( 'Keyphrase in title', 'ai-eshop-optimizer' ), 'weight' => 12,
			
			
			'status' => ! $kp_set ? 'warn' : ( ( false !== strpos( self::lc( $title ), $kp_lc ) ) ? 'pass' : 'warn' ),
			'hint'   => ! $kp_set ? $set_first : __( 'The focus keyphrase should appear in the title.', 'ai-eshop-optimizer' ),
		);
		if ( $post ) {
			$checks[] = array(
				'id' => 'kp_in_slug', 'label' => __( 'Keyphrase in URL slug', 'ai-eshop-optimizer' ), 'weight' => 6,
				'status' => ! $kp_set ? 'warn' : ( ( false !== strpos( self::lc( str_replace( '-', ' ', (string) $post->post_name ) ), $kp_lc ) ) ? 'pass' : 'warn' ),
				'hint'   => ! $kp_set ? $set_first : __( 'Including it in the slug reinforces relevance.', 'ai-eshop-optimizer' ),
			);
		}
		$body     = $post ? ( (string) $post->post_content . ' ' . (string) $post->post_excerpt ) : '';
		$checks[] = array(
			'id' => 'kp_in_body', 'label' => __( 'Keyphrase in content', 'ai-eshop-optimizer' ), 'weight' => 8,
			'status' => ! $kp_set ? 'warn' : ( ( '' !== $body && false !== strpos( self::lc( wp_strip_all_tags( $body ) ), $kp_lc ) ) ? 'pass' : 'warn' ),
			'hint'   => ! $kp_set ? $set_first : __( 'Mention the keyphrase in the body copy.', 'ai-eshop-optimizer' ),
		);

		
		$has_canon = ( '' !== trim( (string) ( $row['canonical_url'] ?? '' ) ) ) || ( '' !== trim( (string) ( $row['canonical_override'] ?? '' ) ) );
		$checks[] = array(
			'id' => 'canonical', 'label' => __( 'Canonical URL', 'ai-eshop-optimizer' ), 'weight' => 8,
			'status' => $has_canon ? 'pass' : 'warn',
			'hint'   => $has_canon ? '' : __( 'Will be set on the next head rebuild.', 'ai-eshop-optimizer' ),
		);

		
		if ( $post ) {
			$has_img = has_post_thumbnail( $object_id ) || ( '' !== trim( (string) ( $row['og_image_custom_url'] ?? '' ) ) );
			$checks[] = array(
				'id' => 'image', 'label' => __( 'Featured/social image', 'ai-eshop-optimizer' ), 'weight' => 6,
				'status' => $has_img ? 'pass' : 'warn',
				'hint'   => $has_img ? '' : __( 'Add a featured image for richer social shares.', 'ai-eshop-optimizer' ),
			);
		}

		
		$noindex = ! empty( $row['robots_noindex'] ) && ( isset( $row['robots_default'] ) && ! $row['robots_default'] );
		$checks[] = array(
			'id' => 'indexable', 'label' => __( 'Indexable', 'ai-eshop-optimizer' ), 'weight' => 5,
			'status' => $noindex ? 'warn' : 'pass',
			'hint'   => $noindex ? __( 'This object is set to noindex — it will not rank.', 'ai-eshop-optimizer' ) : '',
		);

		return $checks;
	}

	
	private static function weighted_score( $checks ) {
		$total = 0;
		$got   = 0;
		foreach ( $checks as $c ) {
			$w      = (int) $c['weight'];
			$total += $w;
			if ( 'pass' === $c['status'] ) {
				$got += $w;
			} elseif ( 'warn' === $c['status'] ) {
				$got += (int) round( $w / 2 );
			}
		}
		return $total > 0 ? (int) round( $got / $total * 100 ) : 0;
	}

	
	
	

	
	public static function demand_score( $object_type, $object_id ) {
		if ( 'product' !== $object_type ) {
			
			
			
			
			
			if ( ! class_exists( 'AIEO_Content_Vitals' ) ) {
				require_once AIEO_DIR . 'includes/class-aieo-content-vitals.php';
			}
			return AIEO_Content_Vitals::demand( $object_type, $object_id );
		}
		
		
		
		if ( 'shop' !== self::site_profile() ) {
			return self::views_demand( $object_id );
		}
		global $wpdb;
		$sp = $wpdb->prefix . 'aieo_sales_predictor';
		$vt = $wpdb->prefix . 'aieo_core_product_vitals';

		
		
		if ( null === self::$anchor_cache ) {
			
			self::$anchor_cache = (string) $wpdb->get_var( "SELECT MAX(prediction_date) FROM `{$sp}` WHERE grain = 'parent'" );
		}
		$anchor = self::$anchor_cache;

		$row = null;
		if ( $anchor ) {
			
			$row = $wpdb->get_row( $wpdb->prepare(
				"SELECT sp.chosen_items_per_day, sp.reliability_score, v.product_latest_sale_date, v.stock
				 FROM `{$sp}` sp
				 LEFT JOIN `{$vt}` v ON v.product_id = sp.product_id
				 WHERE sp.grain = 'parent' AND sp.product_id = %d AND sp.prediction_date = %s",
				(int) $object_id,
				$anchor
			), ARRAY_A );
		}

		if ( ! $row ) {
			return self::recency_demand( $object_id );
		}

		
		
		$vel       = max( 0.0, (float) $row['chosen_items_per_day'] );
		$vel_score = 100.0 * ( 1.0 - exp( -$vel / 1.5 ) );

		
		$rel    = min( 1.0, max( 0.0, (float) ( $row['reliability_score'] ?? 0 ) ) );
		$temper = 0.5 + 0.5 * $rel;

		
		
		$recency  = max( 0.35, self::recency_factor( $row['product_latest_sale_date'] ?? '' ) );
		$in_stock = ( isset( $row['stock'] ) && (int) $row['stock'] > 0 ) ? 1.0 : 0.85;

		return (int) max( 0, min( 100, round( $vel_score * $temper * $recency * $in_stock ) ) );
	}

	
	private static function views_demand( $object_id ) {
		global $wpdb;
		$pm = $wpdb->prefix . 'aieo_product_session_metrics';
		static $has_pm = null;
		if ( null === $has_pm ) {
			$has_pm = ( (string) $wpdb->get_var( $wpdb->prepare(
				'SELECT table_name FROM information_schema.TABLES WHERE table_schema = DATABASE() AND table_name = %s',
				$pm
			) ) === $pm );
		}
		if ( ! $has_pm ) {
			return 0;
		}
		
		$row = $wpdb->get_row( $wpdb->prepare(
			"SELECT unique_sessions_viewed, engagement_score FROM `{$pm}` WHERE product_id = %d",
			(int) $object_id
		), ARRAY_A );
		if ( ! $row ) {
			return 0;
		}
		$views = max( 0, (int) $row['unique_sessions_viewed'] );
		if ( 0 === $views ) {
			return 0;
		}
		$reach  = 100.0 * ( 1.0 - exp( -$views / 50.0 ) );
		$eng    = min( 1.0, max( 0.0, (float) $row['engagement_score'] / 100 ) );
		$temper = 0.5 + 0.5 * $eng;
		return (int) max( 0, min( 100, round( $reach * $temper ) ) );
	}

	
	private static function recency_demand( $object_id ) {
		global $wpdb;
		$vt = $wpdb->prefix . 'aieo_core_product_vitals';
		
		$v = $wpdb->get_row( $wpdb->prepare( "SELECT product_latest_sale_date, stock FROM `{$vt}` WHERE product_id = %d", (int) $object_id ), ARRAY_A );
		if ( ! $v ) {
			return 0;
		}
		$recency = self::recency_factor( $v['product_latest_sale_date'] ?? '' );
		if ( $recency <= 0 ) {
			return 0; 
		}
		$in_stock = ( isset( $v['stock'] ) && (int) $v['stock'] > 0 ) ? 1.0 : 0.85;
		return (int) max( 0, min( 100, round( 100 * $recency * $in_stock ) ) );
	}

	
	private static function recency_factor( $latest_sale_date ) {
		if ( empty( $latest_sale_date ) ) {
			return 0.0;
		}
		$ts = strtotime( (string) $latest_sale_date . ' UTC' );
		if ( ! $ts ) {
			return 0.0;
		}
		$days = max( 0, (int) floor( ( (int) current_time( 'timestamp', true ) - $ts ) / DAY_IN_SECONDS ) );
		return pow( 0.5, $days / 90 );
	}

	
	
	

	private static function save( $object_type, $object_id, $r ) {
		global $wpdb;
		$t              = $wpdb->prefix . 'aieo_seo_scores';
		$breakdown_json = (string) wp_json_encode( $r['breakdown'], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );

		
		$existing = $wpdb->get_row( $wpdb->prepare( "SELECT id, score, fix_count, breakdown FROM `{$t}` WHERE object_type = %s AND object_id = %d", (string) $object_type, (int) $object_id ), ARRAY_A );
		$id       = $existing ? (int) $existing['id'] : 0;

		
		
		
		$unchanged = $id > 0
			&& (int) $existing['score'] === (int) $r['score']
			&& (int) $existing['fix_count'] === (int) $r['fix_count']
			&& (string) $existing['breakdown'] === $breakdown_json;

		if ( ! $unchanged ) {
			$data = array(
				'score'           => (int) $r['score'],
				'breakdown'       => $breakdown_json,
				'fix_count'       => (int) $r['fix_count'],
				'last_calculated' => current_time( 'mysql', true ),
			);
			$fmt = array( '%d', '%s', '%d', '%s' );
			if ( $id > 0 ) {
				$wpdb->update( $t, $data, array( 'id' => $id ), $fmt, array( '%d' ) );
			} else {
				$data['object_type'] = (string) $object_type;
				$data['object_id']   = (int) $object_id;
				$fmt[]               = '%s';
				$fmt[]               = '%d';
				$wpdb->insert( $t, $data, $fmt );
			}
		}

		
		
		
		if ( class_exists( 'AIEO_SEO_Store' ) ) {
			$cur        = (array) AIEO_SEO_Store::get( $object_type, $object_id );
			$cur_demand = ( array_key_exists( 'demand_score', $cur ) && null !== $cur['demand_score'] ) ? (int) $cur['demand_score'] : -1;
			if ( $cur_demand !== (int) $r['demand'] ) {
				AIEO_SEO_Store::save( $object_type, $object_id, array( 'demand_score' => (int) $r['demand'] ) );
			}
		}
	}

	
	
	

	
	public static function run_backfill( $limit = 1500, $cursor_in = null ) {
		$out = array( 'built' => 0, 'done' => true, 'cursor' => array( 'phase' => 0, 'last_id' => 0 ) );
		if ( ! self::enabled() ) {
			return $out;
		}
		$budget     = max( 1, (int) $limit );
		$standalone = is_array( $cursor_in );
		$cursor     = $standalone ? $cursor_in : (array) get_option( self::CURSOR_OPTION, array() );
		$phase      = isset( $cursor['phase'] ) ? (int) $cursor['phase'] : 0;
		$last       = isset( $cursor['last_id'] ) ? (int) $cursor['last_id'] : 0;
		$wrapped    = false;

		
		
		
		$types = ( 0 === $phase )
			? array( 'product' )
			: array_values( array_diff( array_keys( self::public_post_types() ), array( 'product' ) ) );
		$rows   = self::next_batch( $types, $last, $budget );
		foreach ( $rows as $row ) {
			$last = (int) $row['id'];
			try {
				self::build( (string) $row['type'], (int) $row['id'] );
				$out['built']++;
			} catch ( \Throwable $e ) {
				if ( function_exists( 'aieo_debug_log' ) ) {
					aieo_debug_log( 'AIEO_SEO score build failed for ' . $row['type'] . ':' . (int) $row['id'] . ' — ' . $e->getMessage() );
				}
			}
			$budget--;
		}
		if ( empty( $rows ) ) {
			if ( 0 === $phase ) {
				$phase = 1;
				$last  = 0;
			} else {
				$phase   = 0;
				$last    = 0;
				$wrapped = true;
			}
		}
		if ( ! $standalone ) {
			update_option( self::CURSOR_OPTION, array( 'phase' => $phase, 'last_id' => $last ), false );
		}
		$out['done']   = $wrapped;
		$out['cursor'] = array( 'phase' => $phase, 'last_id' => $last );
		return $out;
	}

	
	private static function next_batch( $types, $after, $limit ) {
		global $wpdb;
		$in   = "'" . implode( "','", array_map( 'esc_sql', $types ) ) . "'";
		
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT ID, post_type FROM {$wpdb->posts}
			 WHERE post_status = 'publish' AND post_type IN ($in) AND ID > %d
			 ORDER BY ID ASC LIMIT %d",
			(int) $after,
			(int) $limit
		) );
		$out = array();
		foreach ( (array) $rows as $r ) {
			$out[] = array( 'type' => (string) $r->post_type, 'id' => (int) $r->ID );
		}
		return $out;
	}

	
	private static function lc( $s ) {
		$s = (string) $s;
		return function_exists( 'mb_strtolower' ) ? mb_strtolower( $s, 'UTF-8' ) : strtolower( $s );
	}
}
