<?php
/**
 * Auto-reply form provider registry.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\AutoReplies;

use SUBIN\Forms\FormFieldRepository;
use SUBIN\Forms\FormRepository;

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

/**
 * Provides form and visitor-email-field choices for auto-reply rules.
 */
class AutoReplyFormProviderRegistry {
	/**
	 * Form repository.
	 *
	 * @var FormRepository
	 */
	private $forms;

	/**
	 * Field repository.
	 *
	 * @var FormFieldRepository
	 */
	private $fields;

	/**
	 * Constructor.
	 *
	 * @param FormRepository|null      $forms Forms.
	 * @param FormFieldRepository|null $fields Fields.
	 */
	public function __construct( ?FormRepository $forms = null, ?FormFieldRepository $fields = null ) {
		$this->forms  = $forms ?: new FormRepository();
		$this->fields = $fields ?: new FormFieldRepository();
	}

	/**
	 * Get available providers.
	 *
	 * @return array<int,array<string,mixed>>
	 */
	public function providers() {
		$providers = array();
		foreach ( $this->provider_labels() as $key => $label ) {
			$has_forms = AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM === $key || ! empty( $this->list_forms( $key ) );
			$providers[] = array(
				'key'       => $key,
				'label'     => $label,
				'available' => AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM === $key || $this->is_provider_detected( $key ),
				'forms'     => $has_forms,
			);
		}

		return $providers;
	}

	/**
	 * Get provider label.
	 *
	 * @param string $provider Provider key.
	 * @return string
	 */
	public function provider_label( $provider ) {
		$labels   = $this->provider_labels();
		$provider = sanitize_key( (string) $provider );

		return $labels[ $provider ] ?? __( 'Unknown provider', 'submission-inbox' );
	}

	/**
	 * List forms for a provider.
	 *
	 * @param string $provider Provider key.
	 * @return array<int,array<string,string>>
	 */
	public function list_forms( $provider ) {
		$provider = sanitize_key( (string) $provider );
		if ( AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM !== $provider ) {
			return $this->list_external_forms( $provider );
		}

		$forms = $this->forms->get_paginated(
			array(
				'per_page' => 1000,
				'offset'   => 0,
			)
		);

		$choices = array();
		foreach ( $forms as $form ) {
			$form_id = absint( $form['id'] ?? 0 );
			if ( $form_id <= 0 ) {
				continue;
			}

			$choices[] = array(
				'id'    => (string) $form_id,
				'label' => sanitize_text_field( (string) ( $form['title'] ?? '' ) ),
			);
		}

		return $choices;
	}

	/**
	 * Get one form choice.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @return array<string,string>|null
	 */
	public function get_form( $provider, $form_id ) {
		$provider = sanitize_key( (string) $provider );
		$form_id  = sanitize_text_field( (string) $form_id );
		if ( '' === $form_id ) {
			return null;
		}

		if ( AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM !== $provider ) {
			foreach ( $this->list_external_forms( $provider ) as $form ) {
				if ( $form_id === (string) $form['id'] ) {
					return $form;
				}
			}

			return null;
		}

		$form = $this->forms->get( absint( $form_id ) );
		if ( ! $form ) {
			return null;
		}

		return array(
			'id'    => (string) absint( $form['id'] ?? 0 ),
			'label' => sanitize_text_field( (string) ( $form['title'] ?? '' ) ),
		);
	}

	/**
	 * List visitor email fields for a form.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	public function list_email_fields( $provider, $form_id ) {
		$provider = sanitize_key( (string) $provider );
		if ( AutoReplyRuleRepository::PROVIDER_SUBMISSION_INBOX_FORM !== $provider ) {
			return $this->list_external_email_fields( $provider, $form_id );
		}

		$choices = array();
		foreach ( $this->fields->get_by_form_id( absint( $form_id ) ) as $field ) {
			if ( 'email' !== sanitize_key( $field['field_type'] ?? '' ) ) {
				continue;
			}

			$key = sanitize_key( (string) ( $field['field_key'] ?? '' ) );
			if ( '' === $key ) {
				continue;
			}

			$choices[] = array(
				'key'   => $key,
				'label' => sanitize_text_field( (string) ( $field['field_label'] ?: $key ) ),
			);
		}

		return $choices;
	}

	/**
	 * Validate a selected email field.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @param string $field_key Field key.
	 * @return array<string,string>|null
	 */
	public function get_email_field( $provider, $form_id, $field_key ) {
		$field_key = sanitize_key( (string) $field_key );
		foreach ( $this->list_email_fields( $provider, $form_id ) as $field ) {
			if ( $field_key === $field['key'] ) {
				return $field;
			}
		}

		return null;
	}

	/**
	 * Provider labels.
	 *
	 * @return array<string,string>
	 */
	private function provider_labels() {
		return array(
			'submission_inbox_form' => __( 'Submission Inbox Forms', 'submission-inbox' ),
			'contact_form_7'    => __( 'Contact Form 7', 'submission-inbox' ),
			'wpforms'           => __( 'WPForms', 'submission-inbox' ),
			'fluent_forms'      => __( 'Fluent Forms', 'submission-inbox' ),
			'forminator'        => __( 'Forminator', 'submission-inbox' ),
			'ninja_forms'       => __( 'Ninja Forms', 'submission-inbox' ),
		);
	}

	/**
	 * Detect installed third-party providers without hard dependencies.
	 *
	 * @param string $provider Provider key.
	 * @return bool
	 */
	private function is_provider_detected( $provider ) {
		switch ( sanitize_key( (string) $provider ) ) {
			case 'contact_form_7':
				return defined( 'WPCF7_VERSION' ) || class_exists( 'WPCF7_ContactForm' );
			case 'wpforms':
				return function_exists( 'wpforms' ) || class_exists( 'WPForms' );
			case 'fluent_forms':
				return defined( 'FLUENTFORM' ) || function_exists( 'wpFluentForm' );
			case 'forminator':
				return defined( 'FORMINATOR_VERSION' ) || class_exists( 'Forminator' );
			case 'ninja_forms':
				return function_exists( 'Ninja_Forms' ) || class_exists( 'Ninja_Forms' );
		}

		return false;
	}

	/**
	 * List external provider forms when safe metadata is available.
	 *
	 * @param string $provider Provider key.
	 * @return array<int,array<string,string>>
	 */
	private function list_external_forms( $provider ) {
		switch ( sanitize_key( (string) $provider ) ) {
			case 'contact_form_7':
				return $this->list_cf7_forms();
			case 'wpforms':
				return $this->list_post_type_forms( 'wpforms' );
			case 'fluent_forms':
				return $this->list_fluent_forms();
			case 'forminator':
				return $this->list_post_type_forms( 'forminator_forms' );
			case 'ninja_forms':
				return $this->list_ninja_forms();
		}

		return array();
	}

	/**
	 * List external provider email fields.
	 *
	 * @param string $provider Provider key.
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_external_email_fields( $provider, $form_id ) {
		switch ( sanitize_key( (string) $provider ) ) {
			case 'contact_form_7':
				return $this->list_cf7_email_fields( $form_id );
			case 'wpforms':
				return $this->list_wpforms_email_fields( $form_id );
			case 'fluent_forms':
				return $this->list_fluent_email_fields( $form_id );
			case 'forminator':
				return $this->list_forminator_email_fields( $form_id );
			case 'ninja_forms':
				return $this->list_ninja_email_fields( $form_id );
		}

		return array();
	}

	/**
	 * List forms stored as posts.
	 *
	 * @param string $post_type Post type.
	 * @return array<int,array<string,string>>
	 */
	private function list_post_type_forms( $post_type ) {
		$posts = get_posts(
			array(
				'post_type'      => sanitize_key( $post_type ),
				'post_status'    => 'any',
				'posts_per_page' => 500,
				'orderby'        => 'title',
				'order'          => 'ASC',
			)
		);

		$forms = array();
		foreach ( $posts as $post ) {
			$forms[] = array(
				'id'    => (string) absint( $post->ID ),
				'label' => sanitize_text_field( $post->post_title ),
			);
		}

		return $forms;
	}

	/**
	 * List Contact Form 7 forms.
	 *
	 * @return array<int,array<string,string>>
	 */
	private function list_cf7_forms() {
		if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
			return array();
		}

		$forms = array();
		foreach ( \WPCF7_ContactForm::find() as $form ) {
			$forms[] = array(
				'id'    => (string) absint( $form->id() ),
				'label' => sanitize_text_field( $form->title() ),
			);
		}

		return $forms;
	}

	/**
	 * List Contact Form 7 email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_cf7_email_fields( $form_id ) {
		if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
			return array();
		}

		$form = \WPCF7_ContactForm::get_instance( absint( $form_id ) );
		if ( ! $form ) {
			return array();
		}

		$fields = array();
		foreach ( $form->scan_form_tags() as $tag ) {
			$type = sanitize_key( $tag->basetype ?? $tag->type ?? '' );
			$name = sanitize_key( $tag->name ?? '' );
			if ( 'email' !== $type || '' === $name ) {
				continue;
			}
			$fields[] = array(
				'key'   => $name,
				'label' => $this->label_from_key( $name ),
			);
		}

		return $fields;
	}

	/**
	 * List Fluent Forms forms.
	 *
	 * @return array<int,array<string,string>>
	 */
	private function list_fluent_forms() {
		global $wpdb;

		$table = $wpdb->prefix . 'fluentform_forms';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$rows = $wpdb->get_results( "SELECT id, title FROM {$table} ORDER BY title ASC, id ASC LIMIT 500", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$forms = array();
		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
			$forms[] = array(
				'id'    => (string) absint( $row['id'] ?? 0 ),
				'label' => sanitize_text_field( (string) ( $row['title'] ?? '' ) ),
			);
		}

		return $forms;
	}

	/**
	 * List Fluent Forms email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_fluent_email_fields( $form_id ) {
		global $wpdb;

		$table = $wpdb->prefix . 'fluentform_forms';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$json = $wpdb->get_var( $wpdb->prepare( "SELECT form_fields FROM {$table} WHERE id = %d", absint( $form_id ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$data = is_string( $json ) ? json_decode( $json, true ) : null;

		return is_array( $data ) ? $this->extract_fluent_email_fields( $data ) : array();
	}

	/**
	 * List Forminator email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_forminator_email_fields( $form_id ) {
		$meta = get_post_meta( absint( $form_id ), 'forminator_form_meta', true );
		$data = is_array( $meta ) ? $meta : maybe_unserialize( $meta );
		$fields = is_array( $data['fields'] ?? null ) ? $data['fields'] : array();
		$choices = array();

		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) || 'email' !== sanitize_key( $field['type'] ?? $field['element'] ?? '' ) ) {
				continue;
			}
			$key = sanitize_key( (string) ( $field['element_id'] ?? $field['id'] ?? '' ) );
			if ( '' === $key ) {
				continue;
			}
			$choices[] = array(
				'key'   => $key,
				'label' => sanitize_text_field( (string) ( $field['field_label'] ?? $field['label'] ?? $this->label_from_key( $key ) ) ),
			);
		}

		return $choices;
	}

	/**
	 * List Ninja Forms forms.
	 *
	 * @return array<int,array<string,string>>
	 */
	private function list_ninja_forms() {
		global $wpdb;

		$table = $wpdb->prefix . 'nf3_forms';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$rows = $wpdb->get_results( "SELECT id, title FROM {$table} ORDER BY title ASC, id ASC LIMIT 500", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
		$forms = array();
		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
			$forms[] = array(
				'id'    => (string) absint( $row['id'] ?? 0 ),
				'label' => sanitize_text_field( (string) ( $row['title'] ?? '' ) ),
			);
		}

		return $forms;
	}

	/**
	 * List Ninja Forms email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_ninja_email_fields( $form_id ) {
		global $wpdb;

		$table = $wpdb->prefix . 'nf3_fields';
		if ( ! $this->table_exists( $table ) ) {
			return array();
		}

		$rows = $wpdb->get_results(
			$wpdb->prepare( "SELECT field_key, `key`, label FROM {$table} WHERE parent_id = %d AND type = %s ORDER BY id ASC", absint( $form_id ), 'email' ), // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			ARRAY_A
		);
		$fields = array();
		foreach ( is_array( $rows ) ? $rows : array() as $row ) {
			$key = sanitize_key( (string) ( $row['field_key'] ?: $row['key'] ?? '' ) );
			if ( '' === $key ) {
				continue;
			}
			$fields[] = array(
				'key'   => $key,
				'label' => sanitize_text_field( (string) ( $row['label'] ?: $this->label_from_key( $key ) ) ),
			);
		}

		return $fields;
	}

	/**
	 * List WPForms email fields.
	 *
	 * @param string $form_id Form id.
	 * @return array<int,array<string,string>>
	 */
	private function list_wpforms_email_fields( $form_id ) {
		$post = get_post( absint( $form_id ) );
		if ( ! $post || 'wpforms' !== $post->post_type ) {
			return array();
		}

		$data = json_decode( (string) $post->post_content, true );
		$fields = is_array( $data['fields'] ?? null ) ? $data['fields'] : array();
		$choices = array();
		foreach ( $fields as $field ) {
			if ( ! is_array( $field ) || 'email' !== sanitize_key( $field['type'] ?? '' ) ) {
				continue;
			}
			$id = absint( $field['id'] ?? 0 );
			$key = $id > 0 ? 'wpforms_field_' . $id . '_email' : sanitize_key( $field['key'] ?? $field['label'] ?? '' );
			if ( '' === $key ) {
				continue;
			}
			$choices[] = array(
				'key'   => $key,
				'label' => sanitize_text_field( (string) ( $field['label'] ?? $this->label_from_key( $key ) ) ),
			);
		}

		return $choices;
	}

	/**
	 * Extract Fluent Forms email fields from nested field config.
	 *
	 * @param array<string,mixed> $data Form fields config.
	 * @return array<int,array<string,string>>
	 */
	private function extract_fluent_email_fields( array $data ) {
		$fields = array();
		$walker = function ( $node ) use ( &$walker, &$fields ) {
			if ( ! is_array( $node ) ) {
				return;
			}
			$element = sanitize_key( (string) ( $node['element'] ?? '' ) );
			$type    = sanitize_key( (string) ( $node['attributes']['type'] ?? '' ) );
			if ( 'input_email' === $element || 'email' === $type ) {
				$key = sanitize_key( (string) ( $node['attributes']['name'] ?? '' ) );
				if ( '' !== $key ) {
					$fields[ $key ] = array(
						'key'   => $key,
						'label' => sanitize_text_field( (string) ( $node['settings']['admin_field_label'] ?? $node['settings']['label'] ?? $this->label_from_key( $key ) ) ),
					);
				}
			}
			foreach ( $node as $child ) {
				$walker( $child );
			}
		};
		$walker( $data );

		return array_values( $fields );
	}

	/**
	 * Check whether a custom table exists.
	 *
	 * @param string $table Table name.
	 * @return bool
	 */
	private function table_exists( $table ) {
		global $wpdb;

		return (string) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table;
	}

	/**
	 * Build a readable label from a field key.
	 *
	 * @param string $key Field key.
	 * @return string
	 */
	private function label_from_key( $key ) {
		return ucwords( str_replace( array( '-', '_' ), ' ', sanitize_key( (string) $key ) ) );
	}
}
