<?php
/**
 * Full data importer
 *
 * Applies decisions against a normalized ImportManifest produced by
 * ImportParser. Two phases:
 *
 * - `analyze_collisions()` — enriches each course/module/slide/template with
 *   an `existing_match` entry pointing to a title-matching target (or null).
 * - `execute()` — creates, updates, or skips items per the decisions map.
 *
 * Memory variables are never imported (archival-only section); the parser
 * still carries them for fidelity.
 *
 * @package SlideMind\Core
 * @since   0.6.0
 */

namespace SlideMind\Core;

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

class FullImporter {

	/** @var ConfigRepository */
	private $config_repository;

	/** @var TemplateManager */
	private $template_manager;

	public function __construct( ConfigRepository $config_repository, TemplateManager $template_manager ) {
		$this->config_repository = $config_repository;
		$this->template_manager  = $template_manager;
	}

	/**
	 * Enrich each titled section with `existing_match` keyed by post_id / slug.
	 *
	 * Match key for posts: lowercased trimmed title within the section.
	 * Match key for templates: slug first, fallback to title.
	 */
	public function analyze_collisions( ImportManifest $manifest ): ImportManifest {
		$this->enrich_with_post_match( $manifest->courses, $this->existing_titles_by_section( 'courses' ) );
		$this->enrich_with_post_match( $manifest->modules, $this->existing_titles_by_section( 'modules' ) );
		$this->enrich_with_post_match( $manifest->slides,  $this->existing_titles_by_section( 'slides' )  );

		$existing_templates = $this->existing_templates_by_slug_and_title();
		foreach ( $manifest->templates as &$tpl ) {
			$slug  = isset( $tpl['slug'] ) ? strtolower( trim( (string) $tpl['slug'] ) ) : '';
			$title = isset( $tpl['title'] ) ? strtolower( trim( (string) $tpl['title'] ) ) : '';
			$match = null;
			if ( $slug !== '' && isset( $existing_templates['by_slug'][ $slug ] ) ) {
				$match = $existing_templates['by_slug'][ $slug ];
			} elseif ( $title !== '' && isset( $existing_templates['by_title'][ $title ] ) ) {
				$match = $existing_templates['by_title'][ $title ];
			}
			$tpl['existing_match'] = $match;
		}
		unset( $tpl );

		return $manifest;
	}

	/**
	 * @param array<int, array<string, mixed>> $items    Reference — updated in place.
	 * @param array<string, array<string, mixed>> $titles Lowercased-trimmed-title => target summary.
	 */
	private function enrich_with_post_match( array &$items, array $titles ): void {
		foreach ( $items as &$item ) {
			$title = strtolower( trim( (string) ( $item['title'] ?? '' ) ) );
			$item['existing_match'] = ( $title !== '' && isset( $titles[ $title ] ) ) ? $titles[ $title ] : null;
		}
		unset( $item );
	}

	/**
	 * @return array<string, array{post_id:int,title:string,status:string}>
	 */
	private function existing_titles_by_section( string $section ): array {
		switch ( $section ) {
			case 'courses':
				$items = $this->config_repository->get_courses( 'any' );
				break;
			case 'modules':
				$items = $this->config_repository->get_all_modules( 'any' );
				break;
			case 'slides':
				$items = $this->config_repository->get_all_slides( 'any' );
				break;
			default:
				return [];
		}

		$index = [];
		foreach ( $items as $item ) {
			$title = strtolower( trim( (string) ( $item['title'] ?? '' ) ) );
			if ( $title === '' ) {
				continue;
			}
			// First match wins on duplicate titles in the DB.
			if ( ! isset( $index[ $title ] ) ) {
				$index[ $title ] = [
					'post_id' => (int) $item['id'],
					'title'   => (string) $item['title'],
					'status'  => (string) ( $item['status'] ?? 'publish' ),
				];
			}
		}
		return $index;
	}

	/**
	 * @return array{by_slug:array<string,array>,by_title:array<string,array>}
	 */
	private function existing_templates_by_slug_and_title(): array {
		$by_slug  = [];
		$by_title = [];
		foreach ( $this->template_manager->get_all() as $tpl ) {
			if ( ! empty( $tpl['is_default'] ) ) {
				continue;
			}
			$summary = [
				'slug'  => (string) ( $tpl['slug'] ?? '' ),
				'title' => (string) ( $tpl['title'] ?? '' ),
			];
			$slug_key  = strtolower( trim( (string) ( $tpl['slug'] ?? '' ) ) );
			$title_key = strtolower( trim( (string) ( $tpl['title'] ?? '' ) ) );
			if ( $slug_key !== '' ) {
				$by_slug[ $slug_key ] = $summary;
			}
			if ( $title_key !== '' ) {
				$by_title[ $title_key ] = $summary;
			}
		}
		return [ 'by_slug' => $by_slug, 'by_title' => $by_title ];
	}

	/**
	 * Execute the import.
	 *
	 * @param ImportManifest $manifest
	 * @param array<string, mixed> $decisions
	 * @return array<string, mixed> ImportReport
	 */
	public function execute( ImportManifest $manifest, array $decisions ): array {
		$report = [
			'created'                     => [],
			'overwritten'                 => [],
			'skipped'                     => [],
			'holders'                     => [ 'courses' => [], 'modules' => [] ],
			'memory_variables_skipped'    => count( $manifest->memory_variables ),
			'memory_definitions_imported' => 0,
			'settings_applied'            => false,
			'errors'                      => [],
		];

		$ref_to_post_id = [];
		if ( isset( $decisions['seeded_refs'] ) && is_array( $decisions['seeded_refs'] ) ) {
			foreach ( $decisions['seeded_refs'] as $ref => $post_id ) {
				if ( is_string( $ref ) && (int) $post_id > 0 ) {
					$ref_to_post_id[ $ref ] = (int) $post_id;
				}
			}
		}
		$course_decisions = isset( $decisions['decisions']['courses'] ) && is_array( $decisions['decisions']['courses'] )
			? $decisions['decisions']['courses']
			: [];

		$this->apply_courses( $manifest->courses, $course_decisions, $ref_to_post_id, $report );

		$module_decisions = isset( $decisions['decisions']['modules'] ) && is_array( $decisions['decisions']['modules'] )
			? $decisions['decisions']['modules']
			: [];

		$holder_course_id = 0; // Lazy — created on first orphan module
		$this->apply_modules( $manifest->modules, $module_decisions, $ref_to_post_id, $holder_course_id, $report );

		$slide_decisions = isset( $decisions['decisions']['slides'] ) && is_array( $decisions['decisions']['slides'] )
			? $decisions['decisions']['slides']
			: [];

		$holder_module_id = 0;
		$this->apply_slides( $manifest->slides, $slide_decisions, $ref_to_post_id, $holder_course_id, $holder_module_id, $report );

		$template_decisions = isset( $decisions['decisions']['templates'] ) && is_array( $decisions['decisions']['templates'] )
			? $decisions['decisions']['templates']
			: [];
		$this->apply_templates( $manifest->templates, $template_decisions, $report );

		$selected_memdef_ids = isset( $decisions['selected_memory_definitions'] ) && is_array( $decisions['selected_memory_definitions'] )
			? array_map( 'strval', $decisions['selected_memory_definitions'] )
			: [];
		$report['memory_definitions_imported'] = $this->apply_memory_definitions( $manifest->memory_definitions, $selected_memdef_ids );

		if ( ! empty( $decisions['apply_settings'] ) && ! empty( $manifest->settings ) ) {
			$report['settings_applied'] = $this->apply_settings( $manifest->settings );
		}

		return $report;
	}

	/**
	 * @param array<int, array<string,mixed>>  $courses
	 * @param array<string, string>            $decisions  file_ref => action
	 * @param array<string, int>               $ref_to_post_id  Reference — updated.
	 * @param array<string, mixed>             $report          Reference — updated.
	 */
	private function apply_courses( array $courses, array $decisions, array &$ref_to_post_id, array &$report ): void {
		foreach ( $courses as $course ) {
			$ref    = (string) $course['file_ref'];
			$action = isset( $decisions[ $ref ] ) ? (string) $decisions[ $ref ] : null;
			if ( $action === null || $action === 'skip' ) {
				// Unselected or explicitly skipped. Record if it had a match, else silent.
				if ( ! empty( $course['existing_match'] ) ) {
					$report['skipped'][] = [ 'type' => 'course', 'title' => (string) $course['title'], 'reason' => 'user_skip' ];
				}
				continue;
			}

			if ( $action === 'overwrite' && ! empty( $course['existing_match']['post_id'] ) ) {
				$post_id = (int) $course['existing_match']['post_id'];
				$result  = $this->config_repository->update_course( $post_id, [
					'title'      => (string) $course['title'],
					'theme'      => (string) $course['theme'],
					'objectives' => (string) $course['objectives'],
					'content'    => (string) $course['content'],
				] );
				if ( is_wp_error( $result ) ) {
					$report['errors'][] = $result->get_error_message();
					continue;
				}
				$ref_to_post_id[ $ref ]    = $post_id;
				$report['overwritten'][]   = [ 'type' => 'course', 'post_id' => $post_id, 'title' => (string) $course['title'] ];
				continue;
			}

			// action === 'new' or 'duplicate' → create fresh
			$new_id = $this->config_repository->create_course( [
				'title'      => (string) $course['title'],
				'theme'      => (string) $course['theme'],
				'objectives' => (string) $course['objectives'],
				'content'    => (string) $course['content'],
				'status'     => 'publish',
			] );
			if ( is_wp_error( $new_id ) ) {
				$report['errors'][] = $new_id->get_error_message();
				continue;
			}
			$ref_to_post_id[ $ref ] = (int) $new_id;
			$report['created'][]    = [ 'type' => 'course', 'post_id' => (int) $new_id, 'title' => (string) $course['title'] ];
		}
	}

	/**
	 * @param array<int, array<string,mixed>>  $modules
	 * @param array<string, string>            $decisions
	 * @param array<string, int>               $ref_to_post_id
	 * @param int                              $holder_course_id  0 until first orphan forces creation.
	 * @param array<string, mixed>             $report
	 */
	private function apply_modules( array $modules, array $decisions, array &$ref_to_post_id, int &$holder_course_id, array &$report ): void {
		foreach ( $modules as $module ) {
			$ref    = (string) $module['file_ref'];
			$action = isset( $decisions[ $ref ] ) ? (string) $decisions[ $ref ] : null;
			if ( $action === null || $action === 'skip' ) {
				if ( ! empty( $module['existing_match'] ) ) {
					$report['skipped'][] = [ 'type' => 'module', 'title' => (string) $module['title'], 'reason' => 'user_skip' ];
				}
				continue;
			}

			$is_overwrite = $action === 'overwrite' && ! empty( $module['existing_match']['post_id'] );

			// Resolve parent course from the manifest if its course was imported in the same run.
			$parent_course_id = 0;
			$parent_ref       = isset( $module['parent_course_ref'] ) ? $module['parent_course_ref'] : null;
			if ( is_string( $parent_ref ) && isset( $ref_to_post_id[ $parent_ref ] ) ) {
				$parent_course_id = (int) $ref_to_post_id[ $parent_ref ];
			} elseif ( ! $is_overwrite ) {
				// new/duplicate without a resolved parent → fall back to a holder course.
				// overwrite without a resolved parent → preserve the existing parent (handled below).
				if ( $holder_course_id === 0 ) {
					$holder_course_id = $this->create_holder_course( $report );
					if ( $holder_course_id === 0 ) {
						continue; // Error already logged.
					}
				}
				$parent_course_id = $holder_course_id;
			}

			if ( $is_overwrite ) {
				$post_id = (int) $module['existing_match']['post_id'];
				$payload = [
					'title'   => (string) $module['title'],
					'subject' => (string) $module['subject'],
					'context' => (string) $module['context'],
				];
				if ( $parent_course_id > 0 ) {
					$payload['parent_course_id'] = $parent_course_id;
				}
				$result = $this->config_repository->update_module( $post_id, $payload );
				if ( is_wp_error( $result ) ) {
					$report['errors'][] = $result->get_error_message();
					continue;
				}
				$ref_to_post_id[ $ref ]   = $post_id;
				$report['overwritten'][]  = [ 'type' => 'module', 'post_id' => $post_id, 'title' => (string) $module['title'] ];
				continue;
			}

			$new_id = $this->config_repository->create_module( [
				'title'            => (string) $module['title'],
				'subject'          => (string) $module['subject'],
				'context'          => (string) $module['context'],
				'parent_course_id' => $parent_course_id,
				'status'           => 'publish',
			] );
			if ( is_wp_error( $new_id ) ) {
				$report['errors'][] = $new_id->get_error_message();
				continue;
			}
			$ref_to_post_id[ $ref ]   = (int) $new_id;
			$report['created'][]      = [ 'type' => 'module', 'post_id' => (int) $new_id, 'title' => (string) $module['title'] ];
		}
	}

	private function create_holder_course( array &$report ): int {
		$title = sprintf(
			/* translators: %s: current date */
			__( 'Imported (orphan parents) — %s', 'slidemind' ),
			wp_date( get_option( 'date_format' ) )
		);
		$id = $this->config_repository->create_course( [
			'title'      => $title,
			'theme'      => '',
			'objectives' => '',
			'content'    => '',
			'status'     => 'publish',
		] );
		if ( is_wp_error( $id ) ) {
			$report['errors'][] = $id->get_error_message();
			return 0;
		}
		$report['holders']['courses'][] = [ 'post_id' => (int) $id, 'title' => $title ];
		return (int) $id;
	}

	private function apply_slides(
		array $slides,
		array $decisions,
		array &$ref_to_post_id,
		int &$holder_course_id,
		int &$holder_module_id,
		array &$report
	): void {
		foreach ( $slides as $slide ) {
			$ref    = (string) $slide['file_ref'];
			$action = isset( $decisions[ $ref ] ) ? (string) $decisions[ $ref ] : null;
			if ( $action === null || $action === 'skip' ) {
				if ( ! empty( $slide['existing_match'] ) ) {
					$report['skipped'][] = [ 'type' => 'slide', 'title' => (string) ( $slide['title'] ?? '' ), 'reason' => 'user_skip' ];
				}
				continue;
			}

			$is_overwrite = $action === 'overwrite' && ! empty( $slide['existing_match']['post_id'] );

			$parent_module_id = 0;
			$parent_course_id = 0;

			$mod_ref = $slide['parent_module_ref'] ?? null;
			if ( is_string( $mod_ref ) && isset( $ref_to_post_id[ $mod_ref ] ) ) {
				$parent_module_id = (int) $ref_to_post_id[ $mod_ref ];
			}

			$course_ref = $slide['parent_course_ref'] ?? null;
			if ( is_string( $course_ref ) && isset( $ref_to_post_id[ $course_ref ] ) ) {
				$parent_course_id = (int) $ref_to_post_id[ $course_ref ];
			}

			if ( $parent_module_id === 0 && ! $is_overwrite ) {
				// new/duplicate without a resolved parent → fall back to a holder module.
				// overwrite without a resolved parent → preserve the existing parent (handled below).
				if ( $holder_course_id === 0 ) {
					$holder_course_id = $this->create_holder_course( $report );
					if ( $holder_course_id === 0 ) {
						continue;
					}
				}
				if ( $holder_module_id === 0 ) {
					$holder_module_id = $this->create_holder_module( $holder_course_id, $report );
					if ( $holder_module_id === 0 ) {
						continue;
					}
				}
				$parent_module_id = $holder_module_id;
				$parent_course_id = $holder_course_id;
			}

			$payload = $this->slide_payload_for_write( $slide, $parent_module_id, $parent_course_id );

			if ( $is_overwrite && $parent_module_id === 0 ) {
				// Preserve existing parent meta when the manifest did not resolve one.
				unset( $payload['parent_module_id'], $payload['parent_course_id'] );
			}

			if ( $is_overwrite ) {
				$post_id = (int) $slide['existing_match']['post_id'];
				$result  = $this->config_repository->update_slide( $post_id, $payload );
				if ( is_wp_error( $result ) ) {
					$report['errors'][] = $result->get_error_message();
					continue;
				}
				$ref_to_post_id[ $ref ]  = $post_id;
				$report['overwritten'][] = [ 'type' => 'slide', 'post_id' => $post_id, 'title' => (string) ( $slide['title'] ?? '' ) ];
				continue;
			}

			$payload['status'] = 'draft';
			$new_id = $this->config_repository->create_slide( $payload );
			if ( is_wp_error( $new_id ) ) {
				$report['errors'][] = $new_id->get_error_message();
				continue;
			}
			$ref_to_post_id[ $ref ] = (int) $new_id;
			$report['created'][]    = [ 'type' => 'slide', 'post_id' => (int) $new_id, 'title' => (string) ( $slide['title'] ?? '' ) ];
		}
	}

	private function create_holder_module( int $course_id, array &$report ): int {
		$title = __( 'Imported slides', 'slidemind' );
		$id    = $this->config_repository->create_module( [
			'title'            => $title,
			'subject'          => '',
			'context'          => '',
			'parent_course_id' => $course_id,
			'status'           => 'publish',
		] );
		if ( is_wp_error( $id ) ) {
			$report['errors'][] = $id->get_error_message();
			return 0;
		}
		$report['holders']['modules'][] = [ 'post_id' => (int) $id, 'title' => $title ];
		return (int) $id;
	}

	/**
	 * Project a slide DTO to the shape accepted by ConfigRepository write methods.
	 * Never writes api_key.
	 */
	private function slide_payload_for_write( array $slide, int $module_id, int $course_id ): array {
		$slide_number = isset( $slide['slide_number'] ) && $slide['slide_number'] !== null && $slide['slide_number'] !== ''
			? (int) $slide['slide_number']
			: '';

		return [
			'title'                 => (string) ( $slide['title'] ?? '' ),
			'parent_course_id'      => $course_id,
			'parent_module_id'      => $module_id,
			'slide_number'          => $slide_number,
			'model_id'              => (string) ( $slide['model_id'] ?? '' ),
			'system_prompt'         => (string) ( $slide['system_prompt'] ?? '' ),
			'temperature'           => isset( $slide['temperature'] ) ? (float) $slide['temperature'] : 0.7,
			'max_tokens'            => isset( $slide['max_tokens'] ) ? (int) $slide['max_tokens'] : 1024,
			'variables'             => $slide['variables'] ?? [],
			'override_course'       => $slide['override_course'] ?? [],
			'override_module'       => $slide['override_module'] ?? [],
			'output_variables'      => $slide['output_variables'] ?? [],
			'notes'                 => (string) ( $slide['notes'] ?? '' ),
			'widget_mode'           => (string) ( $slide['widget_mode'] ?? 'qa' ),
			'max_turns'             => isset( $slide['max_turns'] ) ? (int) $slide['max_turns'] : 0,
			'closing_message'       => (string) ( $slide['closing_message'] ?? '' ),
			'opening_message'       => (string) ( $slide['opening_message'] ?? '' ),
			'graceful_close'        => (string) ( $slide['graceful_close'] ?? '0' ),
			'graceful_close_prompt' => (string) ( $slide['graceful_close_prompt'] ?? '' ),
		];
	}

	/**
	 * Merge file settings into current settings. Strips API keys defensively
	 * even though FullExporter already excludes them at export time.
	 */
	private function apply_settings( array $file_settings ): bool {
		$current = get_option( 'slidemind_settings', [] );
		if ( ! is_array( $current ) ) {
			$current = [];
		}

		foreach ( array_keys( $file_settings ) as $key ) {
			if ( preg_match( '/api[_-]?key|secret|token/i', (string) $key ) ) {
				unset( $file_settings[ $key ] );
			}
		}

		unset( $file_settings['cors_test_mode'], $file_settings['cors_test_mode_until'] );

		// Merge: file values override existing for keys present; other keys untouched.
		$merged = array_merge( $current, $file_settings );

		// `global_provider` is its own option; apply separately if provided.
		if ( isset( $merged['global_provider'] ) ) {
			update_option( 'slidemind_global_provider', sanitize_key( (string) $merged['global_provider'] ) );
			unset( $merged['global_provider'] );
		}

		update_option( 'slidemind_settings', $merged );
		return true;
	}

	/**
	 * Merge selected memory definitions, keyed by `id`. Skip duplicates.
	 *
	 * @param array<int, array<string,mixed>> $definitions
	 * @param array<int, string>              $selected_ids  Whitelist; empty = import all.
	 * @return int Number of newly added definitions.
	 */
	private function apply_memory_definitions( array $definitions, array $selected_ids ): int {
		if ( empty( $definitions ) ) {
			return 0;
		}
		$existing = get_option( 'slidemind_memory_definitions', [] );
		if ( ! is_array( $existing ) ) {
			$existing = [];
		}
		$existing_index = [];
		foreach ( $existing as $def ) {
			if ( isset( $def['id'] ) ) {
				$existing_index[ (string) $def['id'] ] = true;
			}
		}

		$whitelist = ! empty( $selected_ids ) ? array_flip( $selected_ids ) : null;

		$added = 0;
		foreach ( $definitions as $def ) {
			if ( ! is_array( $def ) || ! isset( $def['id'] ) ) {
				continue;
			}
			$id = (string) $def['id'];
			if ( $whitelist !== null && ! isset( $whitelist[ $id ] ) ) {
				continue;
			}
			if ( isset( $existing_index[ $id ] ) ) {
				continue;
			}
			$existing[]              = $def;
			$existing_index[ $id ]   = true;
			$added++;
		}

		if ( $added > 0 ) {
			update_option( 'slidemind_memory_definitions', $existing );
		}
		return $added;
	}

	/**
	 * Validate Pro-plan gates against the requested import.
	 *
	 * Free is read-only on courses and modules (no UI to create them) — any
	 * import that touches those types must require the Dialogue/Pro plan,
	 * including 'overwrite' actions.
	 *
	 * @return bool|\WP_Error Returns true on success, WP_Error when blocked.
	 */
	public function validate_limits( ImportManifest $manifest, array $decisions ): bool|\WP_Error {
		// Free is read-only on courses and modules (no UI to create them).
		// has_dialogue() already returns false in the free build (its body
		// sits inside the Freemius premium-only block and is stripped), so
		// this gate fires in both builds. The gate itself MUST stay outside
		// any premium-only block — wrapping it would strip it from free.
		if ( ! FeatureGate::has_dialogue() ) {
			$mutating = function ( $action ) {
				return in_array( $action, [ 'new', 'duplicate', 'overwrite' ], true );
			};

			$course_decisions = $decisions['decisions']['courses'] ?? [];
			$module_decisions = $decisions['decisions']['modules'] ?? [];

			foreach ( $manifest->courses as $c ) {
				if ( $mutating( $course_decisions[ $c['file_ref'] ] ?? null ) ) {
					return new \WP_Error( 'content_limit', __( 'This import contains courses, which require the Pro or Dialogue plan.', 'slidemind' ), [
						'content_limit' => true,
						'upgrade_url'   => FeatureGate::get_upgrade_url(),
					] );
				}
			}
			foreach ( $manifest->modules as $m ) {
				if ( $mutating( $module_decisions[ $m['file_ref'] ] ?? null ) ) {
					return new \WP_Error( 'content_limit', __( 'This import contains modules, which require the Pro or Dialogue plan.', 'slidemind' ), [
						'content_limit' => true,
						'upgrade_url'   => FeatureGate::get_upgrade_url(),
					] );
				}
			}
		}

		return true;
	}

	private function apply_templates( array $templates, array $decisions, array &$report ): void {
		foreach ( $templates as $tpl ) {
			$ref    = (string) $tpl['file_ref'];
			$action = isset( $decisions[ $ref ] ) ? (string) $decisions[ $ref ] : null;
			if ( $action === null || $action === 'skip' ) {
				if ( ! empty( $tpl['existing_match'] ) ) {
					$report['skipped'][] = [ 'type' => 'template', 'title' => (string) ( $tpl['title'] ?? '' ), 'reason' => 'user_skip' ];
				}
				continue;
			}

			$payload = [
				'title'            => (string) ( $tpl['title'] ?? '' ),
				'slug'             => (string) ( $tpl['slug'] ?? '' ),
				'description'      => (string) ( $tpl['description'] ?? '' ),
				'icon'             => (string) ( $tpl['icon'] ?? '' ),
				'system_prompt'    => (string) ( $tpl['system_prompt'] ?? '' ),
				'temperature'      => isset( $tpl['temperature'] ) ? (float) $tpl['temperature'] : 0.7,
				'max_tokens'       => isset( $tpl['max_tokens'] ) ? (int) $tpl['max_tokens'] : 1024,
				'variables'        => $tpl['variables'] ?? [],
				'widget_mode'      => (string) ( $tpl['widget_mode'] ?? 'chat' ),
				'max_turns'        => isset( $tpl['max_turns'] ) ? (int) $tpl['max_turns'] : 0,
				'closing_message'  => (string) ( $tpl['closing_message'] ?? '' ),
				'opening_message'  => (string) ( $tpl['opening_message'] ?? '' ),
				'graceful_close'   => (bool) ( $tpl['graceful_close'] ?? false ),
				'output_variables' => $tpl['output_variables'] ?? [],
			];

			if ( $action === 'overwrite' && ! empty( $tpl['existing_match']['slug'] ) ) {
				$result = $this->template_manager->update_by_slug( (string) $tpl['existing_match']['slug'], $payload );
				if ( is_wp_error( $result ) ) {
					$report['errors'][] = $result->get_error_message();
					continue;
				}
				$report['overwritten'][] = [ 'type' => 'template', 'slug' => (string) $tpl['existing_match']['slug'], 'title' => $payload['title'] ];
				continue;
			}

			$result = $this->template_manager->create_custom( $payload );
			if ( is_wp_error( $result ) ) {
				$report['errors'][] = $result->get_error_message();
				continue;
			}
			$report['created'][] = [ 'type' => 'template', 'slug' => (string) $payload['slug'], 'title' => $payload['title'] ];
		}
	}
}
