<?php

declare( strict_types=1 );

namespace Limb_Chatbot\Includes\Services\System_Message;

use Exception;

/**
 * Powerful system message builder with structured sections.
 *
 * - Preserves insertion order for unspecified sections.
 * - Renders structured function guidance.
 * - Supports reset() and build($reset = false).
 *
 * @since 1.0.0
 */
class System_Message_Builder {

	/** @var array<string, mixed> */
	protected array $components = [];

	public function __construct( array $initial = [] ) {
		$this->components = $initial;
	}

	public function set_role( string $role ): self {
		return $this->add_section( 'role', $role );
	}

	/**
	 * Generic section setter.
	 *
	 * @param  string  $name
	 * @param  string|array  $content
	 *
	 * @throws Exception
	 */
	public function add_section( string $name, $content ): self {
		if ( ! is_string( $content ) && ! is_array( $content ) ) {
			throw new Exception( __( 'Section content must be string or array.', 'limb-chatbot' ) );
		}

		// replace existing or add (preserves insertion order in PHP arrays)
		$this->components[ $name ] = $content;

		return $this;
	}

	public function set_identity( string $identity ): self {
		return $this->add_section( 'identity', $identity );
	}

	public function set_context( array $context ): self {
		return $this->add_section( 'context', $context );
	}

	public function set_tone( string $tone ): self {
		return $this->add_section( 'tone', $tone );
	}

	public function set_instructions( array $instructions ): self {
		return $this->add_section( 'instructions', $instructions );
	}

	public function set_core_rules( array $rules ): self {
		return $this->add_section( 'core_rules', $rules );
	}

	public function set_response_guidelines( array $guidelines ): self {
		return $this->add_section( 'response_guidelines', $guidelines );
	}

	public function set_edge_cases( array $edge_cases ): self {
		return $this->add_section( 'edge_cases', $edge_cases );
	}

	public function set_meta( array $meta ): self {
		return $this->add_section( 'meta', $meta );
	}

	/**
	 * Store function guidance as structured data (not preformatted strings).
	 */
	public function add_function_guidance(
		string $function_name,
		array $triggers = [],
		array $exceptions = []
	): self {
		$entry = [
			'function'   => $function_name,
			'triggers'   => array_values( $triggers ),
			'exceptions' => array_values( $exceptions ),
		];

		if ( ! isset( $this->components['function_guidance'] ) ) {
			$this->components['function_guidance'] = [];
		}

		$this->components['function_guidance'][] = $entry;

		return $this;
	}

	/**
	 * Build the final system message string.
	 *
	 * @param  bool  $reset  If true, reset builder state after building.
	 *
	 * @return string
	 */
	public function build( bool $reset = false ): string {
		$preferredOrder = [
			'role',
			'identity',
			'knowledge_scope',
			'context',
			'tone',
			'core_rules',
			'instructions',
			'response_guidelines',
			'edge_cases',
			'meta',
		];

		$messageParts = [];
		$processed    = [];

		$is_assoc = function ( array $arr ): bool {
			if ( $arr === [] ) {
				return false;
			}

			return array_keys( $arr ) !== range( 0, count( $arr ) - 1 );
		};

		$renderValue = function ( $value, int $indent = 0 ) use ( &$renderValue, $is_assoc ): string {
			$indentStr = str_repeat( '   ', $indent );
			$lines     = [];

			if ( is_string( $value ) ) {
				$lines[] = $indentStr . $value;

				return implode( "\n", $lines );
			}

			if ( is_array( $value ) ) {
				// numeric list
				if ( ! $is_assoc( $value ) ) {
					foreach ( $value as $item ) {
						if ( is_string( $item ) ) {
							$lines[] = $indentStr  . $item;
						} elseif ( is_array( $item ) && isset( $item['function'] ) ) {
							// structured function guidance
							$fn      = $item['function'];
							$lines[] = $indentStr . "Trigger `{$fn}` when:";
							foreach ( $item['triggers'] as $t ) {
								$lines[] = $indentStr . "   • {$t}";
							}
							if ( ! empty( $item['exceptions'] ) ) {
								$lines[] = $indentStr . "Do NOT trigger `{$fn}` if:";
								foreach ( $item['exceptions'] as $e ) {
									$lines[] = $indentStr . "   • {$e}";
								}
							}
						} elseif ( is_array( $item ) ) {
							// nested list item (associative/numeric)
							if ( $is_assoc( $item ) ) {
								foreach ( $item as $k => $v ) {
									if ( is_array( $v ) ) {
										$lines[] = $indentStr . ucfirst( str_replace( '_', ' ', $k ) ) . ':';
										$lines[] = $renderValue( $v, $indent + 1 );
									} else {
										$lines[] = $indentStr . ucfirst( str_replace( '_',
												' ',
												$k ) ) . ': ' . (string) $v;
									}
								}
							} else {
								// numeric nested array: render recursively
								$lines[] = $renderValue( $item, $indent );
							}
						} else {
							// scalar fallback
							$lines[] = $indentStr . (string) $item;
						}
					}
				} else {
					// associative array => render each key as a bullet or heading
					foreach ( $value as $k => $v ) {
						if ( is_array( $v ) ) {
							$lines[] = $indentStr . ucfirst( str_replace( '_', ' ', $k ) ) . ':';
							$lines[] = $renderValue( $v, $indent + 1 );
						} else {
							$lines[] = $indentStr . ucfirst( str_replace( '_', ' ', $k ) ) . ': ' . (string) $v;
						}
					}
				}

				return implode( "\n", $lines );
			}

			// fallback
			return $indentStr . (string) $value;
		};

		// Render preferred order first (if present)
		foreach ( $preferredOrder as $section ) {
			if ( ! array_key_exists( $section, $this->components ) ) {
				continue;
			}
			$processed[]    = $section;
			$heading        =  $section;
			$messageParts[] = $heading . "\n" . $renderValue( $this->components[ $section ] );
		}

		// Append any remaining components in insertion order
		foreach ( $this->components as $name => $value ) {
			if ( in_array( $name, $processed, true ) ) {
				continue;
			}
			$heading        =  $name;
			$messageParts[] = $heading . "\n" . $renderValue( $value );
		}

		$message = trim( implode( "\n\n", $messageParts ) );

		if ( $reset ) {
			$this->reset();
		}

		return $message;
	}

	public function reset(): self {
		$this->components = [];

		return $this;
	}
}
