<?php
/**
 * Answyr — Plugin orchestrator.
 *
 * Registers AI providers, then loads whichever surface modules are enabled.
 * Modules are built-in (toggleable in settings), NOT separate plugins.
 *
 * @package Answyr
 */

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

final class Answyr_Plugin {

	private static ?Answyr_Plugin $instance = null;

	/** @var Answyr_Module[] */
	private array $modules = array();

	public static function instance(): Answyr_Plugin {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Boot the plugin: register providers, load enabled modules.
	 */
	public function boot(): void {
		$this->register_providers();
		$this->load_modules();

		Answyr_Settings::init_admin();
		Answyr_Onboarding::init();
		( new Answyr_Core_Rest() )->register();

		add_action( 'admin_menu', array( $this, 'register_admin_menu' ) );
		// Late pass: the Docs CPT injects its own submenu, so fix the final order here.
		add_action( 'admin_menu', array( $this, 'reorder_admin_submenu' ), 999 );
		// Late pass: add the unresolved-ticket badge by editing the $menu global —
		// NOT the registered menu title, which WordPress sanitizes into the submenu
		// hook suffixes (a badge in the title would break every screen-hook check).
		add_action( 'admin_menu', array( $this, 'add_menu_badge' ), 1000 );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_core_admin_assets' ) );
		// Brand the browser/tab title on every Answyr admin screen.
		add_filter( 'admin_title', array( $this, 'brand_admin_title' ), 10, 2 );
	}

	/** Shared styles for the core admin screens (Settings, Insights, landing). */
	public function enqueue_core_admin_assets( string $hook ): void {
		$pages  = array(
			'toplevel_page_answyr',
			'answyr_page_answyr-settings',
			'answyr_page_answyr-insights',
		);
		$screen = get_current_screen();
		$is_kb  = $screen && Answyr_Knowledge_Module::CPT === $screen->post_type;
		if ( ! in_array( $hook, $pages, true ) && ! $is_kb ) {
			return;
		}
		wp_enqueue_style( 'answyr-tokens', ANSWYR_URL . 'assets/tokens.css', array(), ANSWYR_VERSION );
		wp_enqueue_style( 'answyr-admin', ANSWYR_URL . 'assets/admin.css', array( 'answyr-tokens' ), ANSWYR_VERSION );
		// Insights reuses the Desk pill styles and adds its Tickets/Chat tab toggle.
		if ( 'answyr_page_answyr-insights' === $hook ) {
			wp_enqueue_style( 'answyr-desk', ANSWYR_URL . 'assets/admin-desk.css', array( 'answyr-tokens' ), ANSWYR_VERSION );
			wp_enqueue_script( 'answyr-insights', ANSWYR_URL . 'assets/admin-insights.js', array(), ANSWYR_VERSION, true );
		}
		// The Knowledge CPT list/taxonomy screens are rendered by core, so brand
		// their native heading (logo + "Answyr — ") with a scoped inline rule.
		if ( $is_kb ) {
			$logo = esc_url( ANSWYR_URL . 'assets/logo.svg' );
			$sel  = 'body.post-type-' . Answyr_Knowledge_Module::CPT . ' .wp-heading-inline';
			$css  = $sel . "{padding-left:36px;min-height:26px;background:url('{$logo}') left center / 26px 26px no-repeat}"
				. $sel . "::before{content:'Answyr \\2014 ';color:#787c82;font-weight:400}";
			wp_add_inline_style( 'answyr-admin', $css );
		}
	}

	/**
	 * Register the built-in AI providers, then let extensions add their own.
	 */
	private function register_providers(): void {
		Answyr_Provider_Registry::register( new Answyr_Provider_Anthropic() );
		Answyr_Provider_Registry::register( new Answyr_Provider_OpenAI() );

		/**
		 * Add custom AI providers. Each must extend Answyr_AI_Provider
		 * and be passed to Answyr_Provider_Registry::register().
		 */
		do_action( 'answyr_register_ai_providers' );
	}

	/**
	 * Instantiate each enabled surface module.
	 */
	private function load_modules(): void {
		// Knowledge base is core — it's the AI's grounding source — so it's always
		// registered, not gated behind a module toggle.
		require_once ANSWYR_PATH . 'inc/modules/knowledge/class-knowledge-module.php';
		$knowledge = new Answyr_Knowledge_Module();
		$knowledge->register();
		$this->modules['knowledge'] = $knowledge;

		$registry = array(
			'desk' => 'Answyr_Desk_Module',
			'chat' => 'Answyr_Chat_Module',
		);

		/** Allow extensions to register additional modules. */
		$registry = apply_filters( 'answyr_modules', $registry );

		foreach ( $registry as $id => $class ) {
			if ( ! Answyr_Settings::module_enabled( $id ) ) {
				continue;
			}
			$file = ANSWYR_PATH . "inc/modules/{$id}/class-{$id}-module.php";
			if ( is_readable( $file ) ) {
				require_once $file;
			}
			if ( class_exists( $class ) ) {
				$module = new $class();
				$module->register();
				$this->modules[ $id ] = $module;
			}
		}
	}

	public function register_admin_menu(): void {
		// Top-level lands on the Desk inbox (tickets), not Settings. Keep the title a
		// plain string — WordPress sanitizes it into the submenu hook suffixes, so any
		// markup/count here would break screen-hook checks. The badge is added later
		// in add_menu_badge() by editing the $menu global.
		add_menu_page(
			__( 'Support Desk', 'answyr' ),
			__( 'Answyr', 'answyr' ),
			'manage_options',
			'answyr',
			array( $this, 'render_landing' ),
			self::menu_icon_uri(),
			58
		);
		add_submenu_page(
			'answyr',
			__( 'Tickets', 'answyr' ),
			__( 'Tickets', 'answyr' ),
			'manage_options',
			'answyr',
			array( $this, 'render_landing' )
		);
		// Insights is always available — even with Tickets/Chat off it still shows the
		// page, with the disabled surface's section dimmed and an enable hint.
		add_submenu_page(
			'answyr',
			__( 'Insights', 'answyr' ),
			__( 'Insights', 'answyr' ),
			'manage_options',
			'answyr-insights',
			array( $this, 'render_insights' )
		);
		// "Knowledge" in the menu is the KB article list (the CPT adds its own
		// submenu via show_in_menu); knowledge-source selection lives in Settings.
		add_submenu_page(
			'answyr',
			__( 'Settings', 'answyr' ),
			__( 'Settings', 'answyr' ),
			'manage_options',
			'answyr-settings',
			array( 'Answyr_Settings', 'render_page' )
		);
	}

	/**
	 * Enforce the submenu order: Tickets, Knowledge, Insights, Settings.
	 * The Knowledge entry is added by the KB CPT (`show_in_menu`), so we can only
	 * fix the final order after every menu is registered. Missing items (a disabled
	 * module) are simply skipped; anything unexpected sinks to the bottom.
	 */
	public function reorder_admin_submenu(): void {
		global $submenu;
		if ( empty( $submenu['answyr'] ) ) {
			return;
		}
		$rank = array_flip(
			array(
				'answyr',                        // Tickets
				'edit.php?post_type=answyr_kb',  // Knowledge (KB articles)
				'answyr-insights',               // Insights
				'answyr-settings',               // Settings
			)
		);
		usort(
			$submenu['answyr'],
			static function ( $a, $b ) use ( $rank ) {
				return ( $rank[ $a[2] ] ?? 999 ) <=> ( $rank[ $b[2] ] ?? 999 );
			}
		);
	}

	/**
	 * Append the unresolved-ticket bubble to the top-level "Answyr" menu by editing
	 * the $menu global in place (index 0 = the rendered title). Runs after the menu
	 * is registered so the already-computed hook suffixes stay based on the plain
	 * title. Mirrors core's Comments/Updates "awaiting count" markup.
	 */
	public function add_menu_badge(): void {
		$count = $this->unresolved_ticket_count();
		if ( $count < 1 ) {
			return;
		}
		global $menu;
		if ( ! is_array( $menu ) ) {
			return;
		}
		$badge = sprintf(
			' <span class="awaiting-mod answyr-unresolved-count"><span class="pending-count">%s</span></span>',
			esc_html( number_format_i18n( $count ) )
		);
		foreach ( $menu as $i => $item ) {
			if ( isset( $item[2] ) && 'answyr' === $item[2] ) {
				// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Editing the admin menu title in place is the supported way to add a count bubble.
				$menu[ $i ][0] .= $badge;
				break;
			}
		}
	}

	/** Count of unresolved tickets (open + pending); 0 when Desk is disabled or unavailable. */
	private function unresolved_ticket_count(): int {
		if ( ! Answyr_Settings::module_enabled( 'desk' ) || ! class_exists( 'Answyr_Desk_Store' ) ) {
			return 0;
		}
		$by_status = Answyr_Desk_Store::count_by_status();
		return (int) ( $by_status['open'] ?? 0 ) + (int) ( $by_status['pending'] ?? 0 );
	}

	/** Monochrome logo mark as a data URI for the top-level admin menu icon. */
	private static function menu_icon_uri(): string {
		$svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="#a7aaad" stroke-width="1.6">'
			. '<path d="M4 3.7h12a1.7 1.7 0 0 1 1.7 1.7v5.2a1.7 1.7 0 0 1-1.7 1.7h-5l-3.5 2.8v-2.8H4a1.7 1.7 0 0 1-1.7-1.7V5.4A1.7 1.7 0 0 1 4 3.7z"/>'
			. '<path d="M10 6.2c.4 2 .9 2.5 2.9 2.9-2 .4-2.5.9-2.9 2.9-.4-2-.9-2.5-2.9-2.9C9.1 8.7 9.6 8.2 10 6.2z" fill="#a7aaad" stroke="none"/>'
			. '</svg>';
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Inline SVG as a data URI for the admin menu icon; WordPress expects the base64 form.
		return 'data:image/svg+xml;base64,' . base64_encode( $svg );
	}

	/**
	 * Branded page header for our own admin screens: the logo mark + "Answyr"
	 * + the section name. Call in place of the plain <h1>.
	 */
	public static function admin_header( string $section ): void {
		printf(
			'<h1 class="answyr-admin-title"><img class="answyr-admin-logo" src="%1$s" alt="" width="26" height="26" />%2$s<span class="answyr-admin-section">&mdash; %3$s</span></h1>',
			esc_url( ANSWYR_URL . 'assets/logo.svg' ),
			esc_html( 'Answyr' ),
			esc_html( $section )
		);
	}

	/** Prefix the browser title on Answyr screens (incl. the Knowledge CPT). */
	public function brand_admin_title( string $admin_title, string $title ): string {
		unset( $title );
		if ( $this->is_answyr_screen() && 0 !== stripos( $admin_title, 'Answyr' ) ) {
			return 'Answyr - ' . $admin_title;
		}
		return $admin_title;
	}

	/** Whether the current admin screen belongs to Answyr. */
	private function is_answyr_screen(): bool {
		if ( ! function_exists( 'get_current_screen' ) ) {
			return false;
		}
		$screen = get_current_screen();
		if ( ! $screen ) {
			return false;
		}
		return false !== strpos( (string) $screen->id, 'answyr' )
			|| Answyr_Knowledge_Module::CPT === $screen->post_type
			|| Answyr_Knowledge_Module::TAX === $screen->taxonomy;
	}

	/** Top-level page: the Desk ticket inbox (or a prompt if Desk is off). */
	public function render_landing(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		if ( ! Answyr_Settings::module_enabled( 'desk' ) || ! class_exists( 'Answyr_Desk_Store' ) ) {
			echo '<div class="wrap answyr-settings">';
			self::admin_header( __( 'Tickets', 'answyr' ) );
			echo '<p>' . esc_html__( 'Enable the Desk module in Settings to manage tickets.', 'answyr' ) . '</p></div>';
			return;
		}
		// No AI key yet: nothing works, so show the checklist alone in place of the inbox.
		if ( Answyr_Onboarding::needs_setup() ) {
			echo '<div class="wrap answyr-settings">';
			self::admin_header( __( 'Tickets', 'answyr' ) );
			Answyr_Onboarding::render_checklist();
			echo '</div>';
			return;
		}
		// Key present but setup unfinished: show live progress above the inbox.
		if ( ! Answyr_Onboarding::onboarding_complete() ) {
			echo '<div class="wrap answyr-settings">';
			Answyr_Onboarding::render_checklist();
			echo '</div>';
		}
		require ANSWYR_PATH . 'inc/modules/desk/views/admin-inbox.php';
	}

	/** Insights: read-only ticket + chat metrics dashboard. */
	public function render_insights(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		// The metrics live in the Desk store (chat escalations included). Load it even
		// when the Desk module is off, so historical numbers still render.
		if ( ! class_exists( 'Answyr_Desk_Store' ) ) {
			$store = ANSWYR_PATH . 'inc/modules/desk/class-desk-store.php';
			if ( is_readable( $store ) ) {
				require_once $store;
			}
		}
		if ( ! class_exists( 'Answyr_Desk_Store' ) ) {
			return;
		}
		require ANSWYR_PATH . 'inc/modules/desk/views/admin-insights.php';
	}

	/** @return Answyr_Module[] */
	public function modules(): array {
		return $this->modules;
	}

	/**
	 * Activation: create tables for enabled modules and seed default options.
	 */
	public static function activate(): void {
		Answyr_Settings::seed_defaults();

		// Desk owns the ticket tables; create them on activation.
		require_once ANSWYR_PATH . 'inc/modules/desk/class-desk-store.php';
		Answyr_Desk_Store::install();

		// Chat conversation log (Pro; premium-only file — absent from the free build).
		$answyr_chat_store = ANSWYR_PATH . 'inc/modules/chat/class-chat-store.php';
		if ( is_readable( $answyr_chat_store ) ) {
			require_once $answyr_chat_store;
			Answyr_Chat_Store::install();
		}

		// Attachments (Pro; premium-only file — absent from the free build).
		$answyr_attachments = ANSWYR_PATH . 'inc/modules/desk/class-attachments.php';
		if ( is_readable( $answyr_attachments ) ) {
			require_once $answyr_attachments;
			Answyr_Attachments::install();
		}

		// Flag a one-time redirect to the setup screen (single-site activations).
		set_transient( 'answyr_activation_redirect', 1, 30 );

		flush_rewrite_rules();
	}
}
