<?php
/**
 * Advanced custom JSON editor UX for the post meta box.
 *
 * @package SEOvaultSEO\Admin
 */

namespace SEOvaultSEO\Admin;

use SEOvaultSEO\Core\PostMeta;
use SEOvaultSEO\Schema\SchemaCustomJson;
use SEOvaultSEO\Schema\Validation\PostSchemaValidationService;

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

/**
 * Renders the Advanced JSON-LD escape hatch with mode, preview, and restore controls.
 */
class SchemaCustomJsonEditor {

	/**
	 * Render the Advanced custom JSON section for a post.
	 *
	 * @param int $post_id Post ID.
	 * @return string
	 */
	public static function render( $post_id ) {
		$post_id = (int) $post_id;
		$json    = PostMeta::get_schema_json( $post_id );
		$mode    = SchemaCustomJson::get_mode( $post_id );
		$meta    = SchemaCustomJson::get_meta_document( $post_id );
		$ux      = SchemaCustomJson::describe_ux_state( $post_id );

		if ( '' === $mode && '' !== trim( (string) $json ) ) {
			$mode = SchemaCustomJson::MODE_ADD;
		}
		if ( SchemaCustomJson::MODE_INVALID_WITHHELD === $mode ) {
			$mode = SchemaCustomJson::MODE_ADD;
		}

		$replace_confirmed = ! empty( $meta['replace_confirmed'] ) || ! empty( $meta['legacy'] );
		$open_advanced     = '' !== trim( (string) $json )
			|| ! empty( $ux['can_restore'] )
			|| in_array( $ux['state'], array( 'invalid_saved', 'replace_unconfirmed', 'emitted_replace' ), true );

		$buffer_level = ob_get_level();
		ob_start();
		try {
			?>
		<details
			class="seovaultseo-schema-custom-json"
			id="seovaultseo-schema-custom-json"
			data-schema-custom-json
			data-ux-state="<?php echo esc_attr( $ux['state'] ); ?>"
			data-saved-fingerprint="<?php echo esc_attr( self::fingerprint( (string) $json ) ); ?>"
			<?php echo $open_advanced ? 'open' : ''; ?>
		>
			<summary><?php esc_html_e( 'Advanced JSON-LD', 'seovault-ai' ); ?></summary>

			<p class="description seovaultseo-schema-custom-json-intro">
				<?php esc_html_e( 'Optional escape hatch for experts. Prefer blocks or guided forms when possible. New saves default to Add (merge). Replace removes automatic schema and requires confirmation.', 'seovault-ai' ); ?>
			</p>

			<div
				class="seovaultseo-schema-custom-json-banner seovaultseo-schema-custom-json-banner--<?php echo esc_attr( $ux['state'] ); ?>"
				data-schema-custom-json-banner
				role="status"
				aria-live="polite"
			>
				<strong data-schema-custom-json-banner-label><?php echo esc_html( $ux['label'] ); ?></strong>
				<span data-schema-custom-json-banner-summary><?php echo esc_html( $ux['summary'] ); ?></span>
				<p class="seovaultseo-schema-custom-json-published" data-schema-custom-json-published>
					<?php echo esc_html( self::published_graph_label( $ux['published_graph'] ) ); ?>
				</p>
			</div>

			<div
				class="seovaultseo-schema-custom-json-banner seovaultseo-schema-custom-json-banner--unsaved"
				data-schema-custom-json-unsaved
				role="status"
				aria-live="polite"
				hidden
			>
				<strong><?php esc_html_e( 'Unsaved editor changes', 'seovault-ai' ); ?></strong>
				<span><?php esc_html_e( 'These edits are not in the published graph until you save the post.', 'seovault-ai' ); ?></span>
			</div>

			<div class="seovault-ai-field">
				<label for="seovault_schema_json_mode">
					<?php esc_html_e( 'Custom JSON mode', 'seovault-ai' ); ?>
				</label>
				<select
					id="seovault_schema_json_mode"
					name="seovault_schema_json_mode"
					data-schema-custom-json-mode
				>
					<option value="add" <?php selected( $mode, SchemaCustomJson::MODE_ADD ); ?>>
						<?php esc_html_e( 'Add — merge with automatic schema', 'seovault-ai' ); ?>
					</option>
					<option value="replace" <?php selected( $mode, SchemaCustomJson::MODE_REPLACE ); ?>>
						<?php esc_html_e( 'Replace — override automatic schema', 'seovault-ai' ); ?>
					</option>
				</select>
				<p class="description" data-schema-custom-json-mode-help-add <?php echo SchemaCustomJson::MODE_REPLACE === $mode ? 'hidden' : ''; ?>>
					<?php esc_html_e( 'Add keeps WebPage, Article, breadcrumbs, and other automatic entities, then merges your custom nodes. Conflicting @id values follow source precedence (custom wins when tagged).', 'seovault-ai' ); ?>
				</p>
				<p class="description" data-schema-custom-json-mode-help-replace <?php echo SchemaCustomJson::MODE_REPLACE === $mode ? '' : 'hidden'; ?>>
					<?php esc_html_e( 'Replace publishes only your custom graph for this page. Automatic entities disappear until you switch back to Add or clear the JSON.', 'seovault-ai' ); ?>
				</p>
			</div>

			<div
				class="seovault-ai-field seovaultseo-schema-custom-json-replace-confirm"
				data-schema-custom-json-replace-wrap
				<?php echo SchemaCustomJson::MODE_REPLACE === $mode ? '' : 'hidden'; ?>
			>
				<label>
					<input
						type="checkbox"
						id="seovault_schema_json_replace_confirm"
						name="seovault_schema_json_replace_confirm"
						value="1"
						data-schema-custom-json-replace-confirm
						<?php checked( $replace_confirmed ); ?>
					/>
					<?php esc_html_e( 'I understand Replace removes automatic schema entities for this page', 'seovault-ai' ); ?>
				</label>
			</div>

			<div class="seovault-ai-field">
				<label for="seovault_schema_json">
					<?php esc_html_e( 'Custom Schema JSON', 'seovault-ai' ); ?>
				</label>
				<textarea
					id="seovault_schema_json"
					name="seovault_schema_json"
					rows="10"
					class="large-text code"
					data-schema-custom-json-input
					spellcheck="false"
					aria-describedby="seovaultseo-schema-custom-json-status"
				><?php echo esc_textarea( $json ); ?></textarea>
			</div>

			<div class="seovaultseo-schema-custom-json-toolbar" role="group" aria-label="<?php esc_attr_e( 'Custom JSON tools', 'seovault-ai' ); ?>">
				<button
					type="button"
					class="button button-secondary"
					data-schema-custom-json-format
				>
					<?php esc_html_e( 'Format', 'seovault-ai' ); ?>
				</button>
				<button
					type="button"
					class="button button-secondary"
					data-schema-custom-json-preview
				>
					<?php esc_html_e( 'Preview', 'seovault-ai' ); ?>
				</button>
				<?php if ( ! empty( $ux['can_restore'] ) ) : ?>
					<button
						type="submit"
						class="button button-secondary"
						name="seovault_schema_json_restore_last_valid"
						value="1"
						data-schema-custom-json-restore
						formnovalidate
					>
						<?php esc_html_e( 'Restore last valid', 'seovault-ai' ); ?>
					</button>
				<?php endif; ?>
			</div>

			<p
				id="seovaultseo-schema-custom-json-status"
				class="seovaultseo-schema-custom-json-status"
				data-schema-custom-json-status
				role="status"
				aria-live="polite"
			></p>

			<div
				class="seovaultseo-schema-custom-json-preview"
				data-schema-custom-json-preview-panel
				hidden
			>
				<h4><?php esc_html_e( 'Preview', 'seovault-ai' ); ?></h4>
				<div data-schema-custom-json-preview-body></div>
			</div>

			<noscript>
				<p class="description">
					<?php esc_html_e( 'Without JavaScript, Format and Preview are unavailable. Mode, confirmation, Restore last valid, and Save still work when you update the post.', 'seovault-ai' ); ?>
				</p>
			</noscript>
		</details>
			<?php

			return (string) ob_get_clean();
		} finally {
			while ( ob_get_level() > $buffer_level ) {
				ob_end_clean();
			}
		}
	}

	/**
	 * Render a compact preview body from a validation response.
	 *
	 * @param array $validation Validation response.
	 * @param array $ux         UX state from SchemaCustomJson::describe_ux_state().
	 * @return string
	 */
	public static function render_preview_body( array $validation, array $ux ) {
		$build      = isset( $validation['build'] ) && is_array( $validation['build'] ) ? $validation['build'] : array();
		$emitted    = isset( $build['emitted'] ) && is_array( $build['emitted'] ) ? $build['emitted'] : array();
		$conflicts  = isset( $build['conflicts'] ) && is_array( $build['conflicts'] ) ? $build['conflicts'] : array();
		$setup      = isset( $validation['setup'] ) && is_array( $validation['setup'] ) ? $validation['setup'] : array();
		$custom     = isset( $setup['custom_json'] ) && is_array( $setup['custom_json'] ) ? $setup['custom_json'] : null;
		$result     = isset( $validation['result'] ) && is_array( $validation['result'] ) ? $validation['result'] : array();

		$buffer_level = ob_get_level();
		ob_start();
		try {
			?>
		<p><strong><?php echo esc_html( (string) ( $ux['label'] ?? '' ) ); ?></strong> — <?php echo esc_html( (string) ( $ux['summary'] ?? '' ) ); ?></p>
		<p><?php echo esc_html( self::published_graph_label( (string) ( $ux['published_graph'] ?? 'automatic' ) ) ); ?></p>
			<?php if ( is_array( $custom ) && ! empty( $custom['errors'] ) ) : ?>
			<p class="seovaultseo-schema-custom-json-preview-error">
				<?php
				printf(
					/* translators: %d: error count */
					esc_html( _n( '%d custom JSON error.', '%d custom JSON errors.', (int) $custom['errors'], 'seovault-ai' ) ),
					(int) $custom['errors']
				);
				?>
			</p>
		<?php endif; ?>
			<?php if ( ! empty( $result['errors'] ) ) : ?>
			<p class="seovaultseo-schema-custom-json-preview-error">
				<?php
				printf(
					/* translators: %d: error count */
					esc_html( _n( '%d published-graph validation error.', '%d published-graph validation errors.', (int) $result['errors'], 'seovault-ai' ) ),
					(int) $result['errors']
				);
				?>
			</p>
		<?php endif; ?>
		<h5><?php esc_html_e( 'Entities in current published graph preview', 'seovault-ai' ); ?></h5>
			<?php if ( empty( $emitted ) ) : ?>
			<p class="description"><?php esc_html_e( 'No entities would be emitted.', 'seovault-ai' ); ?></p>
		<?php else : ?>
			<ul>
				<?php foreach ( array_slice( $emitted, 0, 20 ) as $row ) : ?>
					<?php if ( ! is_array( $row ) ) { continue; } ?>
					<li>
						<span><?php echo esc_html( (string) ( $row['type'] ?? '' ) ); ?></span>
						<span class="description"><?php echo esc_html( SchemaReviewSurface::resolve_emitted_source_label( $row ) ); ?></span>
					</li>
				<?php endforeach; ?>
			</ul>
		<?php endif; ?>
			<?php if ( ! empty( $conflicts ) ) : ?>
			<h5><?php esc_html_e( 'Conflicts', 'seovault-ai' ); ?></h5>
			<ul>
				<?php foreach ( array_slice( $conflicts, 0, 10 ) as $conflict ) : ?>
					<?php if ( ! is_array( $conflict ) ) { continue; } ?>
					<li>
						<?php echo esc_html( (string) ( $conflict['type'] ?? '' ) ); ?>
						—
						<?php echo esc_html( (string) ( $conflict['reason'] ?? __( 'Source conflict', 'seovault-ai' ) ) ); ?>
					</li>
				<?php endforeach; ?>
			</ul>
		<?php endif; ?>
			<?php

			return (string) ob_get_clean();
		} finally {
			while ( ob_get_level() > $buffer_level ) {
				ob_end_clean();
			}
		}
	}

	/**
	 * Author label for which graph publishes.
	 *
	 * @param string $key published_graph key.
	 * @return string
	 */
	public static function published_graph_label( $key ) {
		switch ( (string) $key ) {
			case 'custom_replace':
				return __( 'Published graph: custom JSON only (Replace).', 'seovault-ai' );
			case 'automatic_plus_custom':
				return __( 'Published graph: automatic schema plus custom JSON (Add).', 'seovault-ai' );
			default:
				return __( 'Published graph: automatic schema only.', 'seovault-ai' );
		}
	}

	/**
	 * Stable fingerprint for dirty checking.
	 *
	 * @param string $value Raw string.
	 * @return string
	 */
	public static function fingerprint( $value ) {
		return 'sha256:' . hash( 'sha256', (string) $value );
	}

	/**
	 * Build a preview payload for AJAX (no raw secrets beyond the editor echo).
	 *
	 * @param int    $post_id Post ID.
	 * @param string $raw     Editor JSON.
	 * @param string $mode    Requested mode.
	 * @return array
	 */
	public static function build_preview_payload( $post_id, $raw, $mode = '' ) {
		$post_id = (int) $post_id;
		$service = new PostSchemaValidationService();
		$validation = $service->validate_editor_input( $post_id, $raw );
		$ux         = SchemaCustomJson::describe_ux_state( $post_id, $raw );

		// Overlay requested mode for preview messaging when editor mode differs from saved.
		$mode = SchemaCustomJson::sanitize_mode( $mode );
		if ( SchemaCustomJson::MODE_REPLACE === $mode && 'add' === ( $ux['action'] ?? '' ) ) {
			$ux['label']   = __( 'Preview — Replace (not confirmed on save yet)', 'seovault-ai' );
			$ux['summary'] = __( 'If you save with Replace confirmed, automatic entities disappear. This preview still reflects current saved confirmation rules when validating the editor draft.', 'seovault-ai' );
		}

		$formatted = SchemaCustomJson::format( $raw );

		return array(
			'ok'       => true,
			'ux'       => $ux,
			'html'     => self::render_preview_body( $validation, $ux ),
			'format_ok'=> ! empty( $formatted['ok'] ),
			'state'    => (string) ( $validation['state'] ?? '' ),
			'scope'    => (string) ( $validation['scope'] ?? '' ),
			'setup'    => isset( $validation['setup'] ) ? $validation['setup'] : array(),
			'build'    => isset( $validation['build'] ) ? $validation['build'] : array(),
		);
	}
}
