<?php

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

/**
 * Native, revision-safe access to Niwa's own product settings.
 */
trait Niwa_AI_Tools_Niwa_Settings {

	/**
	 * Preview a read or a write requested through niwa_settings.
	 */
	private function preview_niwa_settings( $args ) {
		$args      = is_array( $args ) ? $args : array();
		$operation = isset( $args['operation'] ) ? sanitize_key( (string) $args['operation'] ) : 'catalog';

		if ( 'catalog' === $operation ) {
			return array(
				'success' => true,
				'catalog' => $this->get_niwa_settings_catalog(),
			);
		}

		$domain = isset( $args['domain'] ) ? sanitize_key( (string) $args['domain'] ) : '';
		if ( ! isset( $this->get_niwa_settings_domain_map()[ $domain ] ) ) {
			return array(
				'success' => false,
				'error'   => 'Nepoznata Niwa settings domena. Prvo pozovi operation=catalog.',
			);
		}

		$current  = $this->read_niwa_settings_domain( $domain );
		$revision = $this->get_niwa_settings_revision( $current );

		if ( 'get' === $operation ) {
			return array(
				'success'        => true,
				'domain'         => $domain,
				'settings'       => $current,
				'fields'         => $this->get_niwa_settings_allowed_keys( $domain, $current ),
				'revision'       => $revision,
				'collection_rule'=> 'Top-level patch. Arrays such as steps, services, team_members, rules and options replace the complete collection.',
			);
		}

		if ( 'update' !== $operation ) {
			return array( 'success' => false, 'error' => 'operation mora biti catalog, get ili update.' );
		}
		if ( ! isset( $args['settings'] ) || ! is_array( $args['settings'] ) || empty( $args['settings'] ) ) {
			return array( 'success' => false, 'error' => 'settings mora biti neprazan objekat sa top-level promenama.' );
		}
		$unknown_keys = array_diff( array_keys( $args['settings'] ), $this->get_niwa_settings_allowed_keys( $domain, $current ) );
		if ( ! empty( $unknown_keys ) ) {
			return array(
				'success' => false,
				'error'   => 'Nepoznata settings polja za domenu ' . $domain . ': ' . implode( ', ', $unknown_keys ) . '.',
			);
		}

		$expected_revision = isset( $args['expected_revision'] ) ? trim( (string) $args['expected_revision'] ) : '';
		if ( '' === $expected_revision || ! hash_equals( $revision, $expected_revision ) ) {
			return array(
				'success'          => false,
				'error'            => 'Settings su promenjeni ili nisu prethodno procitani. Ponovi operation=get i koristi vraceni revision.',
				'current_revision' => $revision,
			);
		}

		$requested = array_replace( $current, $args['settings'] );
		$next      = $this->sanitize_niwa_settings_domain( $domain, $requested );
		$valid     = $this->validate_niwa_settings_domain( $domain, $requested, $next );
		if ( empty( $valid['success'] ) ) {
			return $valid;
		}

		if ( $next === $current ) {
			return array(
				'success'  => true,
				'domain'   => $domain,
				'settings' => $current,
				'revision' => $revision,
				'no_change'=> true,
				'summary'  => 'Niwa settings su vec u trazenom stanju.',
			);
		}

		$changed_keys = array();
		foreach ( array_unique( array_merge( array_keys( $current ), array_keys( $next ) ) ) as $key ) {
			$old = array_key_exists( $key, $current ) ? $current[ $key ] : null;
			$new = array_key_exists( $key, $next ) ? $next[ $key ] : null;
			if ( 0 !== $this->compare_niwa_setting_values( $old, $new ) ) {
				$changed_keys[] = $key;
			}
		}

		return array(
			'success' => true,
			'action'  => array(
				'type'             => 'niwa_settings_update',
				'label'            => 'Azuriraj Niwa settings: ' . $domain,
				'summary'          => 'Menjanje Niwa settings domene ' . $domain . ': ' . implode( ', ', $changed_keys ) . '.',
				'rollback_plan'    => 'Vrati celu domenu na prethodno kanonsko stanje.',
				'rollback_support' => 'full',
				'targets'          => array_map(
					static function ( $key ) use ( $domain ) {
						return array( 'domain' => $domain, 'setting' => $key );
					},
					$changed_keys
				),
				'args'             => array(
					'domain'            => $domain,
					'expected_revision' => $revision,
					'next_settings'     => $next,
					'changed_keys'      => $changed_keys,
				),
			),
		);
	}

	/** Validate the action produced by preview_niwa_settings(). */
	private function validate_niwa_settings_action( $args ) {
		$args   = is_array( $args ) ? $args : array();
		$domain = isset( $args['domain'] ) ? sanitize_key( (string) $args['domain'] ) : '';
		if ( ! isset( $this->get_niwa_settings_domain_map()[ $domain ] ) ) {
			return array( 'success' => false, 'error' => 'Nepoznata Niwa settings domena.' );
		}
		if ( empty( $args['expected_revision'] ) || ! isset( $args['next_settings'] ) || ! is_array( $args['next_settings'] ) ) {
			return array( 'success' => false, 'error' => 'Niwa settings action nema revision ili kanonske settings.' );
		}

		return $this->validate_niwa_settings_domain( $domain, $args['next_settings'], $args['next_settings'] );
	}

	/** Apply one canonical domain update and verify the readback. */
	private function execute_niwa_settings_action( $action ) {
		$args     = isset( $action['args'] ) && is_array( $action['args'] ) ? $action['args'] : array();
		$domain   = isset( $args['domain'] ) ? sanitize_key( (string) $args['domain'] ) : '';
		$valid    = $this->validate_niwa_settings_action( $args );
		if ( empty( $valid['success'] ) ) {
			return $valid;
		}
		$current  = $this->read_niwa_settings_domain( $domain );
		$revision = $this->get_niwa_settings_revision( $current );

		if ( empty( $args['expected_revision'] ) || ! hash_equals( $revision, (string) $args['expected_revision'] ) ) {
			return array(
				'success'          => false,
				'error'            => 'Niwa settings su promenjeni posle preview-a. Ponovi get pa update.',
				'current_revision' => $revision,
			);
		}

		$next       = isset( $args['next_settings'] ) && is_array( $args['next_settings'] ) ? $args['next_settings'] : array();
		$canonical  = $this->sanitize_niwa_settings_domain( $domain, $next );
		if ( $this->canonicalize_niwa_settings_value( $canonical ) !== $this->canonicalize_niwa_settings_value( $next ) ) {
			return array( 'success' => false, 'error' => 'Niwa settings action nije u kanonskom obliku. Ponovi get pa update.' );
		}
		$option_key = $this->get_niwa_settings_domain_map()[ $domain ]['option'];
		update_option( $option_key, $next );
		$readback = $this->read_niwa_settings_domain( $domain );

		if ( $this->canonicalize_niwa_settings_value( $readback ) !== $this->canonicalize_niwa_settings_value( $next ) ) {
			return array(
				'success' => false,
				'error'   => 'Niwa settings readback se ne poklapa sa trazenim kanonskim stanjem.',
				'expected'=> $next,
				'actual'  => $readback,
			);
		}

		return array(
			'success'          => true,
			'summary'          => 'Niwa settings domena ' . $domain . ' je azurirana i procitana nazad.',
			'domain'           => $domain,
			'settings'         => $readback,
			'revision'         => $this->get_niwa_settings_revision( $readback ),
			'changed_keys'     => isset( $args['changed_keys'] ) ? array_values( (array) $args['changed_keys'] ) : array(),
			'executed_actions' => array(
				array( 'type' => 'niwa_settings_update', 'domain' => $domain ),
			),
			'rollback_entries' => array(
				array(
					'type'              => 'settings_restore',
					'previous_settings' => array( $option_key => $current ),
				),
			),
		);
	}

	/** Supported safe domains and their real WordPress option. */
	private function get_niwa_settings_domain_map() {
		return array(
			'scheduler'          => array( 'option' => Niwa_AI_Appointment_Service::OPTION_KEY, 'description' => 'Booking services, team members, working hours, breaks, exclusions, contact fields and confirmation channels.' ),
			'lead_form'          => array( 'option' => Niwa_AI_Constants::OPT_LEAD_FORM_GENERATOR, 'description' => 'Lead Form steps, questions, validation, appointment questions and deterministic or semantic conditional branching.' ),
			'frontend_ui'        => array( 'option' => Niwa_AI_UI_Settings::OPTION_KEY, 'description' => 'Frontend theme, greeting, launcher, flyout, product cards and responsive presentation.' ),
			'frontend_agent'     => array( 'option' => Niwa_AI_Agent_Mode_Settings::OPTION_KEY, 'description' => 'Business identity, knowledge, tone, rules, sales strategy and human handoff profile.' ),
			'attention_capture'  => array( 'option' => Niwa_AI_Attention_Capture::OPTION_KEY, 'description' => 'Welcome, wakeup, nudges, page conditions, checkout incentives and attention capture timing.' ),
			'conversion_busters' => array( 'option' => Niwa_AI_Conversion_Busters::OPTION_KEY, 'description' => 'Conversion booster messages, coupons, reminder behavior and related offer settings.' ),
			'lead_capture'       => array( 'option' => Niwa_AI_Lead_Capture_Settings::OPTION_KEY, 'description' => 'Email/phone capture, lead notifications, missing-product waitlist, and code-email initial/reminder templates.' ),
			'cart_recovery'      => array( 'option' => Niwa_AI_Constants::OPT_CART_RECOVERY_SETTINGS, 'description' => 'Cart recovery timing, channels, templates, coupons and sender identity.' ),
		);
	}

	/** Return the discoverable domain catalog without exposing secret values. */
	private function get_niwa_settings_catalog() {
		$domains = array();
		foreach ( $this->get_niwa_settings_domain_map() as $slug => $meta ) {
			$current = $this->read_niwa_settings_domain( $slug );
			$domains[ $slug ] = array(
				'description' => $meta['description'],
				'fields'      => $this->get_niwa_settings_allowed_keys( $slug, $current ),
			);
		}

		return array(
			'domains'          => $domains,
			'workflow'         => array( 'get domain and revision', 'update with top-level patch and expected_revision', 'verify returned settings and revision' ),
			'collection_rule'  => 'Collection fields are replaced as a whole. Preserve wanted existing items from get when changing steps, services, team_members, rules, options or similar arrays.',
			'email_connection' => 'Use the existing configure_imap_mailbox_from_existing_smtp tool. It preserves and tests SMTP/IMAP secrets without exposing them through settings readback.',
			'lead_form_schema' => array(
				'modes'          => array( 'chat', 'card' ),
				'triggers'       => array( 'immediate', 'after_delay', 'after_first_user_msg', 'ai_only' ),
				'question_types' => Niwa_AI_Lead_Form_Schema::QUESTION_TYPES,
				'match_types'    => Niwa_AI_Lead_Form_Schema::MATCH_TYPES,
				'complete'       => Niwa_AI_Lead_Form_Schema::COMPLETE_SENTINEL,
			),
		);
	}

	/** Allowed top-level patch keys for one domain. */
	private function get_niwa_settings_allowed_keys( $domain, $current ) {
		if ( 'frontend_agent' === $domain ) {
			return array( 'company_name', 'about', 'products_services', 'faq', 'policies', 'tone', 'dos', 'coupon_rules', 'bundle_rules', 'human_handoff', 'human_hook_hours', 'extra' );
		}

		return array_keys( is_array( $current ) ? $current : array() );
	}

	/** Read a domain through its existing canonical getter. */
	private function read_niwa_settings_domain( $domain ) {
		switch ( $domain ) {
			case 'scheduler':
				return Niwa_AI_Appointment_Service::get_settings();
			case 'lead_form':
				return Niwa_AI_Lead_Form_Schema::get();
			case 'frontend_ui':
				$current = get_option( Niwa_AI_UI_Settings::OPTION_KEY, array() );
				$current = is_array( $current ) ? $current : array();
				$current[ Niwa_AI_UI_Settings::FORM_FIELD ] = Niwa_AI_UI_Settings::FORM_UI;
				return ( new Niwa_AI_UI_Settings() )->sanitize( $current );
			case 'frontend_agent':
				$current = get_option( Niwa_AI_Agent_Mode_Settings::OPTION_KEY, array() );
				return ( new Niwa_AI_Agent_Mode_Settings() )->sanitize( is_array( $current ) ? $current : array() );
			case 'attention_capture':
				return ( new Niwa_AI_Attention_Capture() )->get_opts();
			case 'conversion_busters':
				return ( new Niwa_AI_Conversion_Busters() )->get_opts();
			case 'lead_capture':
				return Niwa_AI_Lead_Capture_Settings::get_settings();
			case 'cart_recovery':
				return Niwa_AI_Cart_Recovery_Settings::get_settings();
		}

		return array();
	}

	/** Sanitize a full merged domain through the existing native sanitizer. */
	private function sanitize_niwa_settings_domain( $domain, $settings ) {
		$settings = is_array( $settings ) ? $settings : array();
		switch ( $domain ) {
			case 'scheduler':
				return Niwa_AI_Appointment_Service::sanitize_settings( $settings );
			case 'lead_form':
				return Niwa_AI_Lead_Form_Schema::sanitize( $settings );
			case 'frontend_ui':
				$settings[ Niwa_AI_UI_Settings::FORM_FIELD ] = Niwa_AI_UI_Settings::FORM_UI;
				return ( new Niwa_AI_UI_Settings() )->sanitize( $settings );
			case 'frontend_agent':
				return ( new Niwa_AI_Agent_Mode_Settings() )->sanitize( $settings );
			case 'attention_capture':
				return ( new Niwa_AI_Attention_Capture() )->sanitize( $settings );
			case 'conversion_busters':
				return ( new Niwa_AI_Conversion_Busters() )->sanitize( $settings );
			case 'lead_capture':
				$settings['_niwa_lead_capture_form'] = 1;
				return ( new Niwa_AI_Lead_Capture_Settings( new Niwa_AI_Contact_Service() ) )->sanitize( $settings );
			case 'cart_recovery':
				return Niwa_AI_Cart_Recovery_Settings::sanitize( $settings );
		}

		return array();
	}

	/** Domain-level checks that existing field sanitizers cannot express. */
	private function validate_niwa_settings_domain( $domain, $requested, $sanitized ) {
		if ( 'lead_form' === $domain ) {
			$id_validation = $this->validate_requested_lead_form_ids( $requested );
			if ( empty( $id_validation['success'] ) ) {
				return $id_validation;
			}

			$schema_validation = Niwa_AI_Lead_Form_Validator::validate( $sanitized );
			if ( empty( $schema_validation['success'] ) ) {
				return $schema_validation;
			}

			$scheduler  = Niwa_AI_Appointment_Service::get_settings();
			$service_ids = array_fill_keys( array_column( $scheduler['services'], 'id' ), true );
			$member_ids  = array_fill_keys( array_column( $scheduler['team_members'], 'id' ), true );
			foreach ( $sanitized['steps'] as $step ) {
				foreach ( $step['questions'] as $question ) {
					if ( 'appointment' !== $question['type'] ) {
						continue;
					}
					foreach ( $question['appointment']['service_ids'] as $service_id ) {
						if ( ! isset( $service_ids[ $service_id ] ) ) {
							return array( 'success' => false, 'error' => 'Appointment pitanje ' . $question['id'] . ' koristi nepostojeci Scheduler service id ' . $service_id . '.' );
						}
					}
					foreach ( $question['appointment']['team_member_ids'] as $member_id ) {
						if ( ! isset( $member_ids[ $member_id ] ) ) {
							return array( 'success' => false, 'error' => 'Appointment pitanje ' . $question['id'] . ' koristi nepostojeci Scheduler team member id ' . $member_id . '.' );
						}
					}
				}
			}

			return array( 'success' => true );
		}

		if ( 'scheduler' === $domain ) {
			foreach ( isset( $requested['team_members'] ) && is_array( $requested['team_members'] ) ? $requested['team_members'] : array() as $member ) {
				if ( ! is_array( $member ) || '' === sanitize_key( isset( $member['id'] ) ? (string) $member['id'] : '' ) ) {
					return array( 'success' => false, 'error' => 'Svaki Scheduler team member mora imati stabilan id.' );
				}
			}
			foreach ( isset( $requested['services'] ) && is_array( $requested['services'] ) ? $requested['services'] : array() as $service ) {
				if ( ! is_array( $service ) || '' === sanitize_key( isset( $service['id'] ) ? (string) $service['id'] : '' ) ) {
					return array( 'success' => false, 'error' => 'Svaki Scheduler service mora imati stabilan id.' );
				}
			}

			$requested_members  = isset( $requested['team_members'] ) && is_array( $requested['team_members'] ) ? count( $requested['team_members'] ) : 0;
			$requested_services = isset( $requested['services'] ) && is_array( $requested['services'] ) ? count( $requested['services'] ) : 0;
			if ( $requested_members !== count( $sanitized['team_members'] ) ) {
				return array( 'success' => false, 'error' => 'Jedan ili vise team_members nisu validni. Svaki mora imati jedinstven id, name i validan email.' );
			}
			if ( $requested_services !== count( $sanitized['services'] ) ) {
				return array( 'success' => false, 'error' => 'Jedan ili vise services nisu validni. Svaki mora imati jedinstven id i name.' );
			}

			$member_ids = array_fill_keys( array_column( $sanitized['team_members'], 'id' ), true );
			foreach ( $requested['services'] as $service ) {
				$assigned = isset( $service['team_member_ids'] ) && is_array( $service['team_member_ids'] ) ? $service['team_member_ids'] : array();
				foreach ( $assigned as $member_id ) {
					$member_id = sanitize_key( (string) $member_id );
					if ( '' === $member_id || ! isset( $member_ids[ $member_id ] ) ) {
						return array( 'success' => false, 'error' => 'Scheduler service ' . sanitize_key( (string) $service['id'] ) . ' koristi nepostojeci team member id ' . $member_id . '.' );
					}
				}
			}
		}

		return array( 'success' => true );
	}

	/** Ensure Lead Form IDs are explicit and stable before sanitization. */
	private function validate_requested_lead_form_ids( $requested ) {
		$seen  = array();
		$steps = isset( $requested['steps'] ) && is_array( $requested['steps'] ) ? $requested['steps'] : array();
		foreach ( $steps as $step ) {
			if ( ! is_array( $step ) ) {
				return array( 'success' => false, 'error' => 'Svaki Lead Form step mora biti objekat.' );
			}
			$step_id = Niwa_AI_Lead_Form_Schema::sanitize_slug( isset( $step['id'] ) ? $step['id'] : '', '' );
			if ( '' === $step_id || isset( $seen[ $step_id ] ) ) {
				return array( 'success' => false, 'error' => 'Svaki Lead Form step mora imati jedinstven stabilan id.' );
			}
			$seen[ $step_id ] = true;

			$questions = isset( $step['questions'] ) && is_array( $step['questions'] ) ? $step['questions'] : array();
			foreach ( $questions as $question ) {
				if ( ! is_array( $question ) ) {
					return array( 'success' => false, 'error' => 'Svako Lead Form pitanje mora biti objekat.' );
				}
				$question_id = Niwa_AI_Lead_Form_Schema::sanitize_slug( isset( $question['id'] ) ? $question['id'] : '', '' );
				if ( '' === $question_id || isset( $seen[ $question_id ] ) ) {
					return array( 'success' => false, 'error' => 'Svako Lead Form pitanje mora imati jedinstven stabilan id.' );
				}
				$seen[ $question_id ] = true;
			}
		}

		return array( 'success' => true );
	}

	/** Stable revision for optimistic concurrency. */
	private function get_niwa_settings_revision( $settings ) {
		return hash( 'sha256', wp_json_encode( $this->canonicalize_niwa_settings_value( $settings ) ) );
	}

	/** Comparator for nested array values in changed-key detection. */
	private function compare_niwa_setting_values( $left, $right ) {
		$left  = wp_json_encode( $this->canonicalize_niwa_settings_value( $left ) );
		$right = wp_json_encode( $this->canonicalize_niwa_settings_value( $right ) );
		return strcmp( $left, $right );
	}

	/** Sort object keys recursively while preserving list order. */
	private function canonicalize_niwa_settings_value( $value ) {
		if ( is_object( $value ) ) {
			$value = get_object_vars( $value );
		}
		if ( ! is_array( $value ) ) {
			return $value;
		}

		$is_list = empty( $value ) || array_keys( $value ) === range( 0, count( $value ) - 1 );
		if ( ! $is_list ) {
			ksort( $value, SORT_STRING );
		}
		foreach ( $value as $key => $item ) {
			$value[ $key ] = $this->canonicalize_niwa_settings_value( $item );
		}

		return $value;
	}
}
