<?php
/**
 * Form-plugin adapters. Everything is feature-detected; a missing plugin
 * simply means its hooks never fire. All observation, zero interference.
 *
 * @package Form_Flight_Recorder
 */

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

/**
 * The five adapters + form discovery.
 */
class Ffrd_Adapters {

	/**
	 * Container.
	 *
	 * @var Ffrd_Plugin
	 */
	private $plugin;

	/**
	 * Constructor: wire every adapter hook (harmless when the plugin is absent).
	 *
	 * @param Ffrd_Plugin $plugin Container.
	 */
	public function __construct( $plugin ) {
		$this->plugin = $plugin;

		// Contact Form 7 — CF7 exposes its own mail truth.
		add_action( 'wpcf7_before_send_mail', array( $this, 'cf7_begin' ), 5, 1 );
		add_action( 'wpcf7_mail_sent', array( $this, 'cf7_sent' ), 5, 1 );
		add_action( 'wpcf7_mail_failed', array( $this, 'cf7_failed' ), 5, 1 );

		// WPForms — begin at validation-passed processing, confirm at complete.
		add_action( 'wpforms_process', array( $this, 'wpforms_begin' ), 5, 3 );
		add_action( 'wpforms_process_complete', array( $this, 'wpforms_confirm' ), 5, 4 );

		// Gravity Forms — entry saved = real submission; notifications follow.
		add_action( 'gform_entry_created', array( $this, 'gf_begin' ), 5, 2 );
		add_action( 'gform_send_email_failed', array( $this, 'gf_email_failed' ), 5, 3 );

		// Ninja Forms — begin tentative at submit-data, confirm after submission.
		add_filter( 'ninja_forms_submit_data', array( $this, 'ninja_begin' ), 5, 1 );
		add_action( 'ninja_forms_after_submission', array( $this, 'ninja_confirm' ), 5, 1 );

		// Fluent Forms — entry inserted; notifications ride the same hook later.
		add_action( 'fluentform/submission_inserted', array( $this, 'fluent_begin' ), 1, 3 );
	}

	/**
	 * Collector shortcut.
	 *
	 * @return Ffrd_Collector
	 */
	private function collector() {
		return $this->plugin->get( 'collector' );
	}

	// ------------------------------------------------------------------
	// Contact Form 7.
	// ------------------------------------------------------------------

	/**
	 * CF7 is about to (maybe) send.
	 *
	 * @param WPCF7_ContactForm $contact_form Form.
	 */
	public function cf7_begin( $contact_form ) {
		if ( ! is_object( $contact_form ) || ! method_exists( $contact_form, 'id' ) ) {
			return;
		}
		$expects = true;
		if ( method_exists( $contact_form, 'in_demo_mode' ) && $contact_form->in_demo_mode() ) {
			$expects = false;
		}
		if ( method_exists( $contact_form, 'is_true' ) && $contact_form->is_true( 'skip_mail' ) ) {
			$expects = false;
		}
		$title = method_exists( $contact_form, 'title' ) ? $contact_form->title() : '';
		$this->collector()->begin( 'cf7', (string) $contact_form->id(), $title, $expects, true );
	}

	/**
	 * CF7 says its mail went out.
	 *
	 * @param WPCF7_ContactForm $contact_form Form.
	 */
	public function cf7_sent( $contact_form ) {
		unset( $contact_form );
		$this->collector()->annotate( 'plugin_status', 'mail_sent' );
	}

	/**
	 * CF7 says its mail failed.
	 *
	 * @param WPCF7_ContactForm $contact_form Form.
	 */
	public function cf7_failed( $contact_form ) {
		unset( $contact_form );
		$this->collector()->annotate( 'plugin_status', 'mail_failed' );
	}

	// ------------------------------------------------------------------
	// WPForms.
	// ------------------------------------------------------------------

	/**
	 * WPForms passed field validation (may still abort) — begin tentative.
	 *
	 * @param array $fields    Fields.
	 * @param array $entry     Entry.
	 * @param array $form_data Form data.
	 */
	public function wpforms_begin( $fields, $entry, $form_data ) {
		unset( $fields, $entry );
		if ( ! is_array( $form_data ) || empty( $form_data['id'] ) ) {
			return;
		}
		$settings = isset( $form_data['settings'] ) && is_array( $form_data['settings'] ) ? $form_data['settings'] : array();
		$expects  = ! empty( $settings['notification_enable'] );
		$title    = isset( $settings['form_title'] ) ? $settings['form_title'] : '';
		$this->collector()->begin( 'wpforms', (string) $form_data['id'], $title, $expects, false );
	}

	/**
	 * WPForms finished processing — the submission is real.
	 *
	 * @param array $fields    Fields.
	 * @param array $entry     Entry.
	 * @param array $form_data Form data.
	 * @param int   $entry_id  Entry id.
	 */
	public function wpforms_confirm( $fields, $entry, $form_data, $entry_id ) {
		unset( $fields, $entry, $form_data );
		$this->collector()->confirm( array( 'entry_id' => (int) $entry_id ) );
	}

	// ------------------------------------------------------------------
	// Gravity Forms.
	// ------------------------------------------------------------------

	/**
	 * GF saved an entry; notifications follow in this request.
	 *
	 * @param array $entry Entry.
	 * @param array $form  Form.
	 */
	public function gf_begin( $entry, $form ) {
		if ( ! is_array( $form ) || empty( $form['id'] ) ) {
			return;
		}
		$expects = false;
		if ( ! empty( $form['notifications'] ) && is_array( $form['notifications'] ) ) {
			foreach ( $form['notifications'] as $notification ) {
				if ( ! is_array( $notification ) ) {
					continue;
				}
				$active = ! isset( $notification['isActive'] ) || false !== $notification['isActive'];
				$event  = empty( $notification['event'] ) ? 'form_submission' : $notification['event'];
				if ( $active && 'form_submission' === $event ) {
					$expects = true;
					break;
				}
			}
		}
		$title = isset( $form['title'] ) ? $form['title'] : '';
		$this->collector()->begin( 'gf', (string) $form['id'], $title, $expects, true );
		if ( is_array( $entry ) && isset( $entry['id'] ) ) {
			$this->collector()->annotate( 'entry_id', (int) $entry['id'] );
		}
	}

	/**
	 * GF says a notification email failed.
	 *
	 * @param string $error   Error message.
	 * @param mixed  $details Email details.
	 * @param mixed  $entry   Entry.
	 */
	public function gf_email_failed( $error, $details = null, $entry = null ) {
		unset( $details, $entry );
		$this->collector()->observe_failure( new WP_Error( 'ffrd_gf', is_scalar( $error ) ? (string) $error : 'Gravity Forms reported a failed notification email' ) );
	}

	// ------------------------------------------------------------------
	// Ninja Forms.
	// ------------------------------------------------------------------

	/**
	 * NF received a submission for processing — begin tentative. (Filter: the
	 * data passes through untouched.)
	 *
	 * @param array $form_data Submit data.
	 * @return array Unchanged.
	 */
	public function ninja_begin( $form_data ) {
		if ( is_array( $form_data ) && ! empty( $form_data['id'] ) ) {
			$form_id = (string) absint( $form_data['id'] );
			$title   = $this->ninja_form_title( $form_id );
			$this->collector()->begin( 'ninja', $form_id, $title, true, false );
		}
		return $form_data;
	}

	/**
	 * NF finished the submission — it is real.
	 *
	 * @param array $form_data Form data.
	 */
	public function ninja_confirm( $form_data ) {
		unset( $form_data );
		$this->collector()->confirm();
	}

	/**
	 * Best-effort NF form title.
	 *
	 * @param string $form_id Form id.
	 * @return string
	 */
	private function ninja_form_title( $form_id ) {
		$title = '';
		try {
			if ( function_exists( 'Ninja_Forms' ) ) {
				$form = Ninja_Forms()->form( (int) $form_id )->get();
				if ( is_object( $form ) && method_exists( $form, 'get_setting' ) ) {
					$title = trim( (string) $form->get_setting( 'title' ) );
				}
			}
		} catch ( Throwable $e ) {
			unset( $e );
		}
		if ( '' !== $title ) {
			return $title;
		}
		/* translators: %s: form id. */
		return sprintf( __( 'Ninja Form #%s', 'form-flight-recorder' ), $form_id );
	}

	// ------------------------------------------------------------------
	// Fluent Forms.
	// ------------------------------------------------------------------

	/**
	 * Fluent inserted an entry; its notifier listens on the same hook after us.
	 *
	 * @param int    $entry_id  Entry id.
	 * @param mixed  $form_data Submitted data.
	 * @param object $form      Form.
	 */
	public function fluent_begin( $entry_id, $form_data, $form ) {
		unset( $form_data );
		if ( ! is_object( $form ) || ! isset( $form->id ) ) {
			return;
		}
		$title = isset( $form->title ) ? (string) $form->title : '';
		$this->collector()->begin( 'fluent', (string) $form->id, $title, true, true );
		$this->collector()->annotate( 'entry_id', (int) $entry_id );
	}

	// ------------------------------------------------------------------
	// Discovery.
	// ------------------------------------------------------------------

	/**
	 * Enumerate forms from every detected plugin into the registry (so the
	 * cockpit can show never-submitted forms too). Best effort, never fatal.
	 */
	public function discover() {
		$registry = $this->plugin->get( 'registry' );

		try {
			if ( class_exists( 'WPCF7_ContactForm' ) && is_callable( array( 'WPCF7_ContactForm', 'find' ) ) ) {
				foreach ( (array) WPCF7_ContactForm::find( array( 'posts_per_page' => 50 ) ) as $form ) {
					if ( is_object( $form ) && method_exists( $form, 'id' ) ) {
						$registry->ensure( 'cf7', (string) $form->id(), method_exists( $form, 'title' ) ? $form->title() : '' );
					}
				}
			}
		} catch ( Throwable $e ) {
			unset( $e );
		}

		try {
			if ( function_exists( 'wpforms' ) && is_object( wpforms()->form ) ) {
				foreach ( (array) wpforms()->form->get( '', array( 'posts_per_page' => 50 ) ) as $form ) {
					if ( $form instanceof WP_Post ) {
						$registry->ensure( 'wpforms', (string) $form->ID, $form->post_title );
					}
				}
			}
		} catch ( Throwable $e ) {
			unset( $e );
		}

		try {
			if ( class_exists( 'GFAPI' ) && is_callable( array( 'GFAPI', 'get_forms' ) ) ) {
				foreach ( (array) GFAPI::get_forms() as $form ) {
					if ( is_array( $form ) && ! empty( $form['id'] ) ) {
						$registry->ensure( 'gf', (string) $form['id'], isset( $form['title'] ) ? $form['title'] : '' );
					}
				}
			}
		} catch ( Throwable $e ) {
			unset( $e );
		}

		try {
			if ( function_exists( 'Ninja_Forms' ) ) {
				foreach ( (array) Ninja_Forms()->form()->get_forms() as $form ) {
					if ( is_object( $form ) && method_exists( $form, 'get_id' ) ) {
						$registry->ensure( 'ninja', (string) $form->get_id(), method_exists( $form, 'get_setting' ) ? (string) $form->get_setting( 'title' ) : '' );
					}
				}
			}
		} catch ( Throwable $e ) {
			unset( $e );
		}

		try {
			if ( function_exists( 'wpFluent' ) ) {
				$rows = wpFluent()->table( 'fluentform_forms' )->select( array( 'id', 'title' ) )->limit( 50 )->get();
				foreach ( (array) $rows as $row ) {
					if ( is_object( $row ) && isset( $row->id ) ) {
						$registry->ensure( 'fluent', (string) $row->id, isset( $row->title ) ? (string) $row->title : '' );
					}
				}
			}
		} catch ( Throwable $e ) {
			unset( $e );
		}
	}

	/**
	 * Which of the five supported plugins are active?
	 *
	 * @return array slug => label of detected plugins.
	 */
	public function detected() {
		$out = array();
		if ( class_exists( 'WPCF7_ContactForm' ) ) {
			$out['cf7'] = 'Contact Form 7';
		}
		if ( function_exists( 'wpforms' ) ) {
			$out['wpforms'] = 'WPForms';
		}
		if ( class_exists( 'GFAPI' ) ) {
			$out['gf'] = 'Gravity Forms';
		}
		if ( function_exists( 'Ninja_Forms' ) ) {
			$out['ninja'] = 'Ninja Forms';
		}
		if ( function_exists( 'wpFluent' ) || defined( 'FLUENTFORM' ) ) {
			$out['fluent'] = 'Fluent Forms';
		}
		return $out;
	}
}
