<?php
/**
 * Template persistence model.
 *
 * @package EntryFoundry
 */

namespace EntryFoundry\Core\Models\Template;

use RuntimeException;

/**
 * Stores validated template JSON on the template post type.
 */
final class TemplateRepository {

	/** WordPress post type used for EntryFoundry templates. */
	public const POST_TYPE = 'entryfoundry_tpl';

	/**
	 * Template JSON post meta key.
	 */
	public const META_KEY = '_entryfoundry_template_json';

	/**
	 * Template validator.
	 *
	 * @var TemplateValidator
	 */
	private $validator;

	/**
	 * Constructor.
	 *
	 * @param TemplateValidator $validator Template validator.
	 */
	public function __construct( TemplateValidator $validator ) {
		$this->validator = $validator;
	}

	/**
	 * Load one template.
	 *
	 * @param int $template_id Template post ID.
	 * @return array{id:int, title:string, template:array<string, mixed>}|null
	 */
	public function get( int $template_id ): ?array {
		$post = get_post( $template_id );

		if ( ! $post || self::POST_TYPE !== $post->post_type ) {
			return null;
		}

		$json = get_post_meta( $template_id, self::META_KEY, true );
		if ( ! is_string( $json ) || '' === $json ) {
			return null;
		}

		return array(
			'id'       => (int) $post->ID,
			'title'    => (string) $post->post_title,
			'template' => $this->validator->decode( $json ),
		);
	}

	/**
	 * Create a template.
	 *
	 * @param array<string, mixed> $template Template data.
	 * @return array{id:int, title:string, template:array<string, mixed>}
	 * @throws RuntimeException When WordPress cannot create the post.
	 */
	public function create( array $template ): array {
		$template = $this->validator->validate( $template );
		$post_id  = wp_insert_post(
			array(
				'post_type'   => self::POST_TYPE,
				'post_status' => 'publish',
				'post_title'  => sanitize_text_field( $template['name'] ),
			),
			true
		);

		if ( is_wp_error( $post_id ) ) {
			throw new RuntimeException( 'WordPress could not create the template post.' );
		}

		$this->write_json( (int) $post_id, $template );

		return $this->get( (int) $post_id );
	}

	/**
	 * Find the active template bound to a Formidable form.
	 *
	 * @param int $form_id Formidable form ID.
	 * @return array{id:int,title:string,template:array<string,mixed>}|null
	 */
	public function find_active_for_form( int $form_id ): ?array {
		foreach ( $this->get_active_ids() as $template_id ) {
			$template = $this->get( $template_id );
			if ( $template && (int) ( $template['template']['source']['form_id'] ?? 0 ) === $form_id ) {
				return $template;
			}
		}
		return null;
	}

	/**
	 * Get active template IDs.
	 *
	 * @return int[] Active template IDs.
	 */
	private function get_active_ids(): array {
		$ids = get_posts(
			array(
				'post_type'      => self::POST_TYPE,
				'post_status'    => 'publish',
				'fields'         => 'ids',
				'posts_per_page' => -1,
				'no_found_rows'  => true,
			)
		);
		return array_map( 'intval', is_array( $ids ) ? $ids : array() );
	}

	/**
	 * Update a template.
	 *
	 * @param int                  $template_id Template post ID.
	 * @param array<string, mixed> $template    Template data.
	 * @return array{id:int, title:string, template:array<string, mixed>}|null
	 * @throws RuntimeException When WordPress cannot update the post.
	 */
	public function update( int $template_id, array $template ): ?array {
		if ( null === $this->get( $template_id ) ) {
			return null;
		}

		$template = $this->validator->validate( $template );
		$result   = wp_update_post(
			array(
				'ID'         => $template_id,
				'post_title' => sanitize_text_field( $template['name'] ),
			),
			true
		);

		if ( is_wp_error( $result ) ) {
			throw new RuntimeException( 'WordPress could not update the template post.' );
		}

		$this->write_json( $template_id, $template );

		return $this->get( $template_id );
	}

	/**
	 * Write canonical JSON to post meta.
	 *
	 * @param int                  $template_id Template post ID.
	 * @param array<string, mixed> $template    Validated template.
	 * @return void
	 * @throws RuntimeException When JSON encoding fails.
	 */
	private function write_json( int $template_id, array $template ): void {
		$json = wp_json_encode( $template, JSON_UNESCAPED_SLASHES );
		if ( ! is_string( $json ) ) {
			throw new RuntimeException( 'Template JSON could not be encoded.' );
		}

		update_post_meta( $template_id, self::META_KEY, wp_slash( $json ) );
	}
}
