<?php
/**
 * Main plugin class.
 *
 * Handles plugin initialization, loading core components,
 * and orchestrating all plugin functionality.
 *
 * @package Antimanual_Builder
 * @since   1.0.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class AMBLD_Antimanual_Builder
 *
 * Singleton class that bootstraps the entire plugin.
 *
 * @since 1.0.0
 */
class AMBLD_Antimanual_Builder {

	/**
	 * Singleton instance.
	 *
	 * @since  1.0.0
	 * @var    AMBLD_Antimanual_Builder|null
	 */
	private static $instance = null;

	/**
	 * Admin handler instance.
	 *
	 * @since  1.0.0
	 * @var    AMBLD_Admin_Admin|null
	 */
	private $admin = null;

	/**
	 * REST API handler instance.
	 *
	 * @since  1.0.0
	 * @var    AMBLD_API_Rest_Api|null
	 */
	private $api = null;

	/**
	 * Renderer instance.
	 *
	 * @since  1.0.0
	 * @var    AMBLD_Render_Renderer|null
	 */
	private $renderer = null;

	/**
	 * Get the singleton instance.
	 *
	 * @since  1.0.0
	 * @return AMBLD_Antimanual_Builder
	 */
	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Constructor. Sets up the plugin.
	 *
	 * @since 1.0.0
	 */
	private function __construct() {
		$this->register_post_types();
		$this->init_components();
		$this->register_hooks();
	}



	/**
	 * Register builder post types and integrations.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	private function register_post_types() {
		new AMBLD_Page_Integration();
		new AMBLD_Post_Type_Builder_Component();
		new AMBLD_Post_Type_Builder_Template();
	}

	/**
	 * Initialize core components.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	private function init_components() {
		$this->renderer = new AMBLD_Render_Renderer();

		if ( is_admin() ) {
			$this->admin = new AMBLD_Admin_Admin();
		}

		$this->api = new AMBLD_API_Rest_Api();

		// Initialize asset generator.
		new AMBLD_Render_Asset_Generator();

		// Initialize template loader for frontend.
		new AMBLD_Render_Template_Loader();

		// Register the RAG indexer's save_post hooks. The indexer is a no-op
		// if the parent Antimanual embedding service is unavailable, so this
		// is safe to wire on every load.
		if ( class_exists( 'AMBLD_Rag_Indexer' ) ) {
			$indexer = new AMBLD_Rag_Indexer();
			$indexer->register();

			// Register the cron handler that drives the shipped-corpus
			// background embed. Hook is named on the indexer constant so
			// admin tooling and tests can target it without hardcoding.
			add_action( AMBLD_Rag_Indexer::CRON_HOOK, array( 'AMBLD_Rag_Indexer', 'run_seed_embed_cron' ) );
		}

		// Idempotent schema migration. The activator only runs on a fresh
		// activate, so existing installs upgrading to a build that adds a
		// new table won't pick it up. Gate via wp_options so we run dbDelta
		// at most once per schema version.
		if ( class_exists( 'AMBLD_Rag_Store' ) ) {
			// '2' adds helpfulness / use_count / last_used_at for the behavior
			// feedback rerank. dbDelta is a no-op when columns already exist.
			$schema_version = '2';
			if ( get_option( 'ambld_rag_schema_version' ) !== $schema_version ) {
				AMBLD_Rag_Store::create_table();
				update_option( 'ambld_rag_schema_version', $schema_version, false );
			}
		}

		// Ensure the ambld_assets table exists. The activator creates it on a
		// fresh install, but sites that installed before the table was added
		// (or where the table was accidentally dropped) never get it unless
		// we run dbDelta here too. Using a lightweight SHOW TABLES check so
		// we skip the require_once / dbDelta on every request once the table
		// is present.
		global $wpdb;
		$assets_table = $wpdb->prefix . 'ambld_assets';
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$table_exists = (bool) $wpdb->get_var( "SHOW TABLES LIKE '{$assets_table}'" );
		if ( ! $table_exists ) {
			require_once ABSPATH . 'wp-admin/includes/upgrade.php';
			$charset_collate = $wpdb->get_charset_collate();
			$sql = "CREATE TABLE IF NOT EXISTS {$assets_table} (
				id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
				post_id bigint(20) unsigned NOT NULL,
				asset_type varchar(10) NOT NULL DEFAULT 'css',
				file_path varchar(500) NOT NULL,
				file_url varchar(500) NOT NULL,
				version varchar(32) NOT NULL,
				created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
				updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
				PRIMARY KEY (id),
				KEY post_id (post_id),
				KEY asset_type (asset_type)
			) {$charset_collate};";
			dbDelta( $sql );
		}
	}

	/**
	 * Register plugin-wide hooks.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	private function register_hooks() {
		// Add settings link on plugins page.
		add_filter( 'plugin_action_links_' . AMBLD_PLUGIN_BASENAME, array( $this, 'add_plugin_links' ) );

		// One-time rewrite rules flush for existing installs (post types must be registered first).
		add_action( 'init', array( $this, 'maybe_flush_rewrite_rules' ), 99 );

		// Fix cURL timeouts for local environments by enforcing IPv4 explicitly in WordPress HTTP API.
		add_action( 'http_api_curl', array( $this, 'fix_curl_timeouts' ), 10, 1 );

		// Add "Edit with AM Builder" link to the frontend admin bar.
		add_action( 'admin_bar_menu', array( $this, 'add_frontend_admin_bar_link' ), 80 );

		// RAG behavior feedback — when the user saves an AMB-managed post,
		// promote the most-recent still-pending turn for that post to
		// `accepted` and bump helpfulness on each of its retrieved sources.
		// Late priority (30) so we run after the builder's own meta save
		// pipeline (priorities 10–20) finished writing _ambld_* meta.
		add_action( 'save_post_page', array( $this, 'maybe_mark_turn_accepted' ), 30, 3 );
		add_action( 'save_post_ambld_template', array( $this, 'maybe_mark_turn_accepted' ), 30, 3 );
		add_action( 'save_post_ambld_component', array( $this, 'maybe_mark_turn_accepted' ), 30, 3 );
	}

	/**
	 * Mark the most recent pending agent turn for a saved post as `accepted`
	 * and bump helpfulness on each of its retrieved sources.
	 *
	 * Skips revisions, autosaves, and non-builder pages so a random page
	 * save doesn't reach back and flip a turn that belonged to a different
	 * post. The harness only records a `post_id` when one was passed in the
	 * chat request, so the lookup is always scoped.
	 *
	 * @since  1.7.0
	 * @param  int     $post_id Post id being saved.
	 * @param  WP_Post $post    Post object.
	 * @param  bool    $update  Whether this is an update.
	 * @return void
	 */
	public function maybe_mark_turn_accepted( $post_id, $post, $update ) {
		unset( $update );
		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
			return;
		}
		if ( ! class_exists( 'AMBLD_Agent_Turn_Logger' ) || ! class_exists( 'AMBLD_Rag_Store' ) ) {
			return;
		}
		// For 'page' saves, only act when this is actually a builder-managed
		// page — vanilla WP pages shouldn't touch the feedback log.
		if ( $post instanceof \WP_Post && 'page' === $post->post_type ) {
			if ( ! class_exists( 'AMBLD_Page_Integration' ) || ! AMBLD_Page_Integration::is_builder_page( $post ) ) {
				return;
			}
		}

		$turn = AMBLD_Agent_Turn_Logger::mark_last_turn_outcome( $post_id, 'accepted' );
		if ( ! is_array( $turn ) || empty( $turn['sources'] ) || ! is_array( $turn['sources'] ) ) {
			return;
		}
		foreach ( $turn['sources'] as $src ) {
			if ( empty( $src['type'] ) || empty( $src['id'] ) ) {
				continue;
			}
			AMBLD_Rag_Store::bump_helpfulness( (string) $src['type'], (string) $src['id'], 1 );
		}
	}

	/**
	 * Fix cURL timeouts for local environments.
	 * Forces IPv4 and disables SSL verification for external AI API requests.
	 *
	 * @since  1.0.0
	 * @param  resource $handle cURL handle.
	 * @return void
	 */
	public function fix_curl_timeouts( $handle ) {
		if ( function_exists( 'curl_setopt' ) ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.curl_curl_setopt -- WordPress passes the live cURL handle to this hook specifically for low-level transport tuning.
			curl_setopt( $handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 );
		}
	}

	/**
	 * Flush rewrite rules once after post types are registered.
	 *
	 * This catches existing installations where the rewrite rules
	 * were never properly flushed after post type registration.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public function maybe_flush_rewrite_rules() {
		if ( ! get_option( 'ambld_rewrite_flushed' ) ) {
			flush_rewrite_rules();
			update_option( 'ambld_rewrite_flushed', true );
		}
	}

	/**
	 * Add "Edit with AM Builder" node to the frontend admin bar.
	 *
	 * Only fires on singular pages managed by the builder when the current
	 * user has permission to edit the page.
	 *
	 * @since  1.0.0
	 * @param  WP_Admin_Bar $wp_admin_bar Admin bar instance.
	 * @return void
	 */
	public function add_frontend_admin_bar_link( $wp_admin_bar ) {
		if ( is_admin() ) {
			return;
		}

		if ( ! is_singular( AMBLD_Page_Integration::POST_TYPE ) ) {
			return;
		}

		$post = get_queried_object();
		if ( ! ( $post instanceof WP_Post ) ) {
			return;
		}

		if ( ! AMBLD_Page_Integration::is_builder_page( $post ) ) {
			return;
		}

		if ( ! current_user_can( 'edit_post', $post->ID ) ) {
			return;
		}

		$wp_admin_bar->add_node(
			array(
				'id'    => 'ambld-edit-with-builder',
				'title' => esc_html__( 'Edit with AM Builder', 'antimanual-builder' ),
				'href'  => esc_url( admin_url( 'admin.php?page=ambld-editor&post_id=' . absint( $post->ID ) ) ),
				'meta'  => array(
					'class' => 'ambld-admin-bar-node',
				),
			)
		);
	}

	/**
	 * Add action links to the plugins page.
	 *
	 * @since  1.0.0
	 * @param  array $links Existing action links.
	 * @return array Modified action links.
	 */
	public function add_plugin_links( $links ) {
		$plugin_links = array(
			'<a href="' . esc_url( admin_url( 'admin.php?page=ambld-editor' ) ) . '">' . esc_html__( 'Builder', 'antimanual-builder' ) . '</a>',
			'<a href="' . esc_url( admin_url( 'admin.php?page=ambld-settings' ) ) . '">' . esc_html__( 'Settings', 'antimanual-builder' ) . '</a>',
		);

		return array_merge( $plugin_links, $links );
	}

	/**
	 * Get the admin handler.
	 *
	 * @since  1.0.0
	 * @return AMBLD_Admin_Admin|null
	 */
	public function get_admin() {
		return $this->admin;
	}

	/**
	 * Get the REST API handler.
	 *
	 * @since  1.0.0
	 * @return AMBLD_API_Rest_Api|null
	 */
	public function get_api() {
		return $this->api;
	}

	/**
	 * Get the renderer.
	 *
	 * @since  1.0.0
	 * @return AMBLD_Render_Renderer|null
	 */
	public function get_renderer() {
		return $this->renderer;
	}
}
