<?php

namespace Limb_Chatbot\Includes\Exceptions;

use Limb_Chatbot\Includes\Admin\Pages\Default_Chatbot\Page;
use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Utilities\Chatbot_Utility;
use Limb_Chatbot\Includes\Utilities\Utility;

/**
 * Exception class for user-related and system-level errors with structured error codes.
 *
 * Provides enhanced error handling including error codes, HTTP status,
 * additional data, and utility-related context for Limb Chatbot.
 *
 * @since      1.0.0
 * @package Limb_Gpt_AI_Provider/includes/exceptions
 */
class Exception extends \Exception {

	/**
	 * Integer-based categorized error code.
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	protected ?int $error_code;

	/**
	 * Optional HTTP status code.
	 *
	 * @var int|null
	 * @since 1.0.0
	 */
	protected ?int $http_status;

	/**
	 * Additional error context/data.
	 *
	 * @var mixed
	 * @since 1.0.0
	 */
	protected $error_data;

	/**
	 * Flag indicating if error is related to quota.
	 *
	 * @var bool
	 * @since 1.0.0
	 */
	protected bool $related_to_quota = false;

	/**
	 * Constructor.
	 *
	 * @param  int|null  $error_code  Numeric error code (e.g. 10000).
	 * @param  string  $message  Human-readable error message.
	 * @param  mixed|null  $error_data  Additional context or error data.
	 * @param  int|null  $http_status  Optional HTTP status code, default 400.
	 *
	 * @since 1.0.0
	 */
	public function __construct(
		?int $error_code,
		string $message,
		$error_data = null,
		?int $http_status = 400
	) {
		$this->error_code  = $error_code;
		$this->error_data  = $error_data;
		$this->http_status = $http_status;
		parent::__construct( $message, $error_code ); // Pass $error_code as native code
	}

	/**
	 * Get the custom error code.
	 *
	 * @return int
	 */
	public function get_error_code(): int {
		return $this->error_code;
	}

	/**
	 * Get the HTTP status code.
	 *
	 * @return int|null
	 */
	public function get_http_status(): ?int {
		return $this->http_status;
	}

	/**
	 * Get additional error data.
	 *
	 * @return mixed
	 */
	public function get_error_data() {
		return $this->error_data;
	}


	/**
	 * Set whether this error is related to quota limits.
	 *
	 * @param  bool  $related_to_quota
	 *
	 * @since 1.0.0
	 */
	public function set_related_to_quota( bool $related_to_quota ): void {
		$this->related_to_quota = $related_to_quota;
	}

	/**
	 * Check if this error is related to quota limits.
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	public function is_related_to_quota(): bool {
		return $this->related_to_quota;
	}

	/**
	 * Check if this error is a validation error (error codes 10000-10100).
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	public function is_validation_error(): bool {
		return $this->error_code >= 10000 && $this->error_code <= 10100;
	}

	/**
	 * Check if this error is a chatbot limitation error.
	 *
	 * @return bool
	 * @since 1.0.0
	 */
	public function is_limitation_error(): bool {
		return $this->error_code === Error_Codes::CHATBOT_LIMIT_EXCEED ||
		       $this->error_code === Error_Codes::USER_MESSAGE_LIMIT_EXCEED;
	}

	/**
	 * Append a settings/admin link to the error message based on given utility and page info.
	 * Extra link is only supported when throwing exceptions from inside the Chatbot
	 *
	 * @param  Utility|null  $utility  Utility instance or null.
	 * @param  Chatbot|null  $chatbot  Utility instance or null.
	 * @param  string|null  $page  Admin page slug or null.
	 * @param  string|null  $menu  Menu slug or null.
	 * @param  string|null  $tab  Tab identifier or null.
	 * @param  string|null  $field  Field identifier or null.
	 *
	 * @return self
	 * @since 1.0.0
	 */
	public function with_link( $utility = null, $chatbot = null, $page = null, $menu = null, $tab = null, $field = null ): self {
		if ( isset( $utility ) && ( $utility instanceof Utility || ( property_exists( $utility, 'global_utility' ) && $utility->global_utility instanceof Utility ) ) ) {
			$parent_utility = property_exists( $utility, 'global_utility' ) ? $utility->global_utility : $utility;
			if ( $parent_utility instanceof Chatbot_Utility ) {
				$chatbot = $parent_utility->get_chatbot();
			}
		} elseif ( isset( $utility ) && property_exists( $utility, 'chatbot' ) ) {
			$chatbot = $utility->chatbot;
		}
		if ( isset( $chatbot ) ) {
			$link       = $chatbot->id ? get_edit_post_link( $chatbot->id ) : admin_url( 'admin.php?page=' . ( $page ?? Page::$menu_slug ) );
			$menu       = $menu ?? 'chatbot';
			$query_args = array_filter( compact( 'menu', 'tab', 'field' ), fn( $v ) => isset( $v ) );
			if ( ! empty( $query_args ) && ! empty( $link ) ) {
				// translators: %s placeholders wrap the word "here" with an anchor link to the settings page.
				$this->message .= ' ' . sprintf( __( 'Please check the settings %s here%s.', 'limb-chatbot' ), '<a href="' . esc_url( add_query_arg( $query_args, $link ) ) . '">', '</a>' );
			}
		}

		return $this;
	}

	/**
	 * Attach link to the exception
	 *
	 * @param string $link the link which should be attached
	 * @param string $text the text of the link
	 *
	 * @return void
	 */
	public function attach_link( $link, $text = null ) {
		$this->message .= '<a class="underline" target="_blank" rel="noopener" href="' . esc_url( $link ) . '">' . $text . ' ↗</a>';
	}

	/**
	 * Attach utility and AI provider context to the exception message.
	 *
	 * @param  object|null  $utility  Utility object with a global_utility property or null.
	 * @param  bool  $include_ai_provider  Whether to include AI provider name prefix.
	 *
	 * @return self
	 * @since 1.0.0
	 */
	public function attach_utility( $utility = null, bool $include_ai_provider = false ): self {
		if ( is_object( $utility ) && isset( $utility->global_utility ) && $utility->global_utility instanceof Utility ) {
			$prefix = $utility->global_utility::get_name();
			try {
				$ai_provider = $utility->global_utility->get_ai_provider();
			} catch ( Exception $e ) {
				$ai_provider = null;
			}
			if ( $include_ai_provider && $ai_provider && $ai_provider->get_name() ) {
				$prefix = $ai_provider->get_name() . ' • ' . $prefix;
			}
			$prefix = "[$prefix]";
		}
		if ( isset( $prefix ) ) {
			$this->message = "$prefix $this->message";
		}

		return $this;
	}
}
