<?php
/**
 * This file is part of the Wisewand WordPress plugin.
 *
 * @package Wisewand
 * @since 2.8.2
 * @license GPL-2.0-or-later
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
namespace Wisewand\Content;

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

use WP_Error;
use Wisewand\Utils\Options;
use Wisewand\Logging\Logger;

// phpcs:disable WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- error_log() used within WP_DEBUG conditionals; dynamic placeholder generation for IN clauses; table names derived from $wpdb->prefix are safe to interpolate; direct database calls required for custom plugin table operations

/**
 * Handles creation and updating of WordPress taxonomy terms from Wisewand payloads.
 *
 * Manages inserting new category/product-category terms, updating existing ones,
 * parsing category selection strings, determining target post types, and preparing
 * category descriptions from raw HTML content.
 *
 * @package Wisewand
 * @since 2.8.2
 */
class Category_Creator {

	/**
	 * Plugin options wrapper.
	 *
	 * @since 2.8.2
	 * @var Options
	 */
	private $options;

	/**
	 * Logger instance.
	 *
	 * @since 2.8.2
	 * @var Logger
	 */
	private $logger;

	/**
	 * Reference to Content_Manager for delegating extract_* calls.
	 *
	 * Set via set_content_manager() after construction to avoid circular
	 * constructor dependencies.
	 *
	 * @since 2.8.2
	 * @var Content_Manager|null
	 */
	private $content_manager = null;

	/**
	 * Constructor.
	 *
	 * @since 2.8.2
	 *
	 * @param Options $options Plugin options wrapper.
	 * @param Logger  $logger  Logger instance.
	 */
	public function __construct( Options $options, Logger $logger ) {
		$this->options = $options;
		$this->logger  = $logger;
	}

	/**
	 * Injects the Content_Manager reference for extract_title/content/excerpt delegation.
	 *
	 * Called after both objects are constructed to avoid circular dependencies.
	 *
	 * @since 2.8.2
	 *
	 * @param Content_Manager $manager The parent Content_Manager instance.
	 */
	public function set_content_manager( Content_Manager $manager ) {
		$this->content_manager = $manager;
	}

	/**
	 * Creates or updates a taxonomy term from the given payload.
	 *
	 * Determines the target taxonomy, extracts title and description, and
	 * either updates an existing term or inserts a new one, storing Wisewand
	 * metadata on the resulting term.
	 *
	 * @since 2.8.2
	 *
	 * @param array $payload Wisewand content payload.
	 * @return int|WP_Error Term ID on success, or WP_Error on failure.
	 */
	public function handle_category_creation( array $payload ) {
		// Determine taxonomy and category details
		$target_post_type = $payload['target_post_type'] ?? 'category';
		$taxonomy         = $this->get_taxonomy_for_post_type( $target_post_type );

		$category_name = $this->content_manager->extract_title( $payload );

		// For categories, use content but clean it for description field
		$full_content         = $this->content_manager->extract_content( $payload );
		$category_description = $this->prepare_category_description( $full_content, $payload );

		$this->logger->info(
			'Creating/updating category',
			array(
				'target_post_type'   => $target_post_type,
				'taxonomy'           => $taxonomy,
				'category_name'      => $category_name,
				'description_length' => strlen( $category_description ),
			)
		);

		// Check if updating existing category
		if ( ! empty( $payload['update_existing_category'] ) ) {
			return $this->update_existing_category( $payload, $taxonomy, $category_name, $category_description );
		}

		// Create new category
		$result = wp_insert_term(
			$category_name,
			$taxonomy,
			array(
				'description' => $category_description,
			)
		);

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		$category_id = $result['term_id'];

		// Store Wisewand metadata for the category
		update_term_meta( $category_id, '_wisewand_external_id', $payload['external_id'] );
		update_term_meta( $category_id, '_wisewand_processed_at', current_time( 'mysql' ) );
		update_term_meta( $category_id, '_wisewand_import_method', 'api_import' );

		// Store full content in meta for reference (since categories only have description)
		if ( ! empty( $payload['content'] ) ) {
			update_term_meta( $category_id, '_wisewand_full_content', $payload['content'] );
		}

		// Store SEO data for the category
		if ( ! empty( $payload['seo'] ) ) {
			update_term_meta( $category_id, '_wisewand_seo_data', $payload['seo'] );
		}

		$this->logger->info(
			'Created new category from Wisewand',
			array(
				'category_id'      => $category_id,
				'category_name'    => $category_name,
				'taxonomy'         => $taxonomy,
				'external_id'      => $payload['external_id'],
				'has_seo_data'     => ! empty( $payload['seo'] ),
				'has_full_content' => ! empty( $payload['content'] ),
			)
		);

		return $category_id;
	}

	/**
	 * Updates an existing taxonomy term with new name and description.
	 *
	 * Parses the category selection string, verifies the term exists, updates
	 * it, and refreshes Wisewand metadata.
	 *
	 * @since 2.8.2
	 *
	 * @param array  $payload              Wisewand content payload.
	 * @param string $taxonomy             Taxonomy slug (unused; resolved from selection).
	 * @param string $category_name        New term name.
	 * @param string $category_description New term description.
	 * @return int|WP_Error Term ID on success, or WP_Error on failure.
	 */
	public function update_existing_category( array $payload, $taxonomy, $category_name, $category_description ) {
		$category_info = $this->parse_existing_category_id( $payload['update_existing_category'] );

		if ( ! $category_info ) {
			return new WP_Error( 'invalid_category', 'Invalid existing category selection' );
		}

		$category_id     = $category_info['id'];
		$target_taxonomy = $category_info['taxonomy'];

		// Verify the category exists in the expected taxonomy
		$existing_term = get_term( $category_id, $target_taxonomy );
		if ( is_wp_error( $existing_term ) ) {
			return $existing_term;
		}

		$result = wp_update_term(
			$category_id,
			$target_taxonomy,
			array(
				'name'        => $category_name,
				'description' => $category_description,
			)
		);

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		// Update Wisewand metadata
		update_term_meta( $category_id, '_wisewand_external_id', $payload['external_id'] );
		update_term_meta( $category_id, '_wisewand_processed_at', current_time( 'mysql' ) );

		// Store SEO data for the category
		if ( ! empty( $payload['seo'] ) ) {
			update_term_meta( $category_id, '_wisewand_seo_data', $payload['seo'] );
		}

		$this->logger->info(
			'Updated existing category',
			array(
				'category_id'   => $category_id,
				'category_name' => $category_name,
				'taxonomy'      => $target_taxonomy,
				'external_id'   => $payload['external_id'],
			)
		);

		return $category_id;
	}

	/**
	 * Parses a Wisewand category selection string into an ID and taxonomy.
	 *
	 * Supports the "wp_cat_123" and "woo_cat_456" prefixed formats.
	 *
	 * @since 2.8.2
	 *
	 * @param string $category_selection The raw category selection string.
	 * @return array|null Associative array with 'id' (int) and 'taxonomy' (string),
	 *                    or null if the format is unrecognised.
	 */
	public function parse_existing_category_id( $category_selection ) {
		// Parse the category selection format: "wp_cat_123" or "woo_cat_456"
		if ( 0 === strpos( $category_selection, 'wp_cat_' ) ) {
			return array(
				'id'       => (int) str_replace( 'wp_cat_', '', $category_selection ),
				'taxonomy' => 'category',
			);
		} elseif ( 0 === strpos( $category_selection, 'woo_cat_' ) ) {
			return array(
				'id'       => (int) str_replace( 'woo_cat_', '', $category_selection ),
				'taxonomy' => 'product_cat',
			);
		}

		return null;
	}

	/**
	 * Cleans and truncates HTML content for use as a taxonomy term description.
	 *
	 * Falls back to the payload excerpt if content is empty, or to stripped
	 * plain text if Content_Cleaner is unavailable.
	 *
	 * @since 2.8.2
	 *
	 * @param string $content Raw HTML content.
	 * @param array  $payload Wisewand content payload (used for excerpt fallback).
	 * @return string Sanitised description string.
	 */
	public function prepare_category_description( $content, array $payload ) {
		// Clean content for use as category description
		if ( empty( $content ) ) {
			return $this->content_manager->extract_excerpt( $payload );
		}

		// Load Content_Cleaner for processing
		if ( ! class_exists( 'Wisewand\\Utils\\Content_Cleaner' ) ) {
			$cleaner_file = WISEWAND_PLUGIN_DIR . 'includes/Utils/Content_Cleaner.php';
			if ( file_exists( $cleaner_file ) ) {
				require_once $cleaner_file;
			}
		}

		if ( class_exists( 'Wisewand\\Utils\\Content_Cleaner' ) ) {
			// Clean and strip HTML for category description
			$cleaned_content = \Wisewand\Utils\Content_Cleaner::clean_content(
				$content,
				array(
					'remove_h1'              => true,
					'fix_headings'           => false, // Keep original structure
					'clean_empty_paragraphs' => true,
					'fix_encoded_html'       => true,
				)
			);

			// For categories, we can include some HTML in descriptions
			// But remove complex elements and keep it clean
			$description = wp_kses(
				$cleaned_content,
				array(
					'p'      => array(),
					'br'     => array(),
					'strong' => array(),
					'em'     => array(),
					'b'      => array(),
					'i'      => array(),
					'a'      => array(
						'href'  => true,
						'title' => true,
					),
					'ul'     => array(),
					'ol'     => array(),
					'li'     => array(),
				)
			);

			// Limit length for category descriptions (WordPress recommendation)
			if ( strlen( $description ) > 1000 ) {
				$description = wp_trim_words( $description, 100 ) . '...';
			}

			return $description;
		}

		// Fallback: strip all HTML
		return wp_strip_all_tags( $content );
	}

	/**
	 * Determines the target post type from the Wisewand content payload.
	 *
	 * Checks for an explicit override, then maps content_type_source to a
	 * WordPress post type. Returns 'category' for category pages and 'product'
	 * for product pages when WooCommerce is active.
	 *
	 * @since 2.8.2
	 *
	 * @param array $payload Wisewand content payload.
	 * @return string WordPress post type slug.
	 */
	public function determine_target_post_type( array $payload ) {
		// Check for user override first
		if ( ! empty( $payload['target_post_type'] ) ) {
			return $payload['target_post_type'];
		}

		// Smart defaults based on content type
		$content_type = $payload['content_type_source'] ?? 'articles';

		switch ( $content_type ) {
			case 'articles':
			case 'discoverarticles': // Same as articles
				return 'post';
			case 'categorypages':
				return 'category'; // Special handling for categories
			case 'productpages':
				return class_exists( 'WooCommerce' ) ? 'product' : 'post';
			default:
				return 'post';
		}
	}

	/**
	 * Checks the payload for an existing post/product ID to update instead of create.
	 *
	 * Returns the post ID if a valid product update target is found.
	 * Category update targets are handled separately via handle_category_creation().
	 *
	 * @since 2.8.2
	 *
	 * @param array $payload Wisewand content payload.
	 * @return int|null WordPress post ID if an update target is found, null otherwise.
	 */
	public function find_existing_content_for_upgmdate( array $payload ) {
		// Check if user wants to update existing content
		if ( ! empty( $payload['update_existing_product'] ) ) {
			$existing_post = get_post( $payload['update_existing_product'] );
			return ( $existing_post && 'product' === $existing_post->post_type ) ? $existing_post->ID : null;
		}

		if ( ! empty( $payload['update_existing_category'] ) ) {
			// For categories, we'll handle this differently as they're taxonomies, not posts
			return null;
		}

		return null;
	}

	/**
	 * Returns the taxonomy slug for a given post type slug.
	 *
	 * Maps 'product_cat' to WooCommerce product categories; all other types
	 * map to the standard WordPress 'category' taxonomy.
	 *
	 * @since 2.8.2
	 *
	 * @param string $post_type Post type slug.
	 * @return string Taxonomy slug.
	 */
	public function get_taxonomy_for_post_type( $post_type ) {
		switch ( $post_type ) {
			case 'product_cat':
				return 'product_cat'; // WooCommerce product categories
			case 'category':
			default:
				return 'category'; // WordPress categories
		}
	}
}
