<?php
/**
 * NeccoSpeak agent-ui — Dashboard tab.
 *
 * Structural equivalent of ai-agent-embed v1.5.1.1's
 * ai_agent_embed_render_tab_overview() (includes/admin-agent-settings.php):
 * a 2-column card grid — connection status / agent info / version
 * management (wide) / actions. NeccoSpeak has no separate backend "agent"
 * entity to query, so each card is re-pointed at the closest real
 * NeccoSpeak equivalent per the frozen contract:
 *   - connection status  -> configured API key + widget-enabled + a REST
 *                           /ping reachability check (JS, assets/agent-ui/js/agent-ui.js),
 *                           aggregated into production's single "● 接続OK" /
 *                           "● 接続に問題があります" line (with only the failing
 *                           checks listed below it) instead of 3 always-on
 *                           rows. The REST check is async and agent-ui.js is
 *                           frozen, so the aggregate is recomputed client-side
 *                           by an inline <script> in this file that watches
 *                           #neccospeak-agent-ui-ping-status (the same DOM id
 *                           agent-ui.js already writes into) via
 *                           MutationObserver.
 *   - agent info          -> widget_title / enabled / get_locale(); slug and
 *                           description have no NeccoSpeak equivalent and
 *                           are not rendered at all, same discipline as
 *                           every other tab file.
 *   - version management  -> includes/admin-versions.php's existing
 *                           draft/publish workflow (neccospeak_render_publish_bar())
 *                           plus a "公開版 [vN] model / ドラフト [vN+1] model /
 *                           更新日時 …" summary line (production's badge markup,
 *                           transcribed) above an inline draft-vs-published
 *                           diff table
 *                           (neccospeak_versions_diff_keys()/neccospeak_versions_mask_value()),
 *                           mirroring production's "公開版 vs ドラフト 変更点" table
 *                           (table always renders while the workflow is on;
 *                           a colspan row reading "公開版とドラフトに差分はありません"
 *                           stands in for an empty diff, matching production's
 *                           own JS-rendered empty-state row). NeccoSpeak has no
 *                           per-agent version-number field like production's
 *                           published_version_no/current_draft_version_no, so
 *                           neccospeak_agent_ui_dashboard_version_summary()
 *                           (this file) derives one from the free plan's own
 *                           version history -- see that function's docblock.
 *   - actions              -> real publish button plus an inline rollback
 *                           selector, all in the same card (production's
 *                           admin-agent-settings.php 1225-1253 layout) --
 *                           built on includes/admin-versions.php's existing
 *                           admin_post_neccospeak_publish/_rollback handlers
 *                           and includes/admin-diagnostics.php's
 *                           neccospeak_diagnostics_rows(). "キャッシュクリア"
 *                           has no NeccoSpeak equivalent and is not rendered
 *                           at all; "整合性チェック" stays this file's own
 *                           inline check+link (the new agent-ui tab router
 *                           only recognizes neccospeak_agent_ui_tabs()'s 9
 *                           slugs and has no 'diagnostics' entry, so a link
 *                           built against that slug would silently bounce
 *                           back to this same Dashboard tab).
 *
 * @package NeccoSpeak
 */

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

/**
 * @param array $opts Editing-surface options (see neccospeak_agent_ui_render_page()).
 * @return void
 */
function neccospeak_agent_ui_tab_dashboard( $opts ) {
	$published  = function_exists( 'neccospeak_get_options' ) ? neccospeak_get_options() : $opts;
	$draft      = function_exists( 'neccospeak_get_draft_options' ) ? neccospeak_get_draft_options() : $published;
	$has_key    = ! empty( $published['openai_api_key'] );
	$key_ok     = false;
	if ( $has_key && function_exists( 'neccospeak_decrypt' ) ) {
		$key_ok = '' !== neccospeak_decrypt( $published['openai_api_key'] );
	}
	$enabled         = ! empty( $published['enabled'] );
	$key_ok_flag     = $has_key && $key_ok;
	$widget_ok_flag  = $enabled;
	// True the moment either synchronously-known check fails; the 3rd check
	// (REST reachability) is async, so this can still flip to "problem" later
	// on the client -- see the inline <script> below.
	$connection_known_problem = ( ! $key_ok_flag || ! $widget_ok_flag );
	$workflow_active = ! empty( $published['draft_workflow_enabled'] );
	$diff_keys       = ( $workflow_active && function_exists( 'neccospeak_versions_diff_keys' ) )
		? neccospeak_versions_diff_keys( $published, $draft )
		: array();
	?>
	<div class="ai-agent-section" id="ai-agent-overview">
		<div class="ai-agent-overview-grid">

			<!-- 接続状態カード -->
			<div class="ai-agent-card">
				<h3><?php esc_html_e( 'Connection status', 'neccospeak' ); ?></h3>

				<div id="neccospeak-agent-ui-connection-summary" class="ai-agent-status-indicator">
					<span class="ai-agent-status-dot ai-agent-status-dot--<?php echo $connection_known_problem ? 'error' : 'warning'; ?>"></span>
					<span id="neccospeak-agent-ui-connection-summary-text">
						<?php echo $connection_known_problem ? esc_html__( 'Connection problem', 'neccospeak' ) : esc_html__( 'Checking…', 'neccospeak' ); ?>
					</span>
				</div>

				<?php /* 2026-08 ユーザー決定: 本番の接続状況カードは単一の集約行のみで、
				個別チェック内訳(API key/ウィジェット/REST)や誘導リンクは本番に無い可視要素の
				ため非表示化する。DOMからは削除しない — #neccospeak-agent-ui-ping-status は
				agent-ui.js(frozen)の書き込み先、#neccospeak-agent-ui-rest-row-label/value・
				このdl自体もインラインscriptのMutationObserverが参照しており、要素が
				DOM上に存在し続けることで集約行(summaryDot/summaryText)の再計算が
				機能し続ける。 */ ?>
				<div style="display:none" aria-hidden="true" data-neccospeak-extras-hidden="1">
				<dl class="ai-agent-data-grid" id="neccospeak-agent-ui-connection-details" style="margin-top:10px;<?php echo $connection_known_problem ? '' : 'display:none;'; ?>">
					<?php if ( ! $key_ok_flag ) : ?>
						<dt><?php esc_html_e( 'OpenAI API key', 'neccospeak' ); ?></dt>
						<dd>
							<?php if ( $has_key ) : ?>
								<span class="ai-agent-badge ai-agent-badge--inactive"><?php esc_html_e( 'Saved but cannot be decrypted', 'neccospeak' ); ?></span>
							<?php else : ?>
								<span class="ai-agent-badge ai-agent-badge--inactive"><?php esc_html_e( 'Not set', 'neccospeak' ); ?></span>
							<?php endif; ?>
						</dd>
					<?php endif; ?>
					<?php if ( ! $widget_ok_flag ) : ?>
						<dt><?php esc_html_e( 'Widget', 'neccospeak' ); ?></dt>
						<dd><span class="ai-agent-badge ai-agent-badge--inactive"><?php esc_html_e( 'Disabled', 'neccospeak' ); ?></span></dd>
					<?php endif; ?>
					<?php // REST row: hidden until the async ping (agent-ui.js) resolves to an error; toggled by the inline <script> below. ?>
					<dt id="neccospeak-agent-ui-rest-row-label" style="display:none;"><?php esc_html_e( 'REST endpoint', 'neccospeak' ); ?></dt>
					<dd id="neccospeak-agent-ui-rest-row-value" style="display:none;">
						<span id="neccospeak-agent-ui-ping-status" class="ai-agent-status-indicator">
							<span class="ai-agent-status-dot ai-agent-status-dot--warning"></span>
							<?php esc_html_e( 'Checking…', 'neccospeak' ); ?>
						</span>
					</dd>
				</dl>

				<?php if ( ! $key_ok_flag ) : ?>
					<p class="description" style="margin-top:10px;">
						<?php
						$ai_url = add_query_arg(
							array(
								'page' => NECCOSPEAK_SETTINGS_SLUG,
								'tab'  => 'ai',
							),
							admin_url( 'admin.php' )
						);
						echo wp_kses_post(
							sprintf(
								/* translators: %s: link to the AI Settings tab. */
								__( 'Please set your OpenAI API key on the "<a href="%s">AI Settings</a>" tab.', 'neccospeak' ),
								esc_url( $ai_url )
							)
						);
						?>
					</p>
				<?php endif; ?>
				</div>

				<?php
				/**
				 * agent-ui.js (frozen) only ever writes into
				 * #neccospeak-agent-ui-ping-status (dot class + trailing text);
				 * it knows nothing about the aggregate line above. This inline
				 * script owns the aggregation: it renders the initial state
				 * from the 2 synchronous checks above (so there is no flash of
				 * wrong content), then watches the ping element's dot class via
				 * MutationObserver and recomputes whenever agent-ui.js settles
				 * the 3rd (REST) check.
				 */
				?>
				<script>
				( function () {
					var summaryDot  = document.querySelector( '#neccospeak-agent-ui-connection-summary .ai-agent-status-dot' );
					var summaryText = document.getElementById( 'neccospeak-agent-ui-connection-summary-text' );
					var detailsList = document.getElementById( 'neccospeak-agent-ui-connection-details' );
					var restLabel   = document.getElementById( 'neccospeak-agent-ui-rest-row-label' );
					var restValue   = document.getElementById( 'neccospeak-agent-ui-rest-row-value' );
					var pingStatus  = document.getElementById( 'neccospeak-agent-ui-ping-status' );

					if ( ! summaryDot || ! summaryText || ! detailsList || ! restLabel || ! restValue || ! pingStatus ) {
						return;
					}

					var knownProblem = <?php echo $connection_known_problem ? 'true' : 'false'; ?>;
					var LABEL_OK      = <?php echo wp_json_encode( __( 'Connection OK', 'neccospeak' ) ); ?>;
					var LABEL_PROBLEM = <?php echo wp_json_encode( __( 'Connection problem', 'neccospeak' ) ); ?>;
					var LABEL_PENDING = <?php echo wp_json_encode( __( 'Checking…', 'neccospeak' ) ); ?>;

					function restState() {
						var dot = pingStatus.querySelector( '.ai-agent-status-dot' );
						if ( dot && dot.classList.contains( 'ai-agent-status-dot--ok' ) ) {
							return 'ok';
						}
						if ( dot && dot.classList.contains( 'ai-agent-status-dot--error' ) ) {
							return 'error';
						}
						return 'pending';
					}

					function recompute() {
						var rest    = restState();
						var problem = knownProblem || 'error' === rest;

						restLabel.style.display   = ( 'error' === rest ) ? '' : 'none';
						restValue.style.display   = ( 'error' === rest ) ? '' : 'none';
						detailsList.style.display = problem ? '' : 'none';

						if ( problem ) {
							summaryDot.className = 'ai-agent-status-dot ai-agent-status-dot--error';
							summaryText.textContent = LABEL_PROBLEM;
						} else if ( 'ok' === rest ) {
							summaryDot.className = 'ai-agent-status-dot ai-agent-status-dot--ok';
							summaryText.textContent = LABEL_OK;
						} else {
							summaryDot.className = 'ai-agent-status-dot ai-agent-status-dot--warning';
							summaryText.textContent = LABEL_PENDING;
						}
					}

					recompute();

					if ( 'function' === typeof MutationObserver ) {
						new MutationObserver( recompute ).observe( pingStatus, { attributes: true, subtree: true, attributeFilter: [ 'class' ] } );
					}
				} )();
				</script>
			</div>

			<!-- エージェント情報カード -->
			<div class="ai-agent-card">
				<h3><?php esc_html_e( 'Agent information', 'neccospeak' ); ?></h3>
				<dl class="ai-agent-data-grid">
					<dt><?php esc_html_e( 'Name', 'neccospeak' ); ?></dt>
					<dd><?php echo esc_html( (string) ( $published['widget_title'] ?? '' ) ); ?></dd>
					<dt><?php esc_html_e( 'Status', 'neccospeak' ); ?></dt>
					<dd>
						<?php if ( $enabled ) : ?>
							<span class="ai-agent-badge ai-agent-badge--active"><?php esc_html_e( 'Enabled', 'neccospeak' ); ?></span>
						<?php else : ?>
							<span class="ai-agent-badge ai-agent-badge--inactive"><?php esc_html_e( 'Disabled', 'neccospeak' ); ?></span>
						<?php endif; ?>
					</dd>
					<dt><?php esc_html_e( 'Language', 'neccospeak' ); ?></dt>
					<dd><?php echo esc_html( get_locale() ); ?></dd>
				</dl>
			</div>

			<!-- バージョン管理カード（既存の下書き/公開機構を再利用） -->
			<div class="ai-agent-card ai-agent-card--wide">
				<h3><?php esc_html_e( 'Version management', 'neccospeak' ); ?></h3>
				<?php if ( ! $workflow_active ) : ?>
					<p class="description">
						<?php
						$general_url = add_query_arg(
							array(
								'page' => NECCOSPEAK_SETTINGS_SLUG,
								'tab'  => 'ai',
							),
							admin_url( 'admin.php' )
						);
						echo wp_kses_post(
							sprintf(
								/* translators: %s: link to the AI Settings tab. */
								__( 'The draft/publish workflow is turned off, so saved changes take effect on the live site immediately. If you want to review changes before publishing, turn it on in the "<a href="%s">AI Settings</a>" tab.', 'neccospeak' ),
								esc_url( $general_url )
							)
						);
						?>
					</p>
				<?php elseif ( function_exists( 'neccospeak_render_publish_bar' ) ) : ?>
					<?php
					neccospeak_render_publish_bar( $published, $draft );
					$neccospeak_dashboard_version_summary = neccospeak_agent_ui_dashboard_version_summary( $published, $draft );
					?>
					<dl class="ai-agent-data-grid" style="margin-bottom:14px;">
						<dt><?php esc_html_e( 'Published version', 'neccospeak' ); ?></dt>
						<dd>
							<span class="ai-agent-badge ai-agent-badge--published">v<?php echo esc_html( (string) $neccospeak_dashboard_version_summary['published_no'] ); ?></span>
							<code><?php echo esc_html( $neccospeak_dashboard_version_summary['model_published'] ); ?></code>
						</dd>
						<dt><?php esc_html_e( 'Draft', 'neccospeak' ); ?></dt>
						<dd>
							<span class="ai-agent-badge ai-agent-badge--draft">v<?php echo esc_html( (string) $neccospeak_dashboard_version_summary['draft_no'] ); ?></span>
							<code><?php echo esc_html( $neccospeak_dashboard_version_summary['model_draft'] ); ?></code>
						</dd>
						<dt><?php esc_html_e( 'Last updated', 'neccospeak' ); ?></dt>
						<dd><?php echo esc_html( $neccospeak_dashboard_version_summary['updated_at_display'] ); ?></dd>
					</dl>
					<h4><?php esc_html_e( 'Published vs draft changes', 'neccospeak' ); ?></h4>
					<div style="overflow-x:auto;">
						<table class="wp-list-table widefat fixed striped">
							<thead>
								<tr>
									<th style="width:30%;"><?php esc_html_e( 'Setting', 'neccospeak' ); ?></th>
									<th style="width:35%;"><?php esc_html_e( 'Published version', 'neccospeak' ); ?></th>
									<th style="width:35%;"><?php esc_html_e( 'Draft', 'neccospeak' ); ?></th>
								</tr>
							</thead>
							<tbody>
								<?php if ( empty( $diff_keys ) ) : ?>
									<tr>
										<td colspan="3" style="text-align:center;color:#666;"><?php esc_html_e( 'No differences between the published version and the draft', 'neccospeak' ); ?></td>
									</tr>
								<?php else : ?>
									<?php foreach ( $diff_keys as $neccospeak_dashboard_diff_key ) : ?>
										<tr>
											<td><?php echo esc_html( $neccospeak_dashboard_diff_key ); ?></td>
											<td><?php echo esc_html( neccospeak_agent_ui_dashboard_format_value( $neccospeak_dashboard_diff_key, $published ) ); ?></td>
											<td><?php echo esc_html( neccospeak_agent_ui_dashboard_format_value( $neccospeak_dashboard_diff_key, $draft ) ); ?></td>
										</tr>
									<?php endforeach; ?>
								<?php endif; ?>
							</tbody>
						</table>
					</div>
					<p class="description" style="margin-top:6px;"><?php esc_html_e( 'Only items with changes are shown.', 'neccospeak' ); ?></p>
				<?php else : ?>
					<p class="description"><?php esc_html_e( 'Version management is not available.', 'neccospeak' ); ?></p>
				<?php endif; ?>
			</div>

			<!-- アクションカード -->
			<div class="ai-agent-card">
				<h3><?php esc_html_e( 'Actions', 'neccospeak' ); ?></h3>

				<div class="ai-agent-actions">
					<?php if ( $workflow_active && ! empty( $diff_keys ) ) : ?>
						<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline-block;">
							<input type="hidden" name="action" value="neccospeak_publish" />
							<?php wp_nonce_field( 'neccospeak_versions_publish' ); ?>
							<button type="submit" class="button button-primary"><?php esc_html_e( 'Publish draft', 'neccospeak' ); ?></button>
						</form>
					<?php else : ?>
						<?php
						$neccospeak_dashboard_publish_disabled_title = $workflow_active
							? __( 'There are no changes to publish.', 'neccospeak' )
							: __( 'The draft/publish workflow is turned off.', 'neccospeak' );
						?>
						<button type="button" class="button button-primary" disabled="disabled" title="<?php echo esc_attr( $neccospeak_dashboard_publish_disabled_title ); ?>"><?php esc_html_e( 'Publish draft', 'neccospeak' ); ?></button>
					<?php endif; ?>

					<?php
					$neccospeak_dashboard_check_url = add_query_arg(
						array(
							'page'                        => NECCOSPEAK_SETTINGS_SLUG,
							'tab'                         => 'dashboard',
							'neccospeak_dashboard_check' => '1',
						),
						admin_url( 'admin.php' )
					) . '#ai-agent-overview';
					?>
					<a class="button" href="<?php echo esc_url( $neccospeak_dashboard_check_url ); ?>"><?php esc_html_e( 'Integrity check', 'neccospeak' ); ?></a>
				</div>

				<?php neccospeak_agent_ui_dashboard_render_integrity_result(); ?>

				<div style="margin-top:16px;">
					<h4 style="margin:0 0 8px;"><?php esc_html_e( 'Rollback', 'neccospeak' ); ?></h4>
					<?php neccospeak_agent_ui_dashboard_render_rollback_form(); ?>
				</div>

				<?php /* 2026-08 ユーザー決定: 「変更履歴 / ロールバック」「プレビュー」への
				誘導リンクは本番のアクションカードに無い可視要素のため非表示化する。
				DOMからは削除しない（他のNS独自ブロックと同じ規約）。 */ ?>
				<div style="display:none" aria-hidden="true" data-neccospeak-extras-hidden="1">
				<?php
				$history_url = add_query_arg(
					array(
						'page' => NECCOSPEAK_SETTINGS_SLUG,
						'tab'  => 'history',
					),
					admin_url( 'admin.php' )
				);
				$preview_url = add_query_arg(
					array(
						'page' => NECCOSPEAK_SETTINGS_SLUG,
						'tab'  => 'preview',
					),
					admin_url( 'admin.php' )
				);
				?>
				<p class="description" style="margin-top:12px;">
					<a href="<?php echo esc_url( $history_url ); ?>"><?php esc_html_e( 'Change history / rollback', 'neccospeak' ); ?></a>
					·
					<a href="<?php echo esc_url( $preview_url ); ?>"><?php esc_html_e( 'Preview', 'neccospeak' ); ?></a>
				</p>
				</div>
			</div>

		</div>
	</div>
	<?php
}

/**
 * Safe, masked display string for a single option value in the "バージョン管理"
 * card's draft-vs-published diff table. Wraps includes/admin-versions.php's
 * neccospeak_versions_mask_value() (function_exists()-guarded, same
 * discipline as the rest of this file) so a raw API key or an unbounded
 * custom_css blob is never echoed, then normalizes booleans/null/arrays into
 * short human-readable strings for the table cell.
 *
 * @param string $key
 * @param array  $opts
 * @return string
 */
function neccospeak_agent_ui_dashboard_format_value( $key, array $opts ) {
	$value = array_key_exists( $key, $opts ) ? $opts[ $key ] : null;
	if ( function_exists( 'neccospeak_versions_mask_value' ) ) {
		$value = neccospeak_versions_mask_value( $key, $value );
	}
	if ( is_bool( $value ) ) {
		return $value ? __( 'Enabled', 'neccospeak' ) : __( 'Disabled', 'neccospeak' );
	}
	if ( null === $value || '' === $value ) {
		return __( '(empty)', 'neccospeak' );
	}
	if ( is_array( $value ) ) {
		$json = function_exists( 'wp_json_encode' ) ? wp_json_encode( $value ) : json_encode( $value ); // phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- wp_json_encode() is tried first; this fallback mirrors neccospeak_versions_mask_value()'s own.
		return is_string( $json ) ? $json : '';
	}
	return (string) $value;
}

/**
 * "バージョン管理" card's summary line (mirrors production's "公開版 [v8]
 * gpt-5.4-mini / ドラフト [v9] gpt-5.4-mini / 更新日時 2026/08/03 17:45" line,
 * badge markup transcribed), rendered above the draft-vs-published diff
 * table. NeccoSpeak's free plan has no per-agent version-number field like
 * ai-agent-embed's published_version_no / current_draft_version_no, so both
 * numbers are derived here, straightforwardly, from the plan's own version
 * history (includes/admin-versions.php's read helpers +
 * includes/db.php's neccospeak_db_get_versions(), both function_exists()-guarded,
 * same discipline as the rest of this file -- admin-versions.php itself is
 * not touched, only its existing read functions are called):
 *   - 公開版 = the 1-indexed position, among every kept history row whose
 *     origin is NOT 'discard' (i.e. every row that actually wrote the live
 *     published option: a plain save while the draft workflow is off, a
 *     publish, or a rollback -- see neccospeak_versions_snapshot_listener()'s
 *     own NECCOSPEAK_OPTION guard in admin-versions.php; a 'discard' row only
 *     ever resets the draft, never the published option), of the most recent
 *     such row. Only the last NECCOSPEAK_VERSIONS_KEEP (30) rows are kept, so
 *     this is exactly "how many publish-affecting rows exist" for a site
 *     with <= 30 lifetime publishes, and a reasonable (never-decreasing)
 *     lower bound for an older site whose earliest rows have been pruned.
 *     Falls back to 1 -- a fresh install's single, unrecorded "version 1" --
 *     when there is no history yet (table not ready, or truly empty).
 *   - ドラフト has no NeccoSpeak equivalent to derive a real generation number
 *     from (draft-only saves, while the workflow is on, never touch the
 *     versions table -- only the published option is snapshotted), so per
 *     the frozen contract it is always shown as 公開版+1.
 *   - 更新日時 = the newest kept history row's created_at (same wp_date()
 *     formatting as the rollback selector below), or a "no history yet"
 *     placeholder when there is none.
 *
 * @param array $published neccospeak_get_options().
 * @param array $draft     neccospeak_get_draft_options().
 * @return array{published_no:int,draft_no:int,model_published:string,model_draft:string,updated_at_display:string}
 */
function neccospeak_agent_ui_dashboard_version_summary( array $published, array $draft ) {
	$published_no = 1;
	$updated_at   = '';

	if ( function_exists( 'neccospeak_db_ready' ) && neccospeak_db_ready() && function_exists( 'neccospeak_db_get_versions' ) ) {
		$keep   = defined( 'NECCOSPEAK_VERSIONS_KEEP' ) ? (int) NECCOSPEAK_VERSIONS_KEEP : 30;
		$result = neccospeak_db_get_versions(
			array(
				'page'     => 1,
				'per_page' => max( 1, $keep ),
			)
		);
		$rows = ( is_array( $result ) && isset( $result['rows'] ) && is_array( $result['rows'] ) ) ? array_values( $result['rows'] ) : array();

		// neccospeak_db_get_versions() orders newest-first (id DESC); counting
		// every non-'discard' row gives the latest one's 1-indexed position.
		$published_count = 0;
		foreach ( $rows as $neccospeak_dashboard_version_row ) {
			$neccospeak_dashboard_version_row = (array) $neccospeak_dashboard_version_row;
			if ( isset( $neccospeak_dashboard_version_row['origin'] ) && 'discard' !== (string) $neccospeak_dashboard_version_row['origin'] ) {
				++$published_count;
				if ( '' === $updated_at && isset( $neccospeak_dashboard_version_row['created_at'] ) ) {
					$updated_at = (string) $neccospeak_dashboard_version_row['created_at']; // first match = newest, since $rows is newest-first.
				}
			}
		}
		if ( $published_count > 0 ) {
			$published_no = $published_count;
		}
	}

	$updated_at_display = __( '(no history)', 'neccospeak' );
	if ( '' !== $updated_at && function_exists( 'wp_date' ) ) {
		$ts = strtotime( $updated_at . ' UTC' );
		if ( $ts ) {
			$format              = trim( get_option( 'date_format', 'Y-m-d' ) . ' ' . get_option( 'time_format', 'H:i' ) );
			$updated_at_display = wp_date( '' !== $format ? $format : 'Y-m-d H:i', $ts );
		}
	}

	return array(
		'published_no'       => $published_no,
		'draft_no'           => $published_no + 1,
		'model_published'    => ( isset( $published['model'] ) && '' !== $published['model'] ) ? (string) $published['model'] : 'gpt-4o-mini',
		'model_draft'        => ( isset( $draft['model'] ) && '' !== $draft['model'] ) ? (string) $draft['model'] : 'gpt-4o-mini',
		'updated_at_display' => $updated_at_display,
	);
}

/**
 * "整合性チェック" (Actions card): a read-only summary of
 * includes/admin-diagnostics.php's existing neccospeak_diagnostics_rows()
 * checks (the same rows the old Diagnostics screen renders), shown when the
 * link above sets its query flag. Never mutates anything, so -- unlike the
 * publish/rollback forms above -- no nonce is needed; this only decides what
 * to display.
 *
 * @return void
 */
function neccospeak_agent_ui_dashboard_render_integrity_result() {
	if ( ! isset( $_GET['neccospeak_dashboard_check'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only, triggers no state change; see docblock.
		return;
	}
	if ( ! function_exists( 'neccospeak_diagnostics_rows' ) ) {
		echo '<div class="ai-agent-action-result error" style="display:block;">' . esc_html__( 'Integrity check is not available.', 'neccospeak' ) . '</div>';
		return;
	}

	$rows     = neccospeak_diagnostics_rows();
	$errors   = 0;
	$warns    = 0;
	$problems = array();
	foreach ( $rows as $row ) {
		$state = isset( $row['state'] ) ? (string) $row['state'] : '';
		if ( 'error' === $state ) {
			++$errors;
			$problems[] = isset( $row['label'] ) ? (string) $row['label'] : '';
		} elseif ( 'warn' === $state ) {
			++$warns;
			$problems[] = isset( $row['label'] ) ? (string) $row['label'] : '';
		}
	}

	if ( 0 === $errors && 0 === $warns ) {
		echo '<div class="ai-agent-action-result success" style="display:block;">' . esc_html__( 'No problems were found.', 'neccospeak' ) . '</div>';
		return;
	}

	$problems  = array_values( array_filter( $problems ) );
	$shown     = array_slice( $problems, 0, 5 );
	$remaining = count( $problems ) - count( $shown );

	$message  = sprintf(
		/* translators: 1: number of errors found, 2: number of warnings found. */
		__( '%1$d errors and %2$d warnings were found:', 'neccospeak' ),
		$errors,
		$warns
	);
	$message .= implode( '、', $shown );
	if ( $remaining > 0 ) {
		$message .= sprintf(
			/* translators: %d: number of additional problems not individually listed. */
			__( ', and %d more', 'neccospeak' ),
			$remaining
		);
	}

	echo '<div class="ai-agent-action-result ' . esc_attr( $errors > 0 ? 'error' : 'success' ) . '" style="display:block;">' . esc_html( $message ) . '</div>';
}

/**
 * Inline "ロールバック" form (Actions card): a version picker + submit
 * button posting straight to includes/admin-versions.php's existing
 * admin_post_neccospeak_rollback handler (neccospeak_handle_versions_rollback()),
 * reusing its nonce action name so no new server-side rollback logic is
 * needed here -- only the picker UI. Mirrors production's inline
 * "#ai-agent-rollback-section" (select + button in the same card).
 *
 * @return void
 */
function neccospeak_agent_ui_dashboard_render_rollback_form() {
	$rows = array();
	if ( function_exists( 'neccospeak_db_ready' ) && neccospeak_db_ready() && function_exists( 'neccospeak_db_get_versions' ) ) {
		$result = neccospeak_db_get_versions(
			array(
				'page'     => 1,
				'per_page' => 20,
			)
		);
		$rows   = ( is_array( $result ) && isset( $result['rows'] ) && is_array( $result['rows'] ) ) ? array_values( $result['rows'] ) : array();
	}

	if ( empty( $rows ) ) {
		echo '<p class="description">' . esc_html__( 'There are no versions to roll back to yet.', 'neccospeak' ) . '</p>';
		return;
	}

	echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;" data-neccospeak-confirm="' . esc_attr__( 'Roll back? The current published settings will be replaced (except the API key).', 'neccospeak' ) . '">';
	echo '<input type="hidden" name="action" value="neccospeak_rollback" />';
	wp_nonce_field( 'neccospeak_versions_rollback' );
	echo '<select name="version_id" class="regular-text" style="width:auto;" required>';
	echo '<option value="">' . esc_html__( 'Select a version...', 'neccospeak' ) . '</option>';
	foreach ( $rows as $neccospeak_dashboard_raw_row ) {
		$neccospeak_dashboard_row = (array) $neccospeak_dashboard_raw_row;
		$neccospeak_dashboard_id  = isset( $neccospeak_dashboard_row['id'] ) ? (int) $neccospeak_dashboard_row['id'] : 0;
		if ( $neccospeak_dashboard_id <= 0 ) {
			continue;
		}
		$neccospeak_dashboard_created = isset( $neccospeak_dashboard_row['created_at'] ) ? (string) $neccospeak_dashboard_row['created_at'] : '';
		$neccospeak_dashboard_when    = $neccospeak_dashboard_created;
		if ( '' !== $neccospeak_dashboard_created && function_exists( 'wp_date' ) ) {
			$neccospeak_dashboard_ts = strtotime( $neccospeak_dashboard_created . ' UTC' );
			if ( $neccospeak_dashboard_ts ) {
				$neccospeak_dashboard_format = trim( get_option( 'date_format', 'Y-m-d' ) . ' ' . get_option( 'time_format', 'H:i' ) );
				$neccospeak_dashboard_when   = wp_date( '' !== $neccospeak_dashboard_format ? $neccospeak_dashboard_format : 'Y-m-d H:i', $neccospeak_dashboard_ts );
			}
		}
		$neccospeak_dashboard_origin = isset( $neccospeak_dashboard_row['origin'] ) ? (string) $neccospeak_dashboard_row['origin'] : '';
		if ( function_exists( 'neccospeak_versions_origin_label' ) ) {
			$neccospeak_dashboard_origin = neccospeak_versions_origin_label( $neccospeak_dashboard_origin );
		}
		$neccospeak_dashboard_label = trim( $neccospeak_dashboard_when . ' — ' . $neccospeak_dashboard_origin );
		echo '<option value="' . esc_attr( (string) $neccospeak_dashboard_id ) . '">' . esc_html( $neccospeak_dashboard_label ) . '</option>';
	}
	echo '</select>';
	echo '<button type="submit" class="button">' . esc_html__( 'Run rollback', 'neccospeak' ) . '</button>';
	echo '</form>';
	echo '<p class="description" style="margin-top:4px;">' . esc_html__( 'Replaces the published version with the selected version\'s settings (except the API key). The draft is updated at the same time.', 'neccospeak' ) . '</p>';
}
