<?php
/**
 * Dashboard payload for React admin (tab=alerts).
 *
 * @package Newsroom_AI_Assistant
 */

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

if ( ! function_exists( 'newsai_dashboard_registry_i18n' ) ) {
	function newsai_dashboard_registry_i18n() {
		return [
			'loading'        => __( 'Loading dashboard…', 'newsroom-ai-assistant' ),
			'error'          => __( 'Dashboard could not load.', 'newsroom-ai-assistant' ),
			'classicLink'    => __( 'Open classic dashboard (kanban & full form)', 'newsroom-ai-assistant' ),
			'openClassic'    => __( 'Open classic dashboard', 'newsroom-ai-assistant' ),
			'classicDashTitle' => __( 'Need kanban & radar?', 'newsroom-ai-assistant' ),
			'classicDashDesc'  => __( 'Classic dashboard includes the full kanban board, competitor radar, and legacy task tools.', 'newsroom-ai-assistant' ),
			'chip_all'       => __( 'All', 'newsroom-ai-assistant' ),
			'chip_overdue'   => __( 'Overdue', 'newsroom-ai-assistant' ),
			'chip_today'     => __( 'Due today', 'newsroom-ai-assistant' ),
			'chip_unassigned'=> __( 'Unassigned', 'newsroom-ai-assistant' ),
			'chip_done'      => __( 'Done', 'newsroom-ai-assistant' ),
			'col_task'       => __( 'Task', 'newsroom-ai-assistant' ),
			'col_date'       => __( 'Date', 'newsroom-ai-assistant' ),
			'col_writer'     => __( 'Writer', 'newsroom-ai-assistant' ),
			'col_status'     => __( 'Status', 'newsroom-ai-assistant' ),
			'col_actions'    => __( 'Actions', 'newsroom-ai-assistant' ),
			'actions_label'  => __( 'Actions…', 'newsroom-ai-assistant' ),
			'actions_edit'   => __( 'Edit', 'newsroom-ai-assistant' ),
			'actions_done'   => __( 'Mark done', 'newsroom-ai-assistant' ),
			'actions_delete' => __( 'Delete', 'newsroom-ai-assistant' ),
			'no_tasks'       => __( 'No tasks yet.', 'newsroom-ai-assistant' ),
			'no_articles'    => __( 'No published articles yet.', 'newsroom-ai-assistant' ),
			'all_users'      => __( 'Anyone', 'newsroom-ai-assistant' ),
			'add_task'       => __( 'Add or edit tasks', 'newsroom-ai-assistant' ),
			'latest_posts'   => __( 'Latest published articles', 'newsroom-ai-assistant' ),
			'view_all'       => __( 'View all', 'newsroom-ai-assistant' ),
			'view'           => __( 'View', 'newsroom-ai-assistant' ),
			'tasks_title'    => __( 'Tasks & workflow', 'newsroom-ai-assistant' ),
			'writer_perf'    => __( 'Writer performance', 'newsroom-ai-assistant' ),
			'writer_perf_d'  => __( 'Published posts (last 30 days).', 'newsroom-ai-assistant' ),
			'seo_issues'     => __( 'SEO issues', 'newsroom-ai-assistant' ),
			'desk_alerts'    => __( 'Desk Alerts', 'newsroom-ai-assistant' ),
			'avg_readiness'  => __( 'Avg AI Readiness', 'newsroom-ai-assistant' ),
			'desk_readiness' => __( 'Desk AI Readiness', 'newsroom-ai-assistant' ),
			'seo_health'     => __( 'Site SEO health', 'newsroom-ai-assistant' ),
			'active_tasks'   => __( 'Active tasks', 'newsroom-ai-assistant' ),
			'articles_week'  => __( 'Articles this week', 'newsroom-ai-assistant' ),
			'vs_last_week'   => __( '%1$s %2$d% vs last week', 'newsroom-ai-assistant' ),
			'overdue_today'  => __( '%1$d overdue, %2$d due today', 'newsroom-ai-assistant' ),
			'readiness_sample'=> __( 'From %d scored posts (30d)', 'newsroom-ai-assistant' ),
			'no_scores_yet'  => __( 'No scores yet', 'newsroom-ai-assistant' ),
			'needs_action'   => __( 'Needs immediate action', 'newsroom-ai-assistant' ),
			'all_clear'      => __( 'All clear', 'newsroom-ai-assistant' ),
			'google_ok_t'    => __( 'No active core updates', 'newsroom-ai-assistant' ),
			'google_ok_d'    => __( 'Google search systems are operating normally.', 'newsroom-ai-assistant' ),
			'google_warn'    => __( 'Expect high volatility in SERPs. Avoid major SEO changes.', 'newsroom-ai-assistant' ),
			'view_details'   => __( 'View details', 'newsroom-ai-assistant' ),
			'confirm_delete' => __( 'Delete this task?', 'newsroom-ai-assistant' ),
			'confirm_done'   => __( 'Mark this task as done?', 'newsroom-ai-assistant' ),
			'workflow'       => __( 'Editorial workflow', 'newsroom-ai-assistant' ),
			'workflow_sub'   => __( 'Track and manage your content pipeline', 'newsroom-ai-assistant' ),
			'view_table'     => __( 'Table', 'newsroom-ai-assistant' ),
			'view_kanban'    => __( 'Kanban', 'newsroom-ai-assistant' ),
			'col_overdue'    => __( 'Overdue', 'newsroom-ai-assistant' ),
			'col_due_soon'   => __( 'Due soon', 'newsroom-ai-assistant' ),
			'col_scheduled'  => __( 'Scheduled', 'newsroom-ai-assistant' ),
			'col_done'       => __( 'Done', 'newsroom-ai-assistant' ),
			'trend_radar'    => __( 'Trend radar', 'newsroom-ai-assistant' ),
			'competitor'     => __( 'Competitor watch', 'newsroom-ai-assistant' ),
			'competitor_sub' => __( 'Latest articles from tracked competitors', 'newsroom-ai-assistant' ),
			'trends_tab'     => __( 'Trends', 'newsroom-ai-assistant' ),
			'news_tab'       => __( 'News', 'newsroom-ai-assistant' ),
			'welcome'        => __( 'Welcome back. Here\'s what\'s happening today.', 'newsroom-ai-assistant' ),
			'new_task'       => __( 'New task', 'newsroom-ai-assistant' ),
			'published_badge'=> __( 'Published', 'newsroom-ai-assistant' ),
			'task_title'     => __( 'Task title', 'newsroom-ai-assistant' ),
			'task_keyword'   => __( 'Target keyword', 'newsroom-ai-assistant' ),
			'task_ref'       => __( 'Reference URL', 'newsroom-ai-assistant' ),
			'task_notes'     => __( 'Notes / brief', 'newsroom-ai-assistant' ),
			'task_date'      => __( 'Due date', 'newsroom-ai-assistant' ),
			'task_author'    => __( 'Assign to', 'newsroom-ai-assistant' ),
			'task_lead'      => __( 'Lead days', 'newsroom-ai-assistant' ),
			'task_recurrence'=> __( 'Recurrence', 'newsroom-ai-assistant' ),
			'rec_none'       => __( 'One-time', 'newsroom-ai-assistant' ),
			'rec_yearly'     => __( 'Yearly', 'newsroom-ai-assistant' ),
			'rec_monthly'    => __( 'Monthly', 'newsroom-ai-assistant' ),
			'save_task'      => __( 'Save task', 'newsroom-ai-assistant' ),
			'cancel'         => __( 'Cancel', 'newsroom-ai-assistant' ),
			'edit_task'      => __( 'Edit task', 'newsroom-ai-assistant' ),
			'task_saved'     => __( 'Task saved.', 'newsroom-ai-assistant' ),
			'task_deleted'   => __( 'Task deleted.', 'newsroom-ai-assistant' ),
		];
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_task_status' ) ) {
	/**
	 * @param object $row        Task row.
	 * @param bool   $has_status Status column exists.
	 * @param int    $now_time   Today midnight ts.
	 * @return array{key:string,label:string}
	 */
	function newsai_dashboard_registry_task_status( $row, $has_status, $now_time ) {
		$status_val = $has_status ? (string) ( $row->status ?? 'pending' ) : 'pending';
		if ( $status_val === 'done' ) {
			return [ 'key' => 'done', 'label' => __( 'Done', 'newsroom-ai-assistant' ) ];
		}
		$task_time = function_exists( 'newsai_task_resolve_due_ts' )
			? newsai_task_resolve_due_ts( $row, $now_time )
			: (int) strtotime( gmdate( 'Y-m-d' ) );
		$diff_days = ( $task_time - (int) $now_time ) / 86400;
		$lead      = max( 1, (int) ( $row->lead_days ?? 7 ) );
		if ( $diff_days < 0 ) {
			return [ 'key' => 'overdue', 'label' => __( 'OVERDUE', 'newsroom-ai-assistant' ) ];
		}
		if ( $diff_days <= $lead ) {
			return [
				'key'   => 'due-soon',
				'label' => sprintf(
					/* translators: %d: days until due */
					__( 'Due in %d d', 'newsroom-ai-assistant' ),
					(int) $diff_days
				),
			];
		}
		return [ 'key' => 'scheduled', 'label' => __( 'Scheduled', 'newsroom-ai-assistant' ) ];
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_initials' ) ) {
	function newsai_dashboard_registry_initials( $name ) {
		$name = trim( (string) $name );
		if ( $name === '' || $name === __( 'Anyone', 'newsroom-ai-assistant' ) ) {
			return '—';
		}
		$parts = preg_split( '/\s+/', $name );
		if ( count( $parts ) >= 2 ) {
			$a = function_exists( 'mb_substr' ) ? mb_substr( $parts[0], 0, 1 ) : substr( $parts[0], 0, 1 );
			$b = function_exists( 'mb_substr' ) ? mb_substr( $parts[1], 0, 1 ) : substr( $parts[1], 0, 1 );
			return strtoupper( $a . $b );
		}
		return strtoupper( function_exists( 'mb_substr' ) ? mb_substr( $name, 0, 2 ) : substr( $name, 0, 2 ) );
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_sparkline_posts' ) ) {
	function newsai_dashboard_registry_sparkline_posts( $days = 7 ) {
		global $wpdb;
		$series = [];
		for ( $i = $days - 1; $i >= 0; $i-- ) {
			$start = gmdate( 'Y-m-d 00:00:00', time() - ( $i * DAY_IN_SECONDS ) );
			$end   = gmdate( 'Y-m-d 00:00:00', time() - ( ( $i - 1 ) * DAY_IN_SECONDS ) );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$series[] = (int) $wpdb->get_var(
				$wpdb->prepare(
					"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish' AND post_date_gmt >= %s AND post_date_gmt < %s",
					$start,
					$end
				)
			);
		}
		return $series;
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_sparkline_tasks' ) ) {
	/**
	 * Open-task counts per day (tasks whose computed due date falls on each of the last N days).
	 *
	 * @param string $table Alerts table name.
	 * @param int    $days  Window.
	 * @return int[]
	 */
	function newsai_dashboard_registry_sparkline_tasks( $table, $days = 7 ) {
		global $wpdb;
		$series   = [];
		$now_time = (int) strtotime( gmdate( 'Y-m-d' ) );
		for ( $i = $days - 1; $i >= 0; $i-- ) {
			$day_ts = $now_time - ( $i * DAY_IN_SECONDS );
			$count  = 0;
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$rows = $wpdb->get_results( "SELECT day, month, year, status FROM {$table} LIMIT 500" );
			foreach ( (array) $rows as $r ) {
				if ( isset( $r->status ) && $r->status === 'done' ) {
					continue;
				}
				$y = (int) ( $r->year > 0 ? $r->year : gmdate( 'Y' ) );
				$m = (int) ( $r->month > 0 ? $r->month : gmdate( 'm' ) );
				$d = max( 1, (int) $r->day );
				$task_ts = strtotime( sprintf( '%04d-%02d-%02d', $y, $m, $d ) );
				if ( $task_ts && gmdate( 'Y-m-d', $task_ts ) === gmdate( 'Y-m-d', $day_ts ) ) {
					++$count;
				}
			}
			$series[] = $count;
		}
		return $series;
	}
}

if ( ! function_exists( 'newsai_dashboard_render_kpi_card' ) ) {
	/**
	 * Premium KPI card for classic dashboard.
	 *
	 * @param string $label       Metric label.
	 * @param string $value       Main value HTML-safe.
	 * @param string $delta_text  Pill text.
	 * @param string $delta_class is-up|is-down|is-neutral.
	 * @param string $spark_html  Mini sparkline markup.
	 * @param string $href        Optional link wrapper.
	 * @param string $variant     default|hero|alert|success — visual emphasis from live metrics.
	 * @param string $sub         Optional footnote (e.g. sample size).
	 * @return string
	 */
	function newsai_dashboard_render_kpi_card( $label, $value, $delta_text, $delta_class, $spark_html, $href = '', $variant = 'default', $sub = '' ) {
		$tag     = $href !== '' ? 'a' : 'div';
		$extra   = $href !== '' ? ' href="' . esc_url( $href ) . '"' : '';
		$variant = sanitize_key( (string) $variant );
		if ( ! in_array( $variant, [ 'default', 'hero', 'alert', 'success' ], true ) ) {
			$variant = 'default';
		}
		$class = 'nros-dash-kpi nros-dash-kpi--' . $variant . ( $href !== '' ? ' nros-dash-kpi--link' : '' );
		ob_start();
		?>
		<<?php echo $tag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> class="<?php echo esc_attr( $class ); ?>"<?php echo $extra; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<div class="nros-dash-kpi__top">
				<span class="nros-dash-kpi__label"><?php echo esc_html( $label ); ?></span>
				<?php if ( $delta_text !== '' ) : ?>
					<span class="nros-dash-kpi__pill <?php echo esc_attr( $delta_class ); ?>"><?php echo esc_html( $delta_text ); ?></span>
				<?php endif; ?>
			</div>
			<div class="nros-dash-kpi__val"><?php echo esc_html( $value ); ?></div>
			<?php if ( $sub !== '' ) : ?>
				<div class="nros-dash-kpi__sub"><?php echo esc_html( $sub ); ?></div>
			<?php endif; ?>
			<?php if ( $spark_html !== '' ) : ?>
				<div class="nros-dash-kpi__spark"><?php echo $spark_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
			<?php endif; ?>
		</<?php echo $tag; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
		<?php
		return (string) ob_get_clean();
	}
}

if ( ! function_exists( 'newsai_get_dashboard_desk_readiness_kpis' ) ) {
	/**
	 * Site-wide AI Readiness from saved Focus + Discover post meta (last N days).
	 *
	 * @param int $days Window.
	 * @return array{avg:int,focus_avg:int,discover_avg:int,sample:int,posts_recent:int,low_count:int}
	 */
	function newsai_get_dashboard_desk_readiness_kpis( $days = 30 ) {
		$days = max( 7, min( 90, (int) $days ) );
		$cached = get_transient( 'newsai_dash_readiness_v2_' . $days );
		if ( is_array( $cached ) && isset( $cached['avg'] ) ) {
			return $cached;
		}

		global $wpdb;
		$since = function_exists( 'newsai_sql_datetime_days_ago' )
			? newsai_sql_datetime_days_ago( $days, true )
			: gmdate( 'Y-m-d H:i:s', strtotime( '-' . $days . ' days' ) );

		$posts_recent = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish' AND post_date_gmt > %s",
				$since
			)
		);

		$focus_key  = function_exists( 'newsai_focus_score_meta_key' ) ? newsai_focus_score_meta_key() : '_newsai_focus_score';
		$pinned_key = function_exists( 'newsai_focus_score_history_meta_key' ) ? newsai_focus_score_history_meta_key() : '_newsai_focus_score_at_publish';
		$disc_key   = function_exists( 'newsai_discover_score_meta_key' ) ? newsai_discover_score_meta_key() : '_newsai_discover_score';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT p.ID,
					pm_pin.meta_value AS pinned,
					pm_focus.meta_value AS focus_live,
					pm_disc.meta_value AS discover
				FROM {$wpdb->posts} p
				LEFT JOIN {$wpdb->postmeta} pm_pin ON pm_pin.post_id = p.ID AND pm_pin.meta_key = %s
				LEFT JOIN {$wpdb->postmeta} pm_focus ON pm_focus.post_id = p.ID AND pm_focus.meta_key = %s
				LEFT JOIN {$wpdb->postmeta} pm_disc ON pm_disc.post_id = p.ID AND pm_disc.meta_key = %s
				WHERE p.post_type = 'post' AND p.post_status = 'publish' AND p.post_date_gmt > %s",
				$pinned_key,
				$focus_key,
				$disc_key,
				$since
			)
		);

		$focus_sum = 0;
		$focus_n   = 0;
		$disc_sum  = 0;
		$disc_n    = 0;
		$ready_sum = 0;
		$ready_n   = 0;
		$low_count = 0;

		foreach ( (array) $rows as $r ) {
			$pinned = ( $r->pinned !== null && $r->pinned !== '' ) ? (int) $r->pinned : 0;
			$live   = ( $r->focus_live !== null && $r->focus_live !== '' ) ? (int) $r->focus_live : 0;
			$focus  = max( $pinned, $live );
			$disc   = ( $r->discover !== null && $r->discover !== '' ) ? (int) $r->discover : 0;

			if ( $focus > 0 ) {
				$focus_sum += $focus;
				$focus_n++;
			}
			if ( $disc > 0 ) {
				$disc_sum += $disc;
				$disc_n++;
			}
			if ( $focus > 0 && $disc > 0 ) {
				$composite = (int) round( ( $focus + $disc ) / 2 );
				$ready_sum += $composite;
				$ready_n++;
				if ( $composite < 60 ) {
					$low_count++;
				}
			} elseif ( $focus > 0 ) {
				$ready_sum += $focus;
				$ready_n++;
				if ( $focus < 60 ) {
					$low_count++;
				}
			} elseif ( $disc > 0 ) {
				$ready_sum += $disc;
				$ready_n++;
				if ( $disc < 60 ) {
					$low_count++;
				}
			}
		}

		$out = [
			'avg'           => $ready_n > 0 ? (int) round( $ready_sum / $ready_n ) : 0,
			'focus_avg'     => $focus_n > 0 ? (int) round( $focus_sum / $focus_n ) : 0,
			'discover_avg'  => $disc_n > 0 ? (int) round( $disc_sum / $disc_n ) : 0,
			'sample'        => $ready_n,
			'posts_recent'  => $posts_recent,
			'low_count'     => $low_count,
		];
		set_transient( 'newsai_dash_readiness_v2_' . $days, $out, 15 * MINUTE_IN_SECONDS );
		return $out;
	}
}

if ( ! function_exists( 'newsai_get_dashboard_readiness_sparkline_7d' ) ) {
	/**
	 * Daily site-wide composite AI Readiness (Focus + Discover) for the last 7 days.
	 *
	 * @return int[] Seven values, oldest day first.
	 */
	function newsai_get_dashboard_readiness_sparkline_7d() {
		$cached = get_transient( 'newsai_dash_readiness_spark_7d' );
		if ( is_array( $cached ) && count( $cached ) === 7 ) {
			return $cached;
		}

		global $wpdb;
		$focus_key  = function_exists( 'newsai_focus_score_meta_key' ) ? newsai_focus_score_meta_key() : '_newsai_focus_score';
		$pinned_key = function_exists( 'newsai_focus_score_history_meta_key' ) ? newsai_focus_score_history_meta_key() : '_newsai_focus_score_at_publish';
		$disc_key   = function_exists( 'newsai_discover_score_meta_key' ) ? newsai_discover_score_meta_key() : '_newsai_discover_score';

		$series = [];
		for ( $i = 6; $i >= 0; $i-- ) {
			$start = gmdate( 'Y-m-d 00:00:00', time() - ( $i * DAY_IN_SECONDS ) );
			$end   = gmdate( 'Y-m-d 00:00:00', time() - ( ( $i - 1 ) * DAY_IN_SECONDS ) );

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT pm_pin.meta_value AS pinned,
						pm_focus.meta_value AS focus_live,
						pm_disc.meta_value AS discover
					FROM {$wpdb->posts} p
					LEFT JOIN {$wpdb->postmeta} pm_pin ON pm_pin.post_id = p.ID AND pm_pin.meta_key = %s
					LEFT JOIN {$wpdb->postmeta} pm_focus ON pm_focus.post_id = p.ID AND pm_focus.meta_key = %s
					LEFT JOIN {$wpdb->postmeta} pm_disc ON pm_disc.post_id = p.ID AND pm_disc.meta_key = %s
					WHERE p.post_type = 'post' AND p.post_status = 'publish'
						AND p.post_date_gmt >= %s AND p.post_date_gmt < %s",
					$pinned_key,
					$focus_key,
					$disc_key,
					$start,
					$end
				)
			);

			$ready_sum = 0;
			$ready_n   = 0;
			foreach ( (array) $rows as $row ) {
				$focus = max( (int) ( $row->pinned ?? 0 ), (int) ( $row->focus_live ?? 0 ) );
				$disc  = (int) ( $row->discover ?? 0 );
				if ( $focus > 0 && $disc > 0 ) {
					$ready_sum += (int) round( ( $focus + $disc ) / 2 );
					$ready_n++;
				} elseif ( $focus > 0 ) {
					$ready_sum += $focus;
					$ready_n++;
				} elseif ( $disc > 0 ) {
					$ready_sum += $disc;
					$ready_n++;
				}
			}
			$series[] = $ready_n > 0 ? (int) round( $ready_sum / $ready_n ) : 0;
		}

		set_transient( 'newsai_dash_readiness_spark_7d', $series, 15 * MINUTE_IN_SECONDS );
		return $series;
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_competitor_items' ) ) {
	function newsai_dashboard_registry_competitor_items( $limit = 8 ) {
		$comp_rss = (string) get_option( 'newsai_competitor_rss', '' );
		$urls     = array_filter( array_map( 'trim', explode( "\n", $comp_rss ) ) );
		if ( empty( $urls ) ) {
			return [ 'feeds' => '', 'items' => [] ];
		}
		$filter = static function () {
			return 120;
		};
		add_filter( 'wp_feed_cache_transient_lifetime', $filter );
		$timeout = static function () {
			return 4;
		};
		add_filter( 'http_request_timeout', $timeout, 999 );
		$feed = fetch_feed( $urls );
		remove_filter( 'http_request_timeout', $timeout, 999 );
		remove_filter( 'wp_feed_cache_transient_lifetime', $filter );
		if ( is_wp_error( $feed ) ) {
			return [ 'feeds' => $comp_rss, 'items' => [], 'error' => $feed->get_error_message() ];
		}
		$items = [];
		foreach ( (array) $feed->get_items( 0, $limit ) as $item ) {
			if ( ! $item ) {
				continue;
			}
			$source = '';
			$feed_obj = $item->get_feed();
			if ( $feed_obj && method_exists( $feed_obj, 'get_title' ) ) {
				$source = (string) $feed_obj->get_title();
			}
			$items[] = [
				'title'   => wp_strip_all_tags( (string) $item->get_title() ),
				'url'     => esc_url_raw( (string) $item->get_permalink() ),
				'source'  => $source,
				'ago'     => human_time_diff( $item->get_date( 'U' ), current_time( 'timestamp' ) ),
				'excerpt' => wp_trim_words( wp_strip_all_tags( (string) $item->get_description() ), 18, '…' ),
			];
		}
		return [ 'feeds' => $comp_rss, 'items' => $items ];
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_geo_choices' ) ) {
	function newsai_dashboard_registry_geo_choices() {
		$geo_opt = function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US';
		$codes   = [ 'GR', 'CY', 'US', 'GB', 'CA', 'AU', 'DE', 'FR', 'IT', 'ES', 'NL', 'BE', 'CH', 'SE', 'PT', 'BR', 'MX', 'RU', 'JP', 'IN', 'ID', 'ZA' ];
		$out     = [];
		foreach ( $codes as $code ) {
			$out[ $code ] = $code;
		}
		$out['_default'] = $geo_opt;
		return $out;
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_radar_trends' ) ) {
	function newsai_dashboard_registry_radar_trends( $geo = '' ) {
		if ( $geo === '' ) {
			$geo = function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US';
		}
		if ( ! function_exists( 'newsai_google_trends_get_items' ) ) {
			return [ 'items' => [], 'error' => __( 'Trends module unavailable.', 'newsroom-ai-assistant' ) ];
		}
		$result = newsai_google_trends_get_items( $geo, 8 );
		if ( is_wp_error( $result ) ) {
			return [ 'items' => [], 'error' => $result->get_error_message(), 'stale' => false ];
		}
		$items = [];
		foreach ( (array) ( $result['items'] ?? [] ) as $row ) {
			$items[] = [
				'title'   => (string) ( $row['title'] ?? '' ),
				'url'     => esc_url_raw( (string) ( $row['permalink'] ?? '' ) ),
				'traffic' => (string) ( $row['traffic'] ?? '' ),
				'pct'     => (int) ( $row['numeric_traffic'] ?? 0 ),
			];
		}
		return [
			'items'   => $items,
			'stale'   => ! empty( $result['stale'] ),
			'circuit' => ! empty( $result['circuit'] ),
		];
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_task_date_parts' ) ) {
	/**
	 * @param object $row Task row.
	 * @return array{date:string,recurrence:string}
	 */
	function newsai_dashboard_registry_task_date_parts( $row ) {
		$y = (int) ( $row->year ?? 0 );
		$m = (int) ( $row->month ?? 0 );
		$d = (int) ( $row->day ?? 1 );
		$rec = 'yearly';
		if ( $y === 0 && $m === 0 ) {
			$rec = 'monthly';
			$y = (int) gmdate( 'Y' );
			$m = (int) gmdate( 'm' );
		} elseif ( $y === 0 && $m > 0 ) {
			$rec = 'yearly';
			$y = (int) gmdate( 'Y' );
		} else {
			$rec = 'none';
		}
		return [
			'date'       => sprintf( '%04d-%02d-%02d', $y, $m, $d ),
			'recurrence' => $rec,
		];
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_authors' ) ) {
	function newsai_dashboard_registry_authors() {
		$out = [
			[ 'id' => 0, 'name' => __( 'Anyone', 'newsroom-ai-assistant' ) ],
		];
		$users = get_users(
			[
				'capability' => 'edit_posts',
				'orderby'    => 'display_name',
				'order'      => 'ASC',
				'number'     => 100,
			]
		);
		foreach ( (array) $users as $user ) {
			$out[] = [
				'id'   => (int) $user->ID,
				'name' => (string) $user->display_name,
			];
		}
		return $out;
	}
}

if ( ! function_exists( 'newsai_dashboard_save_task' ) ) {
	/**
	 * Save or update editorial task (mirrors classic POST handler).
	 *
	 * @param array $input Form fields.
	 * @return array{success:bool,message:string,task_id?:int}
	 */
	function newsai_dashboard_save_task( $input ) {
		global $wpdb;
		if ( ! current_user_can( 'edit_posts' ) ) {
			return [ 'success' => false, 'message' => __( 'Forbidden.', 'newsroom-ai-assistant' ) ];
		}
		$table = $wpdb->prefix . 'newsai_alerts';
		$table_cols = get_transient( 'newsai_table_cols_cache' );
		if ( false === $table_cols ) {
			$table_cols = $wpdb->get_col( "DESC {$table}", 0 );
			set_transient( 'newsai_table_cols_cache', $table_cols, DAY_IN_SECONDS );
		}
		$date = isset( $input['task_date'] ) ? sanitize_text_field( (string) $input['task_date'] ) : '';
		if ( $date === '' ) {
			return [ 'success' => false, 'message' => __( 'Due date is required.', 'newsroom-ai-assistant' ) ];
		}
		$parts = explode( '-', $date );
		if ( count( $parts ) < 3 ) {
			return [ 'success' => false, 'message' => __( 'Invalid date.', 'newsroom-ai-assistant' ) ];
		}
		$recurrence = isset( $input['recurrence'] ) ? sanitize_text_field( (string) $input['recurrence'] ) : 'yearly';
		if ( $recurrence === 'monthly' ) {
			$year = 0;
			$month = 0;
		} elseif ( $recurrence === 'yearly' ) {
			$year = 0;
			$month = (int) $parts[1];
		} else {
			$year  = (int) $parts[0];
			$month = (int) $parts[1];
		}
		$day      = (int) $parts[2];
		$alert_id = isset( $input['task_id'] ) ? (int) $input['task_id'] : 0;
		$data     = [
			'title'          => sanitize_text_field( (string) ( $input['title'] ?? '' ) ),
			'day'            => $day,
			'month'          => $month,
			'year'           => $year,
			'lead_days'      => isset( $input['lead_days'] ) ? max( 1, min( 90, (int) $input['lead_days'] ) ) : 7,
			'author_id'      => isset( $input['author_id'] ) ? (int) $input['author_id'] : 0,
			'notes'          => isset( $input['notes'] ) ? sanitize_textarea_field( (string) $input['notes'] ) : '',
			'target_keyword' => isset( $input['keyword'] ) ? sanitize_text_field( (string) $input['keyword'] ) : '',
			'ref_link'       => isset( $input['ref_link'] ) ? sanitize_url( (string) $input['ref_link'] ) : '',
			'status'         => 'pending',
		];
		if ( $data['title'] === '' ) {
			return [ 'success' => false, 'message' => __( 'Title is required.', 'newsroom-ai-assistant' ) ];
		}
		if ( ! in_array( 'target_keyword', $table_cols, true ) ) {
			unset( $data['target_keyword'] );
		}
		if ( ! in_array( 'ref_link', $table_cols, true ) ) {
			unset( $data['ref_link'] );
		}
		if ( ! in_array( 'status', $table_cols, true ) ) {
			unset( $data['status'] );
		}
		$prev_author = 0;
		if ( $alert_id > 0 ) {
			if ( function_exists( 'newsai_task_can_access' ) && ! newsai_task_can_access( $alert_id ) ) {
				return [ 'success' => false, 'message' => __( 'Forbidden.', 'newsroom-ai-assistant' ) ];
			}
			$prev_author = (int) $wpdb->get_var( $wpdb->prepare( "SELECT author_id FROM {$table} WHERE id = %d", $alert_id ) );
			$wpdb->update( $table, $data, [ 'id' => $alert_id ] );
			do_action( 'newsai_task_assigned', $alert_id, false, $prev_author );
			$new_id = $alert_id;
		} else {
			$wpdb->insert( $table, $data );
			$new_id = (int) $wpdb->insert_id;
			if ( $new_id > 0 ) {
				do_action( 'newsai_task_assigned', $new_id, true, 0 );
			}
		}
		if ( function_exists( 'nros_clear_task_transients' ) ) {
			nros_clear_task_transients();
		}
		return [
			'success' => $new_id > 0,
			'message' => __( 'Task saved.', 'newsroom-ai-assistant' ),
			'task_id' => $new_id,
		];
	}
}

if ( ! function_exists( 'newsai_dashboard_delete_task' ) ) {
	function newsai_dashboard_delete_task( $task_id ) {
		global $wpdb;
		$task_id = (int) $task_id;
		if ( $task_id < 1 || ! current_user_can( 'manage_options' ) ) {
			return [ 'success' => false, 'message' => __( 'Forbidden.', 'newsroom-ai-assistant' ) ];
		}
		$table = $wpdb->prefix . 'newsai_alerts';
		$wpdb->delete( $table, [ 'id' => $task_id ] );
		if ( function_exists( 'nros_clear_task_transients' ) ) {
			nros_clear_task_transients();
		}
		return [ 'success' => true, 'message' => __( 'Task deleted.', 'newsroom-ai-assistant' ) ];
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_tasks' ) ) {
	function newsai_dashboard_registry_tasks() {
		global $wpdb;
		$table      = $wpdb->prefix . 'newsai_alerts';
		$table_cols = get_transient( 'newsai_table_cols_cache' );
		if ( false === $table_cols ) {
			$table_cols = $wpdb->get_col( "DESC {$table}", 0 );
			set_transient( 'newsai_table_cols_cache', $table_cols, DAY_IN_SECONDS );
		}
		$has_status = in_array( 'status', $table_cols, true );
		$order_sql  = $has_status ? 'ORDER BY status DESC, month, day' : 'ORDER BY month, day';
		$see_all    = current_user_can( 'edit_others_posts' );
		if ( $see_all ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} {$order_sql} LIMIT %d", 100 ) );
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$rows = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM {$table} WHERE ( author_id = %d OR author_id = 0 ) {$order_sql} LIMIT %d",
					(int) get_current_user_id(),
					100
				)
			);
		}
		if ( ! is_array( $rows ) ) {
			$rows = [];
		}

		$now_time     = (int) strtotime( gmdate( 'Y-m-d' ) );
		$can_manage   = current_user_can( 'manage_options' );
		$can_edit     = current_user_can( 'edit_posts' );
		$has_status_c = $has_status;
		$out          = [];

		foreach ( $rows as $r ) {
			$user     = get_userdata( (int) $r->author_id );
			$assignee = $user ? $user->display_name : __( 'Anyone', 'newsroom-ai-assistant' );
			$task_time = function_exists( 'newsai_task_resolve_due_ts' )
				? newsai_task_resolve_due_ts( $r, $now_time )
				: $now_time;
			$diff_days = ( $task_time - $now_time ) / 86400;
			$status    = newsai_dashboard_registry_task_status( $r, $has_status_c, $now_time );
			$status_val = $has_status_c ? (string) ( $r->status ?? 'pending' ) : 'pending';
			$id        = (int) $r->id;
			$edit_url  = add_query_arg(
				[
					'page' => 'newsai-settings',
					'tab'  => 'alerts',
					'edit' => $id,
					'ui'   => 'classic',
				],
				admin_url( 'admin.php' )
			);
			$done_url = wp_nonce_url(
				add_query_arg(
					[
						'page'      => 'newsai-settings',
						'tab'       => 'alerts',
						'mark_done' => 1,
						'id'        => $id,
						'ui'        => 'classic',
					],
					admin_url( 'admin.php' )
				),
				'newsai_mark_done_' . $id
			);
			$delete_url = wp_nonce_url(
				add_query_arg(
					[
						'page' => 'newsai-settings',
						'tab'  => 'alerts',
						'del'  => $id,
						'ui'   => 'classic',
					],
					admin_url( 'admin.php' )
				),
				'newsai_delete_task_' . $id
			);

			$date_parts = newsai_dashboard_registry_task_date_parts( $r );

			$out[] = [
				'id'          => $id,
				'title'       => (string) $r->title,
				'keyword'     => isset( $r->target_keyword ) ? (string) $r->target_keyword : '',
				'notes'       => isset( $r->notes ) ? (string) $r->notes : '',
				'refLink'     => isset( $r->ref_link ) ? (string) $r->ref_link : '',
				'taskDate'    => $date_parts['date'],
				'recurrence'  => $date_parts['recurrence'],
				'leadDays'    => (int) ( $r->lead_days ?? 7 ),
				'authorId'    => (int) ( $r->author_id ?? 0 ),
				'dateLabel'   => sprintf( '%02d/%02d', (int) $r->day, (int) $r->month ),
				'assignee'    => (string) $assignee,
				'initials'    => function_exists( 'newsai_dashboard_registry_initials' )
					? newsai_dashboard_registry_initials( (string) $assignee )
					: strtoupper( substr( (string) $assignee, 0, 2 ) ),
				'kanbanColumn'=> $status['key'],
				'status'      => $status_val,
				'statusKey'   => $status['key'],
				'statusLabel' => $status['label'],
				'overdue'     => $diff_days < 0,
				'dueToday'    => (int) $diff_days === 0,
				'unassigned'  => (int) $r->author_id === 0,
				'actions'     => [
					'edit'   => $edit_url,
					'done'   => ( $status_val !== 'done' && $has_status_c && $can_edit ) ? $done_url : '',
					'delete' => $can_manage ? $delete_url : '',
				],
			];
		}

		return $out;
	}
}

if ( ! function_exists( 'newsai_dashboard_registry_payload' ) ) {
	function newsai_dashboard_registry_payload() {
		global $wpdb;
		$table = $wpdb->prefix . 'newsai_alerts';
		$table_cols = get_transient( 'newsai_table_cols_cache' );
		if ( false === $table_cols ) {
			$table_cols = $wpdb->get_col( "DESC {$table}", 0 );
			set_transient( 'newsai_table_cols_cache', $table_cols, DAY_IN_SECONDS );
		}
		$has_status = in_array( 'status', $table_cols, true );

		$total_tasks = $has_status
			? (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE status != %s", 'done' ) )
			: (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );

		$due_metrics = [ 'overdue' => 0, 'due_today' => 0 ];
		if ( function_exists( 'newsai_get_dashboard_due_metrics_cached' ) ) {
			try {
				$due_metrics = newsai_get_dashboard_due_metrics_cached( $table );
			} catch ( \Throwable $e ) {
				$due_metrics = [ 'overdue' => 0, 'due_today' => 0 ];
			}
		}
		$post_kpis = [ 'posts_this_week' => 0, 'posts_last_week' => 0, 'total_posts_30d' => 0 ];
		if ( function_exists( 'newsai_get_dashboard_post_publish_kpis' ) ) {
			try {
				$post_kpis = newsai_get_dashboard_post_publish_kpis();
			} catch ( \Throwable $e ) {
				$post_kpis = [ 'posts_this_week' => 0, 'posts_last_week' => 0, 'total_posts_30d' => 0 ];
			}
		}

		$posts_this_week = (int) $post_kpis['posts_this_week'];
		$posts_last_week = (int) $post_kpis['posts_last_week'];
		$week_diff       = $posts_this_week - $posts_last_week;
		$week_trend      = ( $posts_last_week > 0 ) ? round( ( $week_diff / $posts_last_week ) * 100 ) : ( $posts_this_week > 0 ? 100 : 0 );

		$missing_seo = 0;
		if ( function_exists( 'newsroom_os_get_missing_seo_count' ) ) {
			try {
				$missing_seo = (int) newsroom_os_get_missing_seo_count();
			} catch ( \Throwable $e ) {
				$missing_seo = 0;
			}
		}
		$total_posts_30d = (int) $post_kpis['total_posts_30d'];
		$readiness_kpis  = function_exists( 'newsai_get_dashboard_desk_readiness_kpis' )
			? newsai_get_dashboard_desk_readiness_kpis( 30 )
			: [ 'avg' => 0, 'sample' => 0, 'low_count' => 0, 'focus_avg' => 0, 'discover_avg' => 0 ];

		$google = [ 'state' => 'clear', 'message' => '', 'link' => '' ];
		if ( function_exists( 'newsroom_os_check_google_updates' ) ) {
			try {
				$google = newsroom_os_check_google_updates();
			} catch ( \Throwable $e ) {
				$google = [ 'state' => 'clear', 'message' => '', 'link' => '' ];
			}
		}

		$latest = [];
		$posts  = get_posts(
			[
				'post_type'      => 'post',
				'post_status'    => 'publish',
				'posts_per_page' => 5,
			]
		);
		foreach ( $posts as $lp ) {
			$edit_l = get_edit_post_link( $lp->ID, 'raw' );
			$latest[] = [
				'id'        => (int) $lp->ID,
				'title'     => (string) $lp->post_title,
				'author'    => (string) get_the_author_meta( 'display_name', $lp->post_author ),
				'ago'       => human_time_diff( get_post_time( 'U', false, $lp->ID ), current_time( 'timestamp' ) ),
				'editUrl'   => $edit_l ? $edit_l : '',
				'viewUrl'   => get_permalink( $lp->ID ) ?: '',
			];
		}

		$writers = [];
		if ( function_exists( 'newsai_is_pro' ) && newsai_is_pro() && function_exists( 'newsai_pro_get_writer_leaderboard' ) ) {
			try {
				foreach ( (array) newsai_pro_get_writer_leaderboard( 5 ) as $row ) {
					$writers[] = [
						'name'     => (string) ( $row['name'] ?? '' ),
						'initials' => (string) ( $row['initials'] ?? '' ),
						'posts30d' => (int) ( $row['posts_30d'] ?? 0 ),
						'trend'    => (string) ( $row['trend'] ?? 'flat' ),
					];
				}
			} catch ( \Throwable $e ) {
				$writers = [];
			}
		}

		$competitor = [ 'feeds' => '', 'items' => [] ];
		if ( function_exists( 'newsai_dashboard_registry_competitor_items' ) ) {
			try {
				$competitor = newsai_dashboard_registry_competitor_items( 8 );
			} catch ( \Throwable $e ) {
				$competitor = [ 'feeds' => '', 'items' => [], 'error' => $e->getMessage() ];
			}
		}
		$post_spark = [];
		if ( function_exists( 'newsai_dashboard_registry_sparkline_posts' ) ) {
			try {
				$post_spark = newsai_dashboard_registry_sparkline_posts( 7 );
			} catch ( \Throwable $e ) {
				$post_spark = [];
			}
		}

		return [
			'version'      => defined( 'NEWSAI_VERSION' ) ? NEWSAI_VERSION : '1',
			'kpis'         => [
				'activeTasks'    => $total_tasks,
				'overdue'        => (int) $due_metrics['overdue'],
				'dueToday'       => (int) $due_metrics['due_today'],
				'postsThisWeek'  => $posts_this_week,
				'weekTrend'      => (int) $week_trend,
				'weekTrendUp'    => $week_trend >= 0,
				'missingSeo'     => (int) $missing_seo,
				'deskReadiness'  => (int) ( $readiness_kpis['avg'] ?? 0 ),
				'readinessSample'=> (int) ( $readiness_kpis['sample'] ?? 0 ),
				'readinessLow'   => (int) ( $readiness_kpis['low_count'] ?? 0 ),
				'seoHealth'      => (int) ( $readiness_kpis['avg'] ?? 0 ),
				'seoIssuesUrl'   => admin_url( 'edit.php?newsai_filter=missing_seo' ),
			],
			'sparklines'   => [
				'articles'   => $post_spark,
				'activeTasks'=> array_fill( 0, max( 1, count( $post_spark ) ), $total_tasks ),
				'seoIssues'  => array_fill( 0, max( 1, count( $post_spark ) ), (int) $missing_seo ),
				'readiness'  => array_fill( 0, max( 1, count( $post_spark ) ), (int) ( $readiness_kpis['avg'] ?? 0 ) ),
			],
			'radar'        => [
				'geo'     => function_exists( 'newsai_get_target_geo' ) ? newsai_get_target_geo() : 'US',
				'choices' => function_exists( 'newsai_dashboard_registry_geo_choices' ) ? newsai_dashboard_registry_geo_choices() : [],
			],
			'competitor'   => $competitor,
			'google'       => [
				'state'   => (string) ( $google['state'] ?? 'clear' ),
				'message' => (string) ( $google['message'] ?? '' ),
				'link'    => (string) ( $google['link'] ?? '' ),
			],
			'tasks'        => newsai_dashboard_registry_tasks(),
			'latestPosts'  => $latest,
			'writers'      => $writers,
			'capabilities' => [
				'manageAll'   => current_user_can( 'manage_options' ),
				'seeAllTasks' => current_user_can( 'edit_others_posts' ),
				'editTasks'   => current_user_can( 'edit_posts' ),
			],
			'urls'         => [
				'classic'   => admin_url( 'admin.php?page=newsai-settings&tab=alerts&ui=classic' ),
				'dashboard' => admin_url( 'admin.php?page=newsai-settings&tab=alerts' ),
				'addTask'   => admin_url( 'admin.php?page=newsai-settings&tab=alerts' ),
				'posts'     => admin_url( 'edit.php' ),
				'settings'  => admin_url( 'admin.php?page=newsai-settings&tab=settings' ),
			],
			'authors'      => newsai_dashboard_registry_authors(),
			'taskNonce'    => wp_create_nonce( 'newsai_add_alert_action' ),
			'i18n'         => newsai_dashboard_registry_i18n(),
		];
	}
}
