<?php
/**
 * Ability: wp-agent/upsert-pattern
 *
 * Legt eine Landing-Page (post_type 'page') aus einer deklarativen Sektionsliste
 * an oder aktualisiert sie idempotent (über slug oder post_id). Das Block-Markup
 * wird serverseitig als valide Gutenberg-Core-Blocks erzeugt, damit WordPress nie
 * still auf den Classic Editor (core/freeform) zurückfällt. Vor dem Speichern
 * wird mit parse_blocks verifiziert, dass 0 freeform-Blöcke entstehen.
 *
 * Design: Jede Sektion wird zu einem vollbreiten core/group mit Farb-, Abstands-
 * und Rahmen-Tokens aus theme.json. Feature-Karten liegen in einem responsiven
 * core/group mit Grid-Layout (statt N zerquetschten Spalten). Die HTML-Klassen
 * und Inline-Styles entsprechen exakt der Gutenberg-Serialisierung, damit der
 * Editor die Blöcke als gültig akzeptiert (keine Block-Recovery). Farb-, Abstands-
 * und Schriftgrössen-Slugs werden gegen die aktive theme.json-Skala aufgelöst:
 * unbekannte Farb-Defaults werden weggelassen, ein fehlender Abstands-Slug auf den
 * numerisch nächsten vorhandenen abgebildet und eine fehlende Schriftgrösse auf die
 * nächste Standardgrösse, statt tote has-*-Klassen in ein fremdes Theme zu schreiben.
 * So hängt der Builder nicht mehr an der Twenty-Twenty-Five-Skala.
 *
 * @package WPAgentCompanion
 */

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

function wpagent_ability_upsert_pattern(): array {
	return array(
		'label'               => __( 'Landing-Page aus Sektionen anlegen/aktualisieren', 'wp-agent-companion' ),
		'description'         => __( 'Baut eine Landing-Page (Seite) aus einer deklarativen Sektionsliste (hero, features, media-text, logos, cta, pricing, testimonials, faq, stats, gallery, steps, comparison) als professionell gestaltete, valide Gutenberg-Core-Blöcke mit theme.json-Tokens (Farben, Abstände, Grid-Karten, Split-Layouts, Karten-Bilder, Hero-Hintergrundbild via core/cover, Bildergalerien via core/gallery, Akkordeons, KPI-Grids, nummerierte Prozess-Schritte, Vergleichstabellen via core/table) und legt sie idempotent über slug oder post_id an bzw. aktualisiert sie. Erzeugt garantiert 0 freeform.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'       => 'object',
			'properties' => array(
				'title'    => array(
					'type'        => 'string',
					'description' => __( 'Seitentitel.', 'wp-agent-companion' ),
				),
				'slug'     => array(
					'type'        => 'string',
					'description' => __( 'Seiten-Slug; wird für die idempotente Zuordnung genutzt, wenn keine post_id gesetzt ist.', 'wp-agent-companion' ),
				),
				'post_id'  => array(
					'type'        => 'integer',
					'description' => __( 'Wenn gesetzt, wird genau diese Seite aktualisiert (höchste Priorität vor slug).', 'wp-agent-companion' ),
				),
				'status'   => array(
					'type'    => 'string',
					'enum'    => array( 'draft', 'publish', 'pending', 'private' ),
					'default' => 'draft',
				),
				'sections' => array(
					'type'        => 'array',
					'description' => __( 'Geordnete Sektionsliste. Jede Sektion hat ein type-Feld (hero|features|media-text|logos|cta|pricing|testimonials|faq|stats|gallery|steps|comparison) plus die zugehörigen Felder.', 'wp-agent-companion' ),
					'items'       => array(
						'type'       => 'object',
						'properties' => array(
							'type'            => array(
								'type' => 'string',
								'enum' => array( 'hero', 'features', 'media-text', 'logos', 'cta', 'pricing', 'testimonials', 'faq', 'stats', 'gallery', 'steps', 'comparison' ),
							),
							'heading'         => array( 'type' => 'string' ),
							'subheading'      => array(
								'type'        => 'string',
								'description' => __( 'Optionale Unterüberschrift unter dem H2 (gedämpft, mittig).', 'wp-agent-companion' ),
							),
							'backgroundColor' => array(
								'type'        => 'string',
								'description' => __( 'Optionaler theme.json-Farb-Slug für den Sektions-Hintergrund (z. B. accent-3). Ohne Angabe wählt der Builder einen sinnvollen Standard.', 'wp-agent-companion' ),
							),
							'textColor'       => array(
								'type'        => 'string',
								'description' => __( 'Optionaler theme.json-Farb-Slug für die Sektions-Textfarbe.', 'wp-agent-companion' ),
							),
							'cta_label'       => array( 'type' => 'string' ),
							'cta_url'         => array( 'type' => 'string' ),
							'button_label'    => array(
								'type'        => 'string',
								'description' => __( 'Alternative zu cta_label. hero und cta akzeptieren BEIDE Schreibweisen (Fallback), damit kein Button still verschluckt wird.', 'wp-agent-companion' ),
							),
							'button_url'      => array( 'type' => 'string' ),
							'image_url'       => array(
								'type'        => 'string',
								'description' => __( 'media-text: Bild-URL des Split-Layouts (Pflicht für diese Sektion). hero: optionales Hintergrundbild; mit ihm wird der Hero als core/cover (Bild + Farb-Overlay) statt als farbiger Block gerendert.', 'wp-agent-companion' ),
							),
							'image_alt'       => array(
								'type'        => 'string',
								'description' => __( 'media-text/hero: Alt-Text des Sektions- bzw. Hintergrundbildes (darf leer sein).', 'wp-agent-companion' ),
							),
							'dimRatio'        => array(
								'type'        => 'integer',
								'description' => __( 'Nur hero mit image_url: Deckkraft des Farb-Overlays über dem Hintergrundbild (0..100, Standard 50). Wird auf 0..100 geklemmt.', 'wp-agent-companion' ),
							),
							'mediaPosition'   => array(
								'type'        => 'string',
								'enum'        => array( 'left', 'right' ),
								'description' => __( 'Nur media-text: Bildseite. Ohne Angabe wechseln aufeinanderfolgende media-text-Sektionen automatisch die Seite (erste links).', 'wp-agent-companion' ),
							),
							'columns'         => array(
								'type'        => 'array',
								'items'       => array( 'type' => 'string' ),
								'description' => __( 'Nur comparison: Spalten-Überschriften der Vergleichstabelle. Die erste Spalte ist das Zeilen-Label, die weiteren die Vergleichsobjekte (z. B. ["Merkmal","wpagently","Andere"]).', 'wp-agent-companion' ),
							),
							'items'           => array(
								'type'        => 'array',
								'description' => __( 'Karten/Logos/Preis-Pläne/Stimmen/FAQ-Fragen/KPI-Kacheln/Prozess-Schritte/Tabellenzeilen, je nach Sektionstyp.', 'wp-agent-companion' ),
								'items'       => array(
									'type'       => 'object',
									'properties' => array(
										'title'        => array( 'type' => 'string' ),
										'text'         => array( 'type' => 'string' ),
										'subheading'   => array(
											'type'        => 'string',
											'description' => __( 'Nur stats: optionaler kleiner Zusatz unter dem Label einer KPI-Kachel.', 'wp-agent-companion' ),
										),
										'badge'        => array(
											'type'        => 'string',
											'description' => __( 'Optionales kleines, farbiges Label oben in der Karte.', 'wp-agent-companion' ),
										),
										'icon'         => array(
											'type'        => 'string',
											'description' => __( 'Nur features/stats/steps: optionaler Icon-Name aus der mitgelieferten Phosphor-Allowlist. Wird als kleines, natives core/image oben in der Karte/Kachel über Titel bzw. Nummer gerendert. Erlaubt: check, check-circle, star, rocket, shield-check, lightning, heart, gear-six, chart-line-up, users-three, lock-key, magic-wand, clock, globe, sparkle, trophy, thumbs-up, wrench, plugs-connected, gauge. Unbekannter oder leerer Name: kein Icon (Sektion bleibt valide).', 'wp-agent-companion' ),
										),
										'label'        => array(
											'type'        => 'string',
											'description' => __( 'Alias für badge. Bei comparison zusätzlich das Zeilen-Label (erste Spalte) einer Tabellenzeile.', 'wp-agent-companion' ),
										),
										'price'        => array(
											'type'        => 'string',
											'description' => __( 'Nur pricing: hervorgehobene Preiszeile (z. B. "49 €/Monat").', 'wp-agent-companion' ),
										),
										'role'         => array(
											'type'        => 'string',
											'description' => __( 'Nur testimonials: Rolle/Firma unter dem Namen.', 'wp-agent-companion' ),
										),
										'featured'     => array(
											'type'        => 'boolean',
											'description' => __( 'Nur pricing: hebt die Karte farbig hervor.', 'wp-agent-companion' ),
										),
										'image_url'    => array(
											'type'        => 'string',
											'description' => __( 'logos: Bild-URL statt Text-Logo. features/pricing: optionales Karten-Bild oben in der Karte. gallery: Bild-URL einer Galerie-Kachel (Pflicht je Kachel).', 'wp-agent-companion' ),
										),
										'image_alt'    => array(
											'type'        => 'string',
											'description' => __( 'features/pricing/gallery: Alt-Text des Bildes (Fallback: title).', 'wp-agent-companion' ),
										),
										'cta_label'    => array( 'type' => 'string' ),
										'cta_url'      => array( 'type' => 'string' ),
										'button_label' => array( 'type' => 'string' ),
										'button_url'   => array( 'type' => 'string' ),
										'values'       => array(
											'type'        => 'array',
											'items'       => array( 'type' => 'string' ),
											'description' => __( 'Nur comparison: Zellwerte einer Tabellenzeile OHNE das Zeilen-Label (label ist die erste Spalte). Sollte columns.length - 1 Werte enthalten; fehlende Werte werden zu leeren Zellen, überzählige abgeschnitten.', 'wp-agent-companion' ),
										),
									),
								),
							),
						),
						'required'   => array( 'type' ),
					),
				),
			),
			'required'   => array( 'title', 'sections' ),
		),
		'output_schema'       => array(
			'type'       => 'object',
			'properties' => array(
				'post_id'        => array( 'type' => 'integer' ),
				'url'            => array( 'type' => 'string' ),
				'status'         => array( 'type' => 'string' ),
				'content_hash'   => array( 'type' => 'string' ),
				'block_count'    => array( 'type' => 'integer' ),
				'freeform_count' => array( 'type' => 'integer' ),
				'block_types'    => array(
					'type'  => 'array',
					'items' => array( 'type' => 'string' ),
				),
				'updated'        => array( 'type' => 'boolean' ),
			),
		),
		'execute_callback'    => 'wpagent_exec_upsert_pattern',
		'permission_callback' => 'wpagent_perm_edit_pages',
		'meta'                => array(
			'show_in_rest' => true,
			'annotations'  => array(
				'readonly'    => false,
				'destructive' => false,
				'idempotent'  => true,
			),
		),
	);
}

/**
 * @param mixed $input
 * @return array|WP_Error
 */
function wpagent_exec_upsert_pattern( $input ) {
	$input = is_array( $input ) ? $input : array();
	$title = trim( (string) ( $input['title'] ?? '' ) );

	if ( '' === $title ) {
		return new WP_Error( 'wpagent_missing_title', __( 'Titel fehlt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}

	$sections = $input['sections'] ?? null;
	if ( ! is_array( $sections ) || array() === $sections ) {
		return new WP_Error( 'wpagent_missing_sections', __( 'sections fehlt oder ist leer.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}

	// Idempotente Zuordnung und Berechtigungen werden vor der vergleichsweise
	// teuren Block-Erzeugung geprüft. post_id darf niemals einen Beitrag still
	// durch Setzen von post_type=page in eine Seite umwandeln.
	$post_id = (int) ( $input['post_id'] ?? 0 );
	$slug    = isset( $input['slug'] ) ? sanitize_title( (string) $input['slug'] ) : '';
	if ( $post_id <= 0 && '' !== $slug ) {
		$existing_by_slug = get_page_by_path( $slug, OBJECT, 'page' );
		if ( $existing_by_slug instanceof WP_Post ) {
			$post_id = (int) $existing_by_slug->ID;
		}
	}

	$existing_post = null;
	if ( $post_id > 0 ) {
		$existing_post = get_post( $post_id );
		if ( ! $existing_post ) {
			return new WP_Error( 'wpagent_no_post', __( 'Angegebene Seite nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		}
		if ( 'page' !== $existing_post->post_type ) {
			return new WP_Error( 'wpagent_not_page', __( 'upsert-pattern darf ausschließlich bestehende Seiten aktualisieren.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung für diese Seite.', 'wp-agent-companion' ), array( 'status' => 403 ) );
		}
	} elseif ( ! current_user_can( wpagent_post_type_create_cap( 'page' ) ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung, Seiten anzulegen.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( ! current_user_can( wpagent_post_type_edit_cap( 'page' ) ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung, Seiten zu bearbeiten.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}

	$status = (string) ( $input['status'] ?? 'draft' );
	if ( ! in_array( $status, array( 'draft', 'publish', 'pending', 'private' ), true ) ) {
		$status = 'draft';
	}
	$status_cap = wpagent_check_post_status_cap( 'page', $status );
	if ( is_wp_error( $status_cap ) ) {
		return $status_cap;
	}

	// Sektionsliste in valides Core-Block-Markup übersetzen. Jede Sektion liefert
	// genau einen vollbreiten Top-Level-core/group.
	$parts            = array();
	$features_index   = 0; // Für den abwechselnden Hintergrund der Feature-Bänder.
	$media_text_index = 0; // Für die abwechselnde Bildseite der media-text-Sektionen.
	foreach ( $sections as $index => $section ) {
		if ( ! is_array( $section ) ) {
			/* translators: %d: zero-based section index. */
			return new WP_Error( 'wpagent_bad_section', sprintf( __( 'Sektion %d ist kein Objekt.', 'wp-agent-companion' ), (int) $index ), array( 'status' => 400 ) );
		}
		$type = (string) ( $section['type'] ?? '' );
		switch ( $type ) {
			case 'hero':
				$parts[] = wpagent_pattern_build_hero( $section );
				break;
			case 'features':
				$parts[] = wpagent_pattern_build_features( $section, $features_index );
				$features_index++;
				break;
			case 'media-text':
				$parts[] = wpagent_pattern_build_media_text( $section, $media_text_index );
				$media_text_index++;
				break;
			case 'logos':
				$parts[] = wpagent_pattern_build_logos( $section );
				break;
			case 'cta':
				$parts[] = wpagent_pattern_build_cta( $section );
				break;
			case 'pricing':
				$parts[] = wpagent_pattern_build_pricing( $section );
				break;
			case 'testimonials':
				$parts[] = wpagent_pattern_build_testimonials( $section );
				break;
			case 'faq':
				$parts[] = wpagent_pattern_build_faq( $section );
				break;
			case 'stats':
				$parts[] = wpagent_pattern_build_stats( $section );
				break;
			case 'gallery':
				$parts[] = wpagent_pattern_build_gallery( $section );
				break;
			case 'steps':
				$parts[] = wpagent_pattern_build_steps( $section );
				break;
			case 'comparison':
				$parts[] = wpagent_pattern_build_comparison( $section );
				break;
			default:
				return new WP_Error(
					'wpagent_bad_section_type',
					/* translators: %s: submitted landing-page section type. */
					sprintf( __( 'Unbekannter Sektionstyp "%s" (erlaubt: hero, features, media-text, logos, cta, pricing, testimonials, faq, stats, gallery, steps, comparison).', 'wp-agent-companion' ), $type ),
					array( 'status' => 400 )
				);
		}
	}

	$content = implode( "\n\n", array_filter( $parts, static fn( $p ) => '' !== $p ) );

	// Selbst-Verifikation VOR dem Speichern: nur die explizite Core-Allowlist.
	$report    = wpagent_block_report( $content );
	$validated = wpagent_validate_generated_core_blocks( $content );
	if ( is_wp_error( $validated ) ) {
		return $validated;
	}

	$postarr = array(
		'post_title'   => $title,
		'post_content' => $content,
		'post_status'  => $status,
		'post_type'    => 'page',
	);
	if ( '' !== $slug ) {
		$postarr['post_name'] = $slug;
	}

	$updated       = false;
	$post_snapshot = $existing_post instanceof WP_Post ? wpagent_content_write_snapshot( $existing_post ) : null;
	if ( $post_id > 0 ) {
		$postarr['ID'] = $post_id;
		$res           = wp_update_post( wp_slash( $postarr ), true );
		$updated       = true;
	} else {
		$res = wp_insert_post( wp_slash( $postarr ), true );
	}

	if ( is_wp_error( $res ) ) {
		if ( null !== $post_snapshot ) {
			return wpagent_content_write_failure_after_compensation( $res, $post_id, $post_snapshot );
		}
		return $res;
	}
	$post_id = (int) $res;

	$fresh    = get_post( $post_id );
	$expected = array(
		'post_title'   => $title,
		'post_content' => $content,
		'post_status'  => $status,
		'post_type'    => 'page',
	);
	if ( '' !== $slug ) {
		$expected['post_name'] = $slug;
	}
	if ( ! $fresh instanceof WP_Post || ! wpagent_content_write_matches( $fresh, $expected ) ) {
		$mismatches = $fresh instanceof WP_Post ? wpagent_content_write_mismatches( $fresh, $expected ) : array( 'post_missing' );
		return wpagent_content_write_failure_after_compensation(
			new WP_Error( 'wpagent_post_write_unverified', __( 'WordPress hat die angeforderte Seite nicht exakt gespeichert.', 'wp-agent-companion' ), array( 'status' => 502, 'mismatched_fields' => $mismatches ) ),
			$post_id,
			$post_snapshot
		);
	}

	return array(
		'post_id'        => $post_id,
		'url'            => get_permalink( $post_id ),
		'status'         => get_post_status( $post_id ),
		'content_hash'   => hash( 'sha256', $content ),
		'block_count'    => $report['block_count'],
		'freeform_count' => $report['freeform_count'],
		'block_types'    => $report['block_types'],
		'updated'        => $updated,
	);
}
