<?php
/**
 * Guided schema form dialog shell and PHP field renderer.
 *
 * Client-side validity is advisory only; SchemaInstanceEndpoints remain
 * authoritative for persistence and readiness.
 *
 * @package SEOvaultSEO\Admin
 */

namespace SEOvaultSEO\Admin;

use SEOvaultSEO\Schema\SchemaGuidedFormSchema;
use SEOvaultSEO\Schema\SchemaInstanceEndpoints;
use SEOvaultSEO\Schema\SchemaSanitizer;

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

/**
 * Renders guided form chrome and field controls from server schemas.
 */
class SchemaGuidedFormRenderer {

	/**
	 * Dialog shell used by the metabox (empty body filled by JS or PHP).
	 *
	 * @param int $post_id Post ID.
	 * @return string
	 */
	public static function render_shell( $post_id = 0 ) {
		$post_id = absint( $post_id );
		$sync    = $post_id ? SchemaInstanceEndpoints::post_sync_meta( $post_id ) : array(
			'post_id'           => 0,
			'content_hash'      => '',
			'post_modified_gmt' => '',
		);

		$buffer_level = ob_get_level();
		ob_start();
		try {
			?>
		<dialog
			id="seovaultseo-schema-guided-form"
			class="seovaultseo-schema-guided-form"
			aria-labelledby="seovaultseo-schema-guided-form-title"
			aria-modal="true"
			data-post-id="<?php echo esc_attr( (string) $sync['post_id'] ); ?>"
			data-content-hash="<?php echo esc_attr( (string) $sync['content_hash'] ); ?>"
			data-post-modified-gmt="<?php echo esc_attr( (string) $sync['post_modified_gmt'] ); ?>"
		>
			<div class="seovaultseo-schema-add-picker-chrome">
				<header class="seovaultseo-schema-add-picker-header">
					<h2 id="seovaultseo-schema-guided-form-title"><?php esc_html_e( 'Guided schema form', 'seovault-ai' ); ?></h2>
					<button
						type="button"
						class="button-link seovaultseo-schema-guided-form-close"
						aria-label="<?php esc_attr_e( 'Close guided schema form', 'seovault-ai' ); ?>"
					>
						<span aria-hidden="true">&times;</span>
					</button>
				</header>
				<div class="seovaultseo-schema-guided-form-body" id="seovaultseo-schema-guided-form-body" tabindex="-1">
					<div class="seovaultseo-schema-guided-form-status" role="status" aria-live="polite" data-guided-setup-summary hidden></div>
					<div class="seovaultseo-schema-guided-form-fields" data-guided-form-fields></div>
					<div class="seovaultseo-schema-guided-form-errors" role="alert" data-guided-form-errors hidden></div>
				</div>
				<footer class="seovaultseo-schema-add-picker-footer seovaultseo-schema-guided-form-footer">
					<button type="button" class="button button-secondary seovaultseo-schema-guided-form-cancel">
						<?php esc_html_e( 'Cancel', 'seovault-ai' ); ?>
					</button>
					<button type="button" class="button button-link-delete seovaultseo-schema-guided-form-remove" hidden>
						<?php esc_html_e( 'Remove', 'seovault-ai' ); ?>
					</button>
					<button type="button" class="button button-primary seovaultseo-schema-guided-form-save">
						<?php esc_html_e( 'Save draft', 'seovault-ai' ); ?>
					</button>
				</footer>
			</div>
		</dialog>
			<?php

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

	/**
	 * Render a full form for a type (PHP/no-JS and unit tests).
	 *
	 * @param string               $type   Schema type.
	 * @param array<string,mixed>  $values Current values.
	 * @param array<string,mixed>  $extra  Extra: instance_id, source_meta, readiness.
	 * @return string
	 */
	public static function render_form( $type, array $values = array(), array $extra = array() ) {
		$schema = SchemaGuidedFormSchema::build_for_type( $type );
		if ( empty( $schema['ok'] ) ) {
			return '<p class="seovaultseo-schema-guided-form-unavailable">' . esc_html__( 'This schema form is not available.', 'seovault-ai' ) . '</p>';
		}

		$instance_id = isset( $extra['instance_id'] ) ? (string) $extra['instance_id'] : '';
		$source_meta = isset( $extra['source_meta'] ) && is_array( $extra['source_meta'] ) ? $extra['source_meta'] : array();
		$prefix      = 'seovault_guided_' . sanitize_key( $type );

		$buffer_level = ob_get_level();
		ob_start();
		try {
			?>
		<form class="seovaultseo-schema-guided-form-engine" data-schema-type="<?php echo esc_attr( $type ); ?>" data-instance-id="<?php echo esc_attr( $instance_id ); ?>" novalidate>
			<?php if ( ! empty( $schema['help'] ) ) : ?>
				<p class="seovaultseo-schema-guided-form-help" data-guided-type-help="1"><?php echo esc_html( (string) $schema['help'] ); ?></p>
			<?php endif; ?>
			<?php
			$source_label = SchemaReviewSurface::resolve_setup_source_label(
				array(
					'source'      => 'post_instance',
					'source_meta' => $source_meta,
				)
			);
			if ( '' !== $source_label ) :
				?>
				<p class="seovaultseo-schema-guided-form-source" data-guided-source-label="1">
					<span class="seovaultseo-schema-source-label"><?php echo esc_html( $source_label ); ?></span>
				</p>
			<?php endif; ?>
			<p class="seovaultseo-schema-guided-form-advisory" role="note">
				<?php esc_html_e( 'Field hints help you fill the form. SEOvault decides readiness on the server when you save.', 'seovault-ai' ); ?>
			</p>
			<?php echo self::render_fields( $schema['fields'], $values, $prefix ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escapes inside. ?>
			<?php echo self::render_visibility_section( $schema, $source_meta, $prefix ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escapes inside. ?>
		</form>
			<?php

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

	/**
	 * Render a list of fields.
	 *
	 * @param array                $fields Field schemas.
	 * @param array<string,mixed>  $values Values.
	 * @param string               $prefix Name prefix.
	 * @return string
	 */
	public static function render_fields( array $fields, array $values, $prefix ) {
		$html = '';
		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) || empty( $field['id'] ) ) {
				continue;
			}
			$html .= self::render_field( $field, $values, $prefix );
		}
		return $html;
	}

	/**
	 * Render one field control.
	 *
	 * @param array               $field  Field schema.
	 * @param array<string,mixed> $values Values bag.
	 * @param string              $prefix Name prefix.
	 * @return string
	 */
	public static function render_field( array $field, array $values, $prefix ) {
		$id      = (string) $field['id'];
		$control = (string) ( $field['control'] ?? 'text' );
		$name    = $prefix . '[' . $id . ']';
		$input_id = $prefix . '_' . sanitize_html_class( $id );
		$value   = array_key_exists( $id, $values ) ? $values[ $id ] : ( $field['default'] ?? '' );
		$desc_id = $input_id . '_desc';
		$err_id  = $input_id . '_error';
		$required = ! empty( $field['required'] );

		if ( 'hidden' === $control ) {
			return sprintf(
				'<input type="hidden" name="%1$s" id="%2$s" value="%3$s" data-guided-field="%4$s" data-guided-control="hidden" />',
				esc_attr( $name ),
				esc_attr( $input_id ),
				esc_attr( is_scalar( $value ) ? (string) $value : '' ),
				esc_attr( $id )
			);
		}

		$buffer_level = ob_get_level();
		ob_start();
		try {
			$visible_when = isset( $field['visible_when'] ) && is_array( $field['visible_when'] ) ? $field['visible_when'] : null;
			$when_attrs   = '';
			if ( is_array( $visible_when ) ) {
				if ( isset( $visible_when['path'] ) ) {
					$when_attrs .= ' data-visible-when-path="' . esc_attr( (string) $visible_when['path'] ) . '"';
				}
				if ( isset( $visible_when['in'] ) && is_array( $visible_when['in'] ) ) {
					$when_attrs .= ' data-visible-when-in="' . esc_attr( wp_json_encode( array_values( $visible_when['in'] ) ) ) . '"';
				}
				if ( isset( $visible_when['not_in'] ) && is_array( $visible_when['not_in'] ) ) {
					$when_attrs .= ' data-visible-when-not-in="' . esc_attr( wp_json_encode( array_values( $visible_when['not_in'] ) ) ) . '"';
				}
			}
			?>
		<div class="seovaultseo-schema-guided-field seovaultseo-schema-guided-field-<?php echo esc_attr( $control ); ?>" data-guided-field-wrap="<?php echo esc_attr( $id ); ?>"<?php echo $when_attrs; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built with esc_attr above. ?>>
			<?php if ( 'group' !== $control && 'list' !== $control && 'list_text' !== $control ) : ?>
				<label for="<?php echo esc_attr( $input_id ); ?>">
					<?php echo esc_html( (string) ( $field['label'] ?? $id ) ); ?>
					<?php if ( $required ) : ?>
						<span class="seovaultseo-schema-guided-required" aria-hidden="true">*</span>
					<?php endif; ?>
				</label>
			<?php else : ?>
				<span class="seovaultseo-schema-guided-legend" id="<?php echo esc_attr( $input_id ); ?>_legend">
					<?php echo esc_html( (string) ( $field['label'] ?? $id ) ); ?>
					<?php if ( $required ) : ?>
						<span class="seovaultseo-schema-guided-required" aria-hidden="true">*</span>
					<?php endif; ?>
				</span>
			<?php endif; ?>

			<?php if ( ! empty( $field['description'] ) ) : ?>
				<p class="description" id="<?php echo esc_attr( $desc_id ); ?>"><?php echo esc_html( (string) $field['description'] ); ?></p>
			<?php endif; ?>

			<?php
			$describedby = trim(
				( ! empty( $field['description'] ) ? $desc_id . ' ' : '' ) . $err_id
			);
			echo self::render_control( $field, $value, $name, $input_id, $describedby, $required ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			?>

			<p class="seovaultseo-schema-guided-field-error" id="<?php echo esc_attr( $err_id ); ?>" data-guided-field-error hidden></p>
		</div>
			<?php

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

	/**
	 * Render the control element(s).
	 *
	 * @param array  $field       Field.
	 * @param mixed  $value       Value.
	 * @param string $name        Input name.
	 * @param string $input_id    DOM id.
	 * @param string $describedby aria-describedby.
	 * @param bool   $required    Required flag (advisory).
	 * @return string
	 */
	private static function render_control( array $field, $value, $name, $input_id, $describedby, $required ) {
		$control = (string) ( $field['control'] ?? 'text' );
		$id      = (string) $field['id'];
		$attrs   = sprintf(
			' id="%1$s" name="%2$s" data-guided-field="%3$s" data-guided-control="%4$s"%5$s%6$s',
			esc_attr( $input_id ),
			esc_attr( $name ),
			esc_attr( $id ),
			esc_attr( $control ),
			$describedby ? ' aria-describedby="' . esc_attr( $describedby ) . '"' : '',
			$required ? ' aria-required="true"' : ''
		);

		switch ( $control ) {
			case 'textarea':
				return '<textarea class="large-text"' . $attrs . ' rows="4">' . esc_textarea( is_scalar( $value ) ? (string) $value : '' ) . '</textarea>';

			case 'url':
				return '<input type="url" class="regular-text"' . $attrs . ' value="' . esc_attr( is_scalar( $value ) ? (string) $value : '' ) . '" />';

			case 'number':
				return '<input type="number" class="small-text"' . $attrs . ' value="' . esc_attr( is_scalar( $value ) ? (string) $value : '' ) . '" />';

			case 'datetime':
				$local = is_string( $value ) ? SchemaSanitizer::to_datetime_local_value( $value ) : '';
				return '<input type="datetime-local" class="regular-text"' . $attrs . ' value="' . esc_attr( $local ) . '" />';

			case 'enum':
				$html = '<select' . $attrs . '>';
				$html .= '<option value="">' . esc_html__( 'Select…', 'seovault-ai' ) . '</option>';
				foreach ( (array) ( $field['options'] ?? array() ) as $option ) {
					if ( ! is_array( $option ) ) {
						continue;
					}
					$opt_value = (string) ( $option['value'] ?? '' );
					$html     .= sprintf(
						'<option value="%1$s"%2$s>%3$s</option>',
						esc_attr( $opt_value ),
						selected( (string) $value, $opt_value, false ),
						esc_html( (string) ( $option['label'] ?? $opt_value ) )
					);
				}
				$html .= '</select>';
				return $html;

			case 'media':
				$display = is_scalar( $value ) ? (string) $value : '';
				return sprintf(
					'<div class="seovaultseo-schema-guided-media">
						<input type="text" class="regular-text"%1$s value="%2$s" />
						<button type="button" class="button seovaultseo-schema-guided-media-select" data-guided-media-for="%3$s">%4$s</button>
					</div>',
					$attrs,
					esc_attr( $display ),
					esc_attr( $input_id ),
					esc_html__( 'Select media', 'seovault-ai' )
				);

			case 'group':
				$nested_values = is_array( $value ) ? $value : array();
				$nested_fields = isset( $field['fields'] ) && is_array( $field['fields'] ) ? $field['fields'] : array();
				return '<fieldset class="seovaultseo-schema-guided-group" aria-labelledby="' . esc_attr( $input_id ) . '_legend" data-guided-field="' . esc_attr( $id ) . '" data-guided-control="group">'
					. self::render_fields( $nested_fields, $nested_values, $name )
					. '</fieldset>';

			case 'list':
				return self::render_list_group( $field, $value, $name, $input_id );

			case 'list_text':
				return self::render_list_text( $field, $value, $name, $input_id );

			case 'text':
			default:
				return '<input type="text" class="regular-text"' . $attrs . ' value="' . esc_attr( is_scalar( $value ) ? (string) $value : '' ) . '" />';
		}
	}

	/**
	 * Repeatable nested group list.
	 *
	 * @param array  $field    Field.
	 * @param mixed  $value    Value.
	 * @param string $name     Name.
	 * @param string $input_id Id.
	 * @return string
	 */
	private static function render_list_group( array $field, $value, $name, $input_id ) {
		$items         = is_array( $value ) ? array_values( $value ) : array();
		$nested_fields = isset( $field['fields'] ) && is_array( $field['fields'] ) ? $field['fields'] : array();
		if ( empty( $items ) ) {
			$items = array( array() );
		}

		$html  = '<div class="seovaultseo-schema-guided-list" data-guided-field="' . esc_attr( (string) $field['id'] ) . '" data-guided-control="list" aria-labelledby="' . esc_attr( $input_id ) . '_legend">';
		foreach ( $items as $index => $item ) {
			$item_values = is_array( $item ) ? $item : array();
			$html       .= '<div class="seovaultseo-schema-guided-list-item" data-guided-list-index="' . esc_attr( (string) $index ) . '">';
			$html       .= self::render_fields( $nested_fields, $item_values, $name . '[' . (int) $index . ']' );
			$html       .= '<button type="button" class="button-link seovaultseo-schema-guided-list-remove">' . esc_html__( 'Remove item', 'seovault-ai' ) . '</button>';
			$html       .= '</div>';
		}
		$html .= '<button type="button" class="button seovaultseo-schema-guided-list-add">' . esc_html__( 'Add item', 'seovault-ai' ) . '</button>';
		$html .= '</div>';
		return $html;
	}

	/**
	 * Repeatable plain-text list.
	 *
	 * @param array  $field    Field.
	 * @param mixed  $value    Value.
	 * @param string $name     Name.
	 * @param string $input_id Id.
	 * @return string
	 */
	private static function render_list_text( array $field, $value, $name, $input_id ) {
		if ( is_string( $value ) && '' !== $value ) {
			$items = preg_split( '/\r\n|\r|\n/', $value );
		} elseif ( is_array( $value ) ) {
			$items = array_values( $value );
		} else {
			$items = array( '' );
		}
		if ( empty( $items ) ) {
			$items = array( '' );
		}

		$html = '<div class="seovaultseo-schema-guided-list-text" data-guided-field="' . esc_attr( (string) $field['id'] ) . '" data-guided-control="list_text" aria-labelledby="' . esc_attr( $input_id ) . '_legend">';
		foreach ( $items as $index => $item ) {
			$html .= sprintf(
				'<div class="seovaultseo-schema-guided-list-item"><input type="text" class="regular-text" name="%1$s[%2$d]" value="%3$s" data-guided-list-text="1" /><button type="button" class="button-link seovaultseo-schema-guided-list-remove">%4$s</button></div>',
				esc_attr( $name ),
				(int) $index,
				esc_attr( is_scalar( $item ) ? (string) $item : '' ),
				esc_html__( 'Remove', 'seovault-ai' )
			);
		}
		$html .= '<button type="button" class="button seovaultseo-schema-guided-list-add">' . esc_html__( 'Add item', 'seovault-ai' ) . '</button>';
		$html .= '</div>';
		return $html;
	}

	/**
	 * Content visibility confirmation section.
	 *
	 * @param array               $schema     Form schema.
	 * @param array<string,mixed> $source_meta Source meta.
	 * @param string              $prefix     Prefix.
	 * @return string
	 */
	public static function render_visibility_section( array $schema, array $source_meta, $prefix ) {
		$mode   = isset( $source_meta['visibility_mode'] ) ? (string) $source_meta['visibility_mode'] : 'confirm_visible';
		$anchor = isset( $source_meta['visibility_anchor'] ) ? (string) $source_meta['visibility_anchor'] : '';
		$name   = $prefix . '[__visibility_mode]';
		$anchor_name = $prefix . '[__visibility_anchor]';
		$requires = ! empty( $schema['requires_visibility'] );

		$buffer_level = ob_get_level();
		ob_start();
		try {
			?>
		<fieldset class="seovaultseo-schema-guided-visibility" data-guided-visibility="1">
			<legend><?php esc_html_e( 'Content visibility', 'seovault-ai' ); ?></legend>
			<p class="description">
				<?php
				if ( $requires ) {
					esc_html_e( 'Specialized schema should match content readers can see on the page.', 'seovault-ai' );
				} else {
					esc_html_e( 'Confirm how this schema relates to visible page content.', 'seovault-ai' );
				}
				?>
			</p>
			<label class="seovaultseo-schema-guided-visibility-option">
				<input type="radio" name="<?php echo esc_attr( $name ); ?>" value="seovault_block" <?php checked( $mode, 'seovault_block' ); ?> data-guided-visibility-mode="1" />
				<?php esc_html_e( 'I will keep matching visible content (SEOvault block/summary when available).', 'seovault-ai' ); ?>
			</label>
			<label class="seovaultseo-schema-guided-visibility-option">
				<input type="radio" name="<?php echo esc_attr( $name ); ?>" value="confirm_visible" <?php checked( $mode, 'confirm_visible' ); ?> data-guided-visibility-mode="1" />
				<?php esc_html_e( 'I confirm equivalent content is already visible on this page.', 'seovault-ai' ); ?>
			</label>
			<label for="<?php echo esc_attr( $prefix ); ?>_visibility_anchor" class="seovaultseo-schema-guided-visibility-anchor-label">
				<?php esc_html_e( 'Optional page section / anchor reference', 'seovault-ai' ); ?>
			</label>
			<input
				type="text"
				class="regular-text"
				id="<?php echo esc_attr( $prefix ); ?>_visibility_anchor"
				name="<?php echo esc_attr( $anchor_name ); ?>"
				value="<?php echo esc_attr( $anchor ); ?>"
				data-guided-visibility-anchor="1"
				placeholder="<?php esc_attr_e( 'e.g. #event-details or “Event section”', 'seovault-ai' ); ?>"
			/>
		</fieldset>
			<?php

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

	/**
	 * Supported control types (for notes/tests).
	 *
	 * @return string[]
	 */
	public static function supported_controls() {
		return SchemaGuidedFormSchema::CONTROL_TYPES;
	}
}
