<?php
/**
 * One form submission, whichever plugin collected it.
 *
 * @package TextTango
 */

declare( strict_types=1 );

namespace TextTango\Forms;

defined( 'ABSPATH' ) || exit;

/**
 * A submission, normalised away from the plugin that produced it (spec §21).
 *
 * Four form plugins hand their data over in four shapes — Contact Form 7 gives a
 * flat array keyed by field name, WPForms an array of arrays with `name` and
 * `value`, Gravity Forms an entry keyed by numeric field id, Fluent Forms a JSON
 * payload. Every one of them is turned into this before anything else in the
 * plugin sees it, so the sending, consent and template code has one shape to
 * understand rather than four.
 */
final class Submission {

	/**
	 * Which form plugin this came from.
	 */
	private string $provider;

	/**
	 * The form's id within that plugin.
	 */
	private string $form_id;

	/**
	 * The form's title, for the merchant to recognise.
	 */
	private string $title;

	/**
	 * Submitted values, keyed by the plugin's own field name.
	 *
	 * @var array<string, string>
	 */
	private array $fields;

	/**
	 * @param string                $provider Provider slug.
	 * @param string                $form_id  Form id within the provider.
	 * @param string                $title    Form title.
	 * @param array<string, string> $fields   Submitted values by field name.
	 */
	public function __construct( string $provider, string $form_id, string $title, array $fields ) {
		$this->provider = $provider;
		$this->form_id  = (string) $form_id;
		$this->title    = $title;
		$this->fields   = $fields;
	}

	/**
	 * How one form is named across every provider.
	 *
	 * Provider and id together, because form ids are only unique within the
	 * plugin that issued them — Contact Form 7's form 3 and Gravity Forms' form 3
	 * are different forms, and settings keyed on the id alone would have them
	 * share a configuration.
	 *
	 * @param string $provider Provider slug.
	 * @param string $form_id  Form id.
	 */
	public static function key_for( string $provider, string $form_id ): string {
		return $provider . ':' . $form_id;
	}

	/**
	 * This submission's form key.
	 */
	public function key(): string {
		return self::key_for( $this->provider, $this->form_id );
	}

	/**
	 * Provider slug.
	 */
	public function provider(): string {
		return $this->provider;
	}

	/**
	 * Form id within the provider.
	 */
	public function form_id(): string {
		return $this->form_id;
	}

	/**
	 * Form title.
	 */
	public function title(): string {
		return $this->title;
	}

	/**
	 * Every submitted value, by field name.
	 *
	 * @return array<string, string>
	 */
	public function fields(): array {
		return $this->fields;
	}

	/**
	 * One submitted value.
	 *
	 * @param string $field Field name.
	 */
	public function field( string $field ): string {
		return $this->fields[ $field ] ?? '';
	}

	/**
	 * Whether a field was submitted with something in it.
	 *
	 * A checkbox that was not ticked arrives either absent or empty depending on
	 * the plugin, and both mean the same thing.
	 *
	 * @param string $field Field name.
	 */
	public function ticked( string $field ): bool {
		return '' !== trim( $this->field( $field ) );
	}

	/**
	 * A field name as a merge tag.
	 *
	 * Form fields are named by whoever built the form — `your-name`, `tel-472`,
	 * `Email Address` — while {@see \TextTango\WooCommerce\Templates::render()}
	 * recognises `{[a-z_]+}` and **strips any tag it does not recognise**. A tag
	 * containing a dash therefore survives that strip and reaches the customer as
	 * the literal text `{your-name}`, which is the exact failure the renderer
	 * exists to prevent. So the name is flattened to what the renderer can see.
	 *
	 * @param string $field Field name.
	 */
	public static function tag_for( string $field ): string {
		$name = strtolower( (string) preg_replace( '/[^a-zA-Z0-9]+/', '_', $field ) );

		return '{' . trim( $name, '_' ) . '}';
	}

	/**
	 * Every submitted value as merge tags, plus the form's own.
	 *
	 * Two fields whose names flatten to the same tag — `your-name` and
	 * `your_name` — would collide. First one wins, deliberately and quietly: the
	 * alternative is refusing to send a message over a naming clash the merchant
	 * cannot see and probably did not make.
	 *
	 * @return array<string, string>
	 */
	public function tags(): array {
		$tags = array();

		foreach ( $this->fields as $field => $value ) {
			$tag = self::tag_for( $field );

			if ( '{}' === $tag || isset( $tags[ $tag ] ) ) {
				continue;
			}

			$tags[ $tag ] = $value;
		}

		$tags['{form_name}'] = $this->title;
		$tags['{site_name}'] = wp_specialchars_decode( (string) get_option( 'blogname' ), ENT_QUOTES );

		return $tags;
	}
}
