<?php

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

class Niwa_AI_Index_Sync {
    const SITE_REINDEX_HOOK = 'niwa_ai_site_knowledge_reindex_deferred';

	/** @var array<int,bool> Variation deletes already handled by a Woo hook. */
	private $handled_variation_deletes = array();
	/** @var array<int,bool> Parent products awaiting a final-state reindex. */
	private $pending_product_reindexes = array();
	/** @var bool Whether the shutdown flush has been registered. */
	private $reindex_flush_registered = false;

    public function register_hooks() {
        add_action( 'save_post', array( $this, 'on_content_upsert' ), 10, 3 );
        add_action( 'transition_post_status', array( $this, 'on_transition_post_status' ), 10, 3 );
        add_action( 'before_delete_post', array( $this, 'on_content_delete' ), 10, 1 );
		add_action( 'woocommerce_save_product_variation', array( $this, 'on_product_variation_save' ), 10, 2 );
		add_action( 'woocommerce_new_product_variation', array( $this, 'on_product_variation_save' ), 10, 1 );
		add_action( 'woocommerce_update_product_variation', array( $this, 'on_product_variation_save' ), 10, 1 );
		add_action( 'woocommerce_before_delete_product_variation', array( $this, 'on_product_variation_delete' ), 10, 1 );
		add_action( 'woocommerce_trash_product_variation', array( $this, 'on_product_variation_delete' ), 10, 1 );
		add_action( 'set_object_terms', array( $this, 'on_product_terms_set' ), 10, 6 );
        add_action( 'updated_option', array( $this, 'on_option_update' ), 10, 3 );
        add_action( 'wp_update_nav_menu', array( $this, 'on_nav_menu_update' ), 10, 1 );
        add_action( 'wp_update_nav_menu_item', array( $this, 'on_nav_menu_update' ), 10, 1 );
        add_action( 'acf/save_post', array( $this, 'on_acf_save_post' ), 10, 1 );
        add_action( 'customize_save_after', array( $this, 'on_customize_save' ), 10, 1 );
        add_action( 'update_option_page_on_front', array( $this, 'on_home_page_option_change' ), 10, 2 );
        add_action( 'update_option_blogname', array( $this, 'on_home_page_option_change' ), 10, 2 );
        add_action( 'update_option_blogdescription', array( $this, 'on_home_page_option_change' ), 10, 2 );
        add_action( self::SITE_REINDEX_HOOK, array( $this, 'run_site_knowledge_reindex' ) );
    }

    public function on_content_upsert( $post_id, $post, $update ) {
        if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
            return;
        }

        if ( ! is_object( $post ) || empty( $post->post_type ) ) {
            return;
        }

        if ( ! in_array( $post->post_type, $this->get_tracked_post_types(), true ) ) {
            return;
        }

        // Template-like types use non-publish statuses (private, inherit, draft) and should always be indexed.
        $always_index_types = array( 'wp_template', 'wp_template_part', 'wp_block', 'wp_navigation', 'elementor_library' );
        if ( 'publish' !== $post->post_status && ! in_array( $post->post_type, $always_index_types, true ) ) {
            do_action( 'niwa_ai_index_delete', $post_id, $post->post_type );
            return;
        }

        do_action( 'niwa_ai_index_upsert', $post_id, $post->post_type, $update );
    }

    public function on_transition_post_status( $new_status, $old_status, $post ) {
        if ( ! is_object( $post ) || empty( $post->ID ) || empty( $post->post_type ) ) {
            return;
        }

        if ( $new_status === $old_status ) {
            return;
        }

        if ( ! in_array( $post->post_type, $this->get_tracked_post_types(), true ) ) {
            return;
        }

        $always_index_types = array( 'wp_template', 'wp_template_part', 'wp_block', 'wp_navigation', 'elementor_library' );
        if ( 'publish' === $new_status || in_array( $post->post_type, $always_index_types, true ) ) {
            do_action( 'niwa_ai_index_upsert', intval( $post->ID ), $post->post_type, true );
            return;
        }

        do_action( 'niwa_ai_index_delete', intval( $post->ID ), $post->post_type );
    }

    public function on_content_delete( $post_id ) {
        $post = get_post( $post_id );
        if ( ! $post ) {
            return;
        }

		if ( 'product_variation' === (string) $post->post_type ) {
			$post_id = intval( $post_id );
			if ( ! isset( $this->handled_variation_deletes[ $post_id ] ) ) {
				$this->handled_variation_deletes[ $post_id ] = true;
				$this->enqueue_product_reindex( isset( $post->post_parent ) ? intval( $post->post_parent ) : 0 );
			}
			return;
		}

        if ( ! in_array( $post->post_type, $this->get_tracked_post_types(), true ) ) {
            return;
        }

        do_action( 'niwa_ai_index_delete', $post_id, $post->post_type );
    }

	/**
	 * Reindex the owning product after WooCommerce persists a variation.
	 *
	 * @param int $variation_id Variation post ID.
	 * @param int $loop_index   Variation loop index (unused).
	 * @return void
	 */
	public function on_product_variation_save( $variation_id, $loop_index = 0 ) {
		$this->enqueue_owning_product( $variation_id );
	}

	/**
	 * Capture the parent before WooCommerce removes the variation post.
	 *
	 * @param int|WC_Product_Variation $variation Variation ID or object.
	 * @return void
	 */
	public function on_product_variation_delete( $variation ) {
		$variation_id = is_object( $variation ) && method_exists( $variation, 'get_id' )
			? intval( $variation->get_id() )
			: intval( $variation );
		if ( $variation_id <= 0 || isset( $this->handled_variation_deletes[ $variation_id ] ) ) {
			return;
		}

		$this->handled_variation_deletes[ $variation_id ] = true;
		$this->enqueue_owning_product( $variation );
	}

	/**
	 * Reindex product evidence after product category or global attribute term
	 * relationships change. This hook only schedules the existing local index
	 * path; it performs no LLM or external API work.
	 *
	 * @param int          $object_id   Object ID.
	 * @param array|string $terms       Terms supplied by WordPress (unused).
	 * @param array        $tt_ids      Term-taxonomy IDs (unused).
	 * @param string       $taxonomy    Taxonomy name.
	 * @param bool         $append      Append flag (unused).
	 * @param array        $old_tt_ids  Previous term-taxonomy IDs (unused).
	 * @return void
	 */
	public function on_product_terms_set( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) {
		$taxonomy = (string) $taxonomy;
		if ( 'product_cat' !== $taxonomy && 0 !== strpos( $taxonomy, 'pa_' ) ) {
			return;
		}

		$post_type = function_exists( 'get_post_type' ) ? (string) get_post_type( intval( $object_id ) ) : '';
		if ( '' === $post_type ) {
			$post = get_post( intval( $object_id ) );
			$post_type = $post && isset( $post->post_type ) ? (string) $post->post_type : '';
		}
		if ( 'product' === $post_type ) {
			$this->enqueue_product_reindex( intval( $object_id ) );
			return;
		}
		if ( 'product_variation' === $post_type ) {
			$this->enqueue_owning_product( intval( $object_id ) );
		}
	}

	private function enqueue_owning_product( $variation ) {
		$variation_id = is_object( $variation ) && method_exists( $variation, 'get_id' )
			? intval( $variation->get_id() )
			: intval( $variation );
		$parent_id = is_object( $variation ) && method_exists( $variation, 'get_parent_id' )
			? intval( $variation->get_parent_id() )
			: 0;
		if ( $parent_id <= 0 && $variation_id > 0 && function_exists( 'wp_get_post_parent_id' ) ) {
			$parent_id = intval( wp_get_post_parent_id( $variation_id ) );
		}
		if ( $parent_id <= 0 && $variation_id > 0 ) {
			$post = get_post( $variation_id );
			$parent_id = $post && isset( $post->post_parent ) ? intval( $post->post_parent ) : 0;
		}

		$this->enqueue_product_reindex( $parent_id );
	}

	private function enqueue_product_reindex( $product_id ) {
		$product_id = intval( $product_id );
		if ( $product_id <= 0 ) {
			return;
		}
		$this->pending_product_reindexes[ $product_id ] = true;
		if ( ! $this->reindex_flush_registered && function_exists( 'add_action' ) ) {
			$this->reindex_flush_registered = true;
			add_action( 'shutdown', array( $this, 'flush_product_reindexes' ), 20 );
		}
	}

	/**
	 * Reindex each affected parent once, after WooCommerce has finished writing
	 * all variation/meta/taxonomy state for the request.
	 *
	 * @return void
	 */
	public function flush_product_reindexes() {
		$product_ids = array_map( 'intval', array_keys( $this->pending_product_reindexes ) );
		$this->pending_product_reindexes = array();
		$this->reindex_flush_registered  = false;

		foreach ( $product_ids as $product_id ) {
			if ( class_exists( 'Niwa_AI_Product_Search_Evidence' ) && method_exists( 'Niwa_AI_Product_Search_Evidence', 'invalidate' ) ) {
				Niwa_AI_Product_Search_Evidence::invalidate( $product_id );
			}
			do_action( 'niwa_ai_index_upsert', $product_id, 'product', true );
		}
	}

    public function on_option_update( $option, $old_value, $value ) {
        if ( ! is_string( $option ) || '' === $option ) {
            return;
        }

        $site_knowledge_options = array(
            'sidebars_widgets',
            Niwa_AI_Constants::LEGACY_OPT_AGENT_PROFILE,
            Niwa_AI_Constants::OPT_AGENT_PROFILE,
        );

        if ( 0 === strpos( $option, 'widget_text' )
            || 0 === strpos( $option, 'widget_custom_html' )
            || 0 === strpos( $option, 'widget_block' )
            || 0 === strpos( $option, 'theme_mods_' )
            || 0 === strpos( $option, 'elementor_' )
            || in_array( $option, $site_knowledge_options, true ) ) {
            $this->schedule_site_knowledge_reindex();
        }
    }

    public function on_nav_menu_update( $menu_id ) {
        $this->schedule_site_knowledge_reindex();
    }

    public function on_acf_save_post( $post_id ) {
        if ( 'options' === $post_id || 'option' === $post_id ) {
            $this->schedule_site_knowledge_reindex();
        }
    }

    public function on_customize_save( $manager ) {
        $this->schedule_site_knowledge_reindex();
    }

    public function on_home_page_option_change( $old_value, $value ) {
        if ( $old_value !== $value ) {
            $this->schedule_site_knowledge_reindex();
        }
    }

    private function schedule_site_knowledge_reindex() {
        if ( ! wp_next_scheduled( self::SITE_REINDEX_HOOK ) ) {
            wp_schedule_single_event( time() + 15, self::SITE_REINDEX_HOOK );
        }
    }

    public function run_site_knowledge_reindex() {
        do_action( 'niwa_ai_index_upsert', 0, 'site_knowledge', true );
    }

    private function get_tracked_post_types() {
        $types = array(
            'page',
            'post',
            'product',
            'elementor_library',
            'wp_template',
            'wp_template_part',
            'wp_block',
            'wp_navigation',
        );

        return apply_filters( 'niwa_ai_index_post_types', $types );
    }
}
