<?php

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

trait Niwa_AI_Tools_Registry {

    public function get_stock_manager_tool_definitions() {
        $allowed = array(
            'extract_partner_product_from_url',
            'clone_partner_variable_product_from_url',
            'set_product_featured_image_from_url',
            'upload_image_to_media',
            'generate_image',
            'restore_product_from_trash',
            'update_simple_product',
            'create_product',
            'bulk_update_catalog_prices',
            'update_product_variation',
            'set_variation_sale_schedule',
            'add_product_variation',
            'bulk_update_variations',
            'remove_product_variation',
            'manage_product_attributes',
            'remove_color',
            'move_to_outlet',
            'manage_product_gallery',
            'inventory_cleanup',
            'bulk_set_individual_prices',
        );

        $all_tools = $this->get_tool_definitions();
        $filtered  = array();

        foreach ( $all_tools as $tool ) {
            $name = isset( $tool['function']['name'] ) ? (string) $tool['function']['name'] : '';
            if ( ! in_array( $name, $allowed, true ) ) {
                continue;
            }
            $filtered[] = $tool;
        }

        // Add product-only trash tools (post_type locked to 'product').
        $filtered[] = $this->build_function_tool(
            'trash_recent_posts',
            'Move the most recent WooCommerce products to trash.',
            array(
                'type'       => 'object',
                'properties' => array(
                    'post_type' => array(
                        'type'        => 'string',
                        'enum'        => array( 'product' ),
                        'description' => 'Always product for stock managers.',
                    ),
                    'count' => array(
                        'type'        => 'integer',
                        'minimum'     => 1,
                        'maximum'     => self::MAX_TRASH_POSTS,
                        'description' => 'How many recent products to trash.',
                    ),
                ),
                'required' => array( 'post_type', 'count' ),
            )
        );

        $filtered[] = $this->build_function_tool(
            'trash_posts_by_query',
            'Move WooCommerce products matching a title search query to trash.',
            array(
                'type'       => 'object',
                'properties' => array(
                    'post_type' => array(
                        'type'        => 'string',
                        'enum'        => array( 'product' ),
                        'description' => 'Always product for stock managers.',
                    ),
                    'search_query' => array(
                        'type'        => 'string',
                        'description' => 'Title search query.',
                    ),
                    'count' => array(
                        'type'        => 'integer',
                        'minimum'     => 1,
                        'maximum'     => self::MAX_TRASH_POSTS,
                        'description' => 'Maximum number of matching products to trash.',
                    ),
                ),
                'required' => array( 'post_type', 'search_query' ),
            )
        );

        return $filtered;
    }

    public function get_tool_definitions() {
        return array(
            $this->build_function_tool(
                'generate_image',
                'Generate a new PNG image with GPT Image 2 through the configured Codex OAuth connection, save it into WordPress Media Library, and return the attachment URL. Use for requested product/blog/marketing visuals when the founder asks to create or generate an image. Each call takes 1-3 minutes: for multi-image tasks generate ONE image per turn, attach it to its target gallery immediately, then continue the rest in the next turn. Calling again with the same title within 24h returns the already generated attachment instead of generating a new one (set force_regenerate=true to override).',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'prompt' => array(
                            'type'        => 'string',
                            'description' => 'Detailed image prompt. Include subject, style, composition, lighting, background, and any text constraints.',
                        ),
                        'title' => array(
                            'type'        => 'string',
                            'description' => 'Media Library title for the generated image.',
                        ),
                        'alt_text' => array(
                            'type'        => 'string',
                            'description' => 'Alt text for accessibility and SEO.',
                        ),
                        'quality' => array(
                            'type'        => 'string',
                            'enum'        => array( 'low', 'medium', 'high' ),
                            'description' => 'Optional override. If omitted, Personal Assistant settings decide the default quality.',
                        ),
                        'size' => array(
                            'type'        => 'string',
                            'enum'        => array( '1024x1024', '1536x1024', '1024x1536' ),
                            'description' => 'Optional image size. Default square 1024x1024.',
                        ),
                        'reference_image_urls' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'REQUIRED whenever the image must depict an EXISTING store product: pass the product\'s real image URL (from woo_product_lookup) so the generator reproduces the exact product. Without a reference the generator INVENTS a generic product that does not match the store item. Omit only for images that do not depict an existing product.',
                        ),
                        'parent_post_id' => array(
                            'type'        => 'integer',
                            'description' => 'Post/page/product ID to attach the generated image to. ALWAYS pass the product ID when the image is for a product: it also auto-fills the product\'s featured image as visual reference when reference_image_urls is empty.',
                        ),
                        'force_regenerate' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true ONLY when the founder explicitly wants a new version although an attachment with the same title was generated within the last 24h. Default false reuses the existing attachment.',
                        ),
                    ),
                    'required'   => array( 'prompt', 'title' ),
                )
            ),
            $this->build_function_tool(
                'extract_partner_product_from_url',
                'Inspect structured product data from a partner/manufacturer/distributor product URL. Use for read-only inspection or mapping review; use clone_partner_variable_product_from_url for an actual copy/import/replace write.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'url' => array(
                            'type'        => 'string',
                            'description' => 'Public partner product page URL.',
                        ),
                        'preferred_language' => array(
                            'type'        => 'string',
                            'description' => 'Target language for downstream rewrite/localization. Default: sr.',
                        ),
                        'max_images' => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => 30,
                            'description' => 'Maximum number of product/gallery images to return.',
                        ),
                        'include_mapping' => array(
                            'type'        => 'boolean',
                            'description' => 'Whether to include local category/brand/tag mapping suggestions. Default true.',
                        ),
                    ),
                    'required' => array( 'url' ),
                )
            ),
            $this->build_function_tool(
                'clone_partner_variable_product_from_url',
                'Clone a partner variable product from a public product URL in one verified operation. Owns the copy/import/replace workflow, imports product images plus variation-specific featured images and galleries when present, creates attributes and variations, verifies the clone, then archives the old local product only after replacement is complete. Preserves this site categories by default.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'source_url' => array(
                            'type'        => 'string',
                            'description' => 'Public source product page URL.',
                        ),
                        'target_product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Existing local product ID to replace or update.',
                        ),
                        'target_sku' => array(
                            'type'        => 'string',
                            'description' => 'Existing local SKU to replace, or final SKU for create_new.',
                        ),
                        'mode' => array(
                            'type'        => 'string',
                            'enum'        => array( 'replace_existing', 'create_new', 'update_existing' ),
                            'description' => 'replace_existing stages a new product and archives the old one after verification. create_new creates only a new product. update_existing writes onto the existing product.',
                        ),
                        'preserve_local_categories' => array(
                            'type'        => 'boolean',
                            'description' => 'Keep categories from the existing local product instead of copying source categories. Default true.',
                        ),
                        'publish_after_verification' => array(
                            'type'        => 'boolean',
                            'description' => 'Publish the staged clone after verification. Default true.',
                        ),
                        'attribute_term_labels' => array(
                            'type'        => 'object',
                            'description' => 'Optional LLM/browser reviewed display labels for attribute terms, keyed by taxonomy and stable slug. Example: {"pa_boja":{"siva-en":"Siva","maslinasto-zelena-en":"Maslinasto zelena"}}. Slugs remain unchanged for Woo matching; only frontend term names are cleaned.',
                            'additionalProperties' => true,
                        ),
                        'dynamic_variant_manifest' => array(
                            'type'        => 'object',
                            'description' => 'Optional browser-clicked variant evidence manifest for dynamic product pages. Include attributes, optional attribute_labels, featured_image, gallery_images, confidence, and evidence/event per variant. The cloner validates and writes this manifest into Woo variation gallery metadata.',
                            'additionalProperties' => true,
                        ),
                    ),
                    'required' => array( 'source_url' ),
                )
            ),
            $this->build_function_tool(
                'trash_recent_posts',
                'Move the most recent blog posts/pages/products to trash. Prefer this for requests like "delete last two blog posts".',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'post_type' => array(
                            'type'        => 'string',
                            'enum'        => array( 'post', 'page', 'product' ),
                            'description' => 'WordPress post type. Use post for blog posts.',
                        ),
                        'count'     => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => self::MAX_TRASH_POSTS,
                            'description' => 'How many recent items to trash.',
                        ),
                    ),
                    'required'   => array( 'post_type', 'count' ),
                )
            ),
            $this->build_function_tool(
                'trash_posts_by_query',
                'Move posts/pages/products matching a title search query to trash.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'post_type'    => array(
                            'type'        => 'string',
                            'enum'        => array( 'post', 'page', 'product' ),
                            'description' => 'WordPress post type.',
                        ),
                        'search_query' => array(
                            'type'        => 'string',
                            'description' => 'Title search query.',
                        ),
                        'count'        => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => self::MAX_TRASH_POSTS,
                            'description' => 'Maximum number of matching items to trash.',
                        ),
                    ),
                    'required'   => array( 'post_type', 'search_query' ),
                )
            ),
            $this->build_function_tool(
                'set_product_featured_image_from_url',
                'Set WooCommerce product featured image from a public image URL or from an already uploaded Telegram/media attachment. IMPORTANT: When multiple products have similar names, ALWAYS use product_id (not product_query) to avoid matching the wrong product. Search first with wp_search_posts if unsure.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact WooCommerce product post ID. PREFERRED over product_query to avoid ambiguous matches.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Product name, slug, SKU, or ID to match. Use only when product_id is not known.',
                        ),
                        'image_url'     => array(
                            'type'        => 'string',
                            'description' => 'Public image URL to import and set as featured image. Optional when attachment_id is already known from the current Telegram image.',
                        ),
                        'attachment_id' => array(
                            'type'        => 'integer',
                            'description' => 'Optional existing WordPress attachment ID to use directly as the featured image.',
                        ),
                    ),
                    'required'   => array(),
                )
            ),
            $this->build_function_tool(
                'upload_image_to_media',
                'Upload an image to WordPress Media Library for general use (blog posts, galleries, pages, product galleries). The image from the current Telegram message is auto-available — you do not need to provide image_url when the founder just sent an image. Always provide a descriptive title.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'image_url'      => array(
                            'type'        => 'string',
                            'description' => 'Public image URL to download. Leave empty to use the attached Telegram image.',
                        ),
                        'title'          => array(
                            'type'        => 'string',
                            'description' => 'Descriptive title for the image in Media Library.',
                        ),
                        'alt_text'       => array(
                            'type'        => 'string',
                            'description' => 'Alt text for accessibility and SEO.',
                        ),
                        'parent_post_id' => array(
                            'type'        => 'integer',
                            'description' => 'Optional post/page/product ID to attach the image to.',
                        ),
                    ),
                    'required'   => array( 'title' ),
                )
            ),
            $this->build_function_tool(
                'restore_product_from_trash',
                'Restore a WooCommerce product that is currently in trash.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Product name, slug, SKU, or ID to match inside trash.',
                        ),
                    ),
                    'required'   => array(),
                )
            ),
            $this->build_function_tool(
                'update_simple_product',
                'Update a WooCommerce product — change any product field: title, description, price, stock, SKU, status, categories, tags, weight, dimensions, visibility, tax, shipping, and more. Works on both simple and variable products for non-variation fields only. Product targeting is deterministic: exact product_id/SKU/title/slug works, and clear selectors such as "90g tartuf" or "najveca kesa tartufa" resolve only when unique. THIS IS THE PRIMARY TOOL for simple-product sale price and finite sale windows: use sale_price plus date_on_sale_from/date_on_sale_to directly. Do not use schedule_admin_plan just to end a simple-product sale; this tool can set WooCommerce native sale dates. Do NOT use this for sale/regular price writes on variable parents; use update_product_variation or set_variation_sale_schedule instead.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact Woo product post ID. PREFERRED — use this when you know the ID to avoid ambiguous name matches.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Product name, slug, SKU, ID, or deterministic selector such as "90g tartuf" / "najveca kesa tartufa". The tool refuses ambiguous writes instead of writing to a fuzzy first result.',
                        ),
                        'title' => array(
                            'type'        => 'string',
                            'description' => 'New product title/name.',
                        ),
                        'description' => array(
                            'type'        => 'string',
                            'description' => 'New full product description.',
                        ),
                        'short_description' => array(
                            'type'        => 'string',
                            'description' => 'New short product description (excerpt).',
                        ),
                        'sku' => array(
                            'type'        => 'string',
                            'description' => 'New product SKU.',
                        ),
                        'status' => array(
                            'type'        => 'string',
                            'enum'        => array( 'publish', 'draft', 'private', 'pending' ),
                            'description' => 'New product status.',
                        ),
                        'regular_price' => array(
                            'type'        => 'string',
                            'description' => 'New regular price as a decimal string.',
                        ),
                        'sale_price' => array(
                            'type'        => 'string',
                            'description' => 'New sale price as a decimal string.',
                        ),
                        'date_on_sale_from' => array(
                            'type'        => 'string',
                            'description' => 'Optional WooCommerce native sale start for simple products, as YYYY-MM-DD or YYYY-MM-DD HH:MM:SS in site timezone.',
                        ),
                        'date_on_sale_to' => array(
                            'type'        => 'string',
                            'description' => 'Optional WooCommerce native sale end for simple products, as YYYY-MM-DD or YYYY-MM-DD HH:MM:SS in site timezone. Use this for finite simple-product discounts instead of schedule_admin_plan.',
                        ),
                        'clear_sale_price' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true to remove existing sale price.',
                        ),
                        'clear_sale_dates' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true to remove WooCommerce native sale start/end dates.',
                        ),
                        'stock_quantity' => array(
                            'type'        => 'integer',
                            'description' => 'Stock quantity to set.',
                        ),
                        'weight' => array(
                            'type'        => 'string',
                            'description' => 'Product weight as decimal string.',
                        ),
                        'length' => array(
                            'type'        => 'string',
                            'description' => 'Product length as decimal string.',
                        ),
                        'width' => array(
                            'type'        => 'string',
                            'description' => 'Product width as decimal string.',
                        ),
                        'height' => array(
                            'type'        => 'string',
                            'description' => 'Product height as decimal string.',
                        ),
                        'categories' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Category names or slugs. Used with category_action.',
                        ),
                        'category_action' => array(
                            'type'        => 'string',
                            'enum'        => array( 'set', 'add', 'remove' ),
                            'description' => 'How to handle categories: set (replace all), add, or remove. Default: set.',
                        ),
                        'tags' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Tag names. Used with tag_action.',
                        ),
                        'tag_action' => array(
                            'type'        => 'string',
                            'enum'        => array( 'set', 'add', 'remove' ),
                            'description' => 'How to handle tags: set (replace all), add, or remove. Default: set.',
                        ),
                        'catalog_visibility' => array(
                            'type'        => 'string',
                            'enum'        => array( 'visible', 'catalog', 'search', 'hidden' ),
                            'description' => 'Product visibility: visible (shop+search), catalog (shop only), search (search only), hidden.',
                        ),
                        'featured' => array(
                            'type'        => 'boolean',
                            'description' => 'Mark product as featured.',
                        ),
                        'virtual' => array(
                            'type'        => 'boolean',
                            'description' => 'Mark product as virtual (no shipping needed).',
                        ),
                        'downloadable' => array(
                            'type'        => 'boolean',
                            'description' => 'Mark product as downloadable.',
                        ),
                        'tax_status' => array(
                            'type'        => 'string',
                            'enum'        => array( 'taxable', 'shipping', 'none' ),
                            'description' => 'Tax status.',
                        ),
                        'tax_class' => array(
                            'type'        => 'string',
                            'description' => 'Tax class slug (e.g. "reduced-rate", "zero-rate"). Empty string for standard.',
                        ),
                        'sold_individually' => array(
                            'type'        => 'boolean',
                            'description' => 'Limit purchases to 1 item per order.',
                        ),
                        'backorders' => array(
                            'type'        => 'string',
                            'enum'        => array( 'no', 'notify', 'yes' ),
                            'description' => 'Allow backorders: no, notify (allow but notify customer), yes.',
                        ),
                        'low_stock_amount' => array(
                            'type'        => 'integer',
                            'description' => 'Low stock notification threshold.',
                        ),
                        'purchase_note' => array(
                            'type'        => 'string',
                            'description' => 'Note sent to customer after purchase.',
                        ),
                        'reviews_allowed' => array(
                            'type'        => 'boolean',
                            'description' => 'Allow customer reviews.',
                        ),
                        'menu_order' => array(
                            'type'        => 'integer',
                            'description' => 'Menu order for sorting.',
                        ),
                        'upsell_ids' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'integer' ),
                            'description' => 'Product IDs for upsells.',
                        ),
                        'cross_sell_ids' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'integer' ),
                            'description' => 'Product IDs for cross-sells.',
                        ),
                    ),
                    'required'   => array(),
                )
            ),
            $this->build_function_tool(
                'create_product',
                'Create a new WooCommerce product (simple or variable) with full control over all product fields. Title is required. For simple products set regular_price. Variable products need attributes added separately via manage_product_attributes.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_type' => array(
                            'type'        => 'string',
                            'enum'        => array( 'simple', 'variable' ),
                            'description' => 'Product type. Default: simple.',
                        ),
                        'title' => array(
                            'type'        => 'string',
                            'description' => 'Product title/name. REQUIRED.',
                        ),
                        'description' => array(
                            'type'        => 'string',
                            'description' => 'Full product description.',
                        ),
                        'short_description' => array(
                            'type'        => 'string',
                            'description' => 'Short product description (excerpt).',
                        ),
                        'regular_price' => array(
                            'type'        => 'string',
                            'description' => 'Regular price (required for simple products).',
                        ),
                        'sale_price' => array(
                            'type'        => 'string',
                            'description' => 'Sale price (optional).',
                        ),
                        'sku' => array(
                            'type'        => 'string',
                            'description' => 'Product SKU.',
                        ),
                        'status' => array(
                            'type'        => 'string',
                            'enum'        => array( 'publish', 'draft' ),
                            'description' => 'Publish status. Default: publish.',
                        ),
                        'categories' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Category names or slugs. Auto-created if missing.',
                        ),
                        'tags' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Tag names.',
                        ),
                        'weight' => array(
                            'type'        => 'string',
                            'description' => 'Product weight.',
                        ),
                        'length' => array(
                            'type'        => 'string',
                            'description' => 'Product length.',
                        ),
                        'width' => array(
                            'type'        => 'string',
                            'description' => 'Product width.',
                        ),
                        'height' => array(
                            'type'        => 'string',
                            'description' => 'Product height.',
                        ),
                        'manage_stock' => array(
                            'type'        => 'boolean',
                            'description' => 'Enable stock management. Default: false.',
                        ),
                        'stock_quantity' => array(
                            'type'        => 'integer',
                            'description' => 'Initial stock quantity (enables manage_stock automatically).',
                        ),
                        'catalog_visibility' => array(
                            'type'        => 'string',
                            'enum'        => array( 'visible', 'catalog', 'search', 'hidden' ),
                            'description' => 'Product visibility. Default: visible.',
                        ),
                        'featured' => array(
                            'type'        => 'boolean',
                            'description' => 'Mark as featured product.',
                        ),
                        'virtual' => array(
                            'type'        => 'boolean',
                            'description' => 'Virtual product (no shipping).',
                        ),
                        'downloadable' => array(
                            'type'        => 'boolean',
                            'description' => 'Downloadable product.',
                        ),
                        'tax_status' => array(
                            'type'        => 'string',
                            'enum'        => array( 'taxable', 'shipping', 'none' ),
                            'description' => 'Tax status.',
                        ),
                        'tax_class' => array(
                            'type'        => 'string',
                            'description' => 'Tax class slug. Empty for standard rate.',
                        ),
                        'sold_individually' => array(
                            'type'        => 'boolean',
                            'description' => 'Limit to 1 per order.',
                        ),
                        'backorders' => array(
                            'type'        => 'string',
                            'enum'        => array( 'no', 'notify', 'yes' ),
                            'description' => 'Allow backorders.',
                        ),
                        'low_stock_amount' => array(
                            'type'        => 'integer',
                            'description' => 'Low stock threshold.',
                        ),
                        'purchase_note' => array(
                            'type'        => 'string',
                            'description' => 'Note sent to customer after purchase.',
                        ),
                        'reviews_allowed' => array(
                            'type'        => 'boolean',
                            'description' => 'Allow reviews.',
                        ),
                        'menu_order' => array(
                            'type'        => 'integer',
                            'description' => 'Menu order for sorting.',
                        ),
                    ),
                    'required'   => array( 'title' ),
                )
            ),
            $this->build_function_tool(
                'manage_product_gallery',
                'Manage product gallery images — set, add, or remove gallery images by URL or existing Media Library attachment IDs. For Telegram albums, prefer attachment_ids from the current attachment context.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Product name, slug, SKU, or ID to match.',
                        ),
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact product post ID when already known.',
                        ),
                        'image_urls' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Array of image URLs for the gallery.',
                        ),
                        'attachment_ids' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'integer' ),
                            'description' => 'Existing WordPress Media Library image attachment IDs, especially current Telegram attachment_ids.',
                        ),
                        'action' => array(
                            'type'        => 'string',
                            'enum'        => array( 'set', 'add', 'remove' ),
                            'description' => 'How to handle gallery: set (replace all), add (append), remove. Default: set.',
                        ),
                    ),
                )
            ),
            $this->build_function_tool(
                'manage_product_attributes',
                'Add or remove product attributes (e.g., Size, Color). For variable products, this is a parent-matrix operation: do not create duplicate semantic attributes such as Broj next to Velicina, and keep child variations aligned. Empty variation values mean wildcard/any option, not missing data.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Product name, slug, SKU, or ID to match.',
                        ),
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact product post ID when already known.',
                        ),
                        'action' => array(
                            'type'        => 'string',
                            'enum'        => array( 'add', 'remove' ),
                            'description' => 'Add or remove the attribute. Default: add.',
                        ),
                        'attribute_name' => array(
                            'type'        => 'string',
                            'description' => 'Attribute name, e.g. "Size", "Color", "Velicina".',
                        ),
                        'attribute_values' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Attribute values/options, e.g. ["S", "M", "L", "XL"].',
                        ),
                        'visible' => array(
                            'type'        => 'boolean',
                            'description' => 'Show attribute on product page. Default: true.',
                        ),
                        'used_for_variations' => array(
                            'type'        => 'boolean',
                            'description' => 'Use attribute for product variations. Default: true.',
                        ),
                    ),
                    'required'   => array( 'attribute_name' ),
                )
            ),
            $this->build_function_tool(
                'remove_color',
                'Remove one color from a variable WooCommerce product and delete all variations that belong to that color.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact variable product ID when already known from memory_search or a previous lookup.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Variable product name, slug, SKU, or ID.',
                        ),
                        'color' => array(
                            'type'        => 'string',
                            'description' => 'Color value to remove from the product and all its variations.',
                        ),
                    ),
                    'required'   => array( 'color' ),
                )
            ),
            $this->build_function_tool(
                'move_to_outlet',
                'Rebuild a variable product into an outlet assortment — adds outlet category, removes all variations NOT in the provided specs list, creates any missing variations from specs, and sets stock quantities. Use this ONLY when the founder provides a FULL outlet specification (which colors/sizes/quantities to keep). If the founder only says "add to outlet category" WITHOUT specs, use update_simple_product with category_action:add instead.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact variable product ID when already known from memory_search or a previous lookup.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Variable product name, slug, SKU, or ID.',
                        ),
                        'specs' => array(
                            'type'        => 'array',
                            'description' => 'Outlet variation specs to keep or create.',
                            'items'       => array(
                                'type'       => 'object',
                                'properties' => array(
                                    'color' => array(
                                        'type'        => 'string',
                                        'description' => 'Color for the outlet variation.',
                                    ),
                                    'size' => array(
                                        'type'        => 'string',
                                        'description' => 'Size for the outlet variation.',
                                    ),
                                    'qty' => array(
                                        'type'        => 'integer',
                                        'description' => 'Stock quantity for that outlet variation.',
                                    ),
                                ),
                                'required' => array( 'color', 'size', 'qty' ),
                            ),
                        ),
                    ),
                    'required'   => array( 'specs' ),
                )
            ),
            $this->build_function_tool(
                'email_message_trash',
                'Move a specific inbound IMAP email message to Trash by UID after the main founder-facing assistant decides it is not worth keeping. After trashing, report the action to the founder.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'uid' => array(
                            'type'        => 'integer',
                            'description' => 'IMAP UID of the inbound email message.',
                        ),
                    ),
                    'required' => array( 'uid' ),
                )
            ),
            $this->build_function_tool(
                'send_email_message',
                'Send an email through Niwa-native SMTP when configured, falling back to the current WordPress mail transport. For inbound email replies, use only after the founder explicitly approves the draft shown in chat.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'recipient_query' => array(
                            'type'        => 'string',
                            'description' => 'Known recipient label, name, role, or contact search query, such as knjigovodja or admin.',
                        ),
                        'to_email' => array(
                            'type'        => 'string',
                            'description' => 'Explicit recipient email address when already known.',
                        ),
                        'subject' => array(
                            'type'        => 'string',
                            'description' => 'Email subject line.',
                        ),
                        'body' => array(
                            'type'        => 'string',
                            'description' => 'Email body. HTML is allowed. You may use placeholders like {{name}}, {{email}}, {{phone}}, {{site_name}}, and {{site_url}}.',
                        ),
                    ),
                    'required'   => array( 'subject', 'body' ),
                )
            ),
            $this->build_function_tool(
                'send_email_with_exchange_rate',
                'Schedule-safe email sender that fetches the official NBS middle exchange rate at execution time, then appends the rate to the email before sending. Use for future email delivery where the message must include the EUR/RSD or another NBS middle rate valid on the send day. Do not put rate placeholders into send_email_message for this case.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'recipient_query' => array(
                            'type'        => 'string',
                            'description' => 'Known recipient label, name, role, or contact search query.',
                        ),
                        'to_email' => array(
                            'type'        => 'string',
                            'description' => 'Explicit recipient email address when already known.',
                        ),
                        'subject' => array(
                            'type'        => 'string',
                            'description' => 'Email subject line.',
                        ),
                        'body' => array(
                            'type'        => 'string',
                            'description' => 'Base email body to send before the dynamic NBS exchange-rate line is appended at execution time.',
                        ),
                        'currency_code' => array(
                            'type'        => 'string',
                            'description' => 'NBS currency code to quote against RSD. Default EUR.',
                        ),
                        'rate_date' => array(
                            'type'        => 'string',
                            'description' => 'Optional YYYY-MM-DD date for the NBS middle rate. Leave empty for the site-local execution date, especially in scheduled jobs.',
                        ),
                    ),
                    'required'   => array( 'subject', 'body' ),
                )
            ),
            $this->build_function_tool(
                'send_followup_to_captured_leads',
                'Send a follow-up email to saved frontend lead-capture contacts who left an email but have not completed a WooCommerce order.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'subject' => array(
                            'type'        => 'string',
                            'description' => 'Email subject line. Placeholders like {{name}} and {{site_name}} are allowed.',
                        ),
                        'body' => array(
                            'type'        => 'string',
                            'description' => 'Email body for each lead. Placeholders like {{name}}, {{email}}, {{phone}}, {{site_name}}, and {{site_url}} are allowed.',
                        ),
                        'limit' => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => 100,
                            'description' => 'Maximum number of leads to email in one run.',
                        ),
                        'search_query' => array(
                            'type'        => 'string',
                            'description' => 'Optional contact search filter before follow-up.',
                        ),
                        'days' => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => 365,
                            'description' => 'Look back window in days for eligible leads.',
                        ),
                    ),
                    'required'   => array( 'subject', 'body' ),
                )
            ),
            $this->build_function_tool(
                'bulk_update_catalog_prices',
                'Bulk update WooCommerce live catalog prices. Use this for requests like "raise all live product prices by 10%". Default scope should be live published products; include variations only when the founder asked for them.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'scope' => array(
                            'type'        => 'string',
                            'enum'        => array( 'live_products', 'live_products_and_variations' ),
                            'description' => 'Target scope. live_products means published product posts. live_products_and_variations also includes published product variations.',
                        ),
                        'change_mode' => array(
                            'type'        => 'string',
                            'enum'        => array( 'percent', 'fixed_delta', 'set_regular_price' ),
                            'description' => 'How to change prices.',
                        ),
                        'change_value' => array(
                            'type'        => 'string',
                            'description' => 'Percent, delta amount, or exact regular price depending on change_mode.',
                        ),
                        'include_sale_prices' => array(
                            'type'        => 'boolean',
                            'description' => 'When true, existing sale prices are adjusted too. Default true for percent/fixed_delta.',
                        ),
                        'limit' => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => self::MAX_BULK_CATALOG_TARGETS,
                            'description' => 'Optional hard cap for how many catalog entries to change in one run.',
                        ),
                    ),
                    'required'   => array( 'change_mode', 'change_value' ),
                )
            ),
            $this->build_function_tool(
                'update_product_variation',
                'Update a specific WooCommerce product variation — price, stock, SKU, image, description, status, weight, dimensions, and more. Use variation_id directly if known. Prefer this exact-ID path after a variation lookup or when the founder already supplied the variation ID.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'variation_id' => array(
                            'type'        => 'integer',
                            'description' => 'Direct variation ID if known (from woo_variation_lookup). When provided, product_query and variation_attributes are not needed.',
                        ),
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact parent variable product ID when already known from memory_search or a previous lookup.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Variable product name, slug, SKU, or ID. Not needed when variation_id is provided.',
                        ),
                        'variation_attributes' => array(
                            'type'                 => 'object',
                            'description'          => 'Variation selector as attribute name to option value map, e.g. {"size":"M","color":"black"}. Not needed when variation_id is provided.',
                            'additionalProperties' => array(
                                'type' => 'string',
                            ),
                        ),
                        'regular_price' => array(
                            'type'        => 'string',
                            'description' => 'Optional new regular price as a decimal string.',
                        ),
                        'sale_price' => array(
                            'type'        => 'string',
                            'description' => 'Optional new sale price as a decimal string.',
                        ),
                        'clear_sale_price' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true to remove existing sale price.',
                        ),
                        'stock_quantity' => array(
                            'type'        => 'integer',
                            'description' => 'Optional stock quantity for the variation.',
                        ),
                        'sku' => array(
                            'type'        => 'string',
                            'description' => 'Optional SKU update for the variation.',
                        ),
                        'image_url' => array(
                            'type'        => 'string',
                            'description' => 'Image URL to set as variation thumbnail. Leave empty to use the Telegram-attached image from current message.',
                        ),
                        'set_image' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true to attach the current Telegram image to this variation (no image_url needed).',
                        ),
                        'variation_description' => array(
                            'type'        => 'string',
                            'description' => 'Variation-specific description text.',
                        ),
                        'variation_status' => array(
                            'type'        => 'string',
                            'enum'        => array( 'publish', 'private' ),
                            'description' => 'Enable/disable variation: publish (enabled) or private (disabled).',
                        ),
                        'weight' => array(
                            'type'        => 'string',
                            'description' => 'Variation weight override.',
                        ),
                        'length' => array(
                            'type'        => 'string',
                            'description' => 'Variation length override.',
                        ),
                        'width' => array(
                            'type'        => 'string',
                            'description' => 'Variation width override.',
                        ),
                        'height' => array(
                            'type'        => 'string',
                            'description' => 'Variation height override.',
                        ),
                        'virtual' => array(
                            'type'        => 'boolean',
                            'description' => 'Mark variation as virtual.',
                        ),
                        'downloadable' => array(
                            'type'        => 'boolean',
                            'description' => 'Mark variation as downloadable.',
                        ),
                        'tax_class' => array(
                            'type'        => 'string',
                            'description' => 'Tax class for this variation. Use "parent" for parent product tax class.',
                        ),
                        'backorders' => array(
                            'type'        => 'string',
                            'enum'        => array( 'no', 'notify', 'yes' ),
                            'description' => 'Allow backorders for this variation.',
                        ),
                    ),
                    'required'   => array(),
                )
            ),
            $this->build_function_tool(
                'set_variation_sale_schedule',
                'Set or clear the scheduled sale period for a specific WooCommerce product variation. Use this when a variation sale price needs start/end dates. Use variation_id directly if known; otherwise resolve by product_query + variation_attributes. Date-only values use the site timezone: from dates start at 00:00:00, to dates end at 23:59:59.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'variation_id' => array(
                            'type'        => 'integer',
                            'description' => 'Direct variation ID if known (from woo_variation_lookup). Preferred exact target.',
                        ),
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact parent variable product ID when already known.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Variable product name, slug, SKU, or ID. Not needed when variation_id is provided.',
                        ),
                        'variation_attributes' => array(
                            'type'                 => 'object',
                            'description'          => 'Variation selector as attribute name to option value map. Not needed when variation_id is provided.',
                            'additionalProperties' => array(
                                'type' => 'string',
                            ),
                        ),
                        'sale_price' => array(
                            'type'        => 'string',
                            'description' => 'Optional sale price as a decimal string. Use when setting the sale amount together with the schedule.',
                        ),
                        'clear_sale_price' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true to remove the sale price. This also removes the sale schedule.',
                        ),
                        'date_on_sale_from' => array(
                            'type'        => 'string',
                            'description' => 'Sale start date/time in YYYY-MM-DD or ISO datetime format. Date-only starts at 00:00:00 site time.',
                        ),
                        'date_on_sale_to' => array(
                            'type'        => 'string',
                            'description' => 'Sale end date/time in YYYY-MM-DD or ISO datetime format. Date-only ends at 23:59:59 site time.',
                        ),
                        'clear_sale_dates' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true to remove the sale start and end dates while keeping the sale price.',
                        ),
                    ),
                    'required'   => array(),
                )
            ),
            $this->build_function_tool(
                'add_product_variation',
                'Add a new WooCommerce product variation to a variable product.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact variable product ID when already known from memory_search or a previous lookup.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Variable product name, slug, SKU, or ID.',
                        ),
                        'variation_attributes' => array(
                            'type'                 => 'object',
                            'description'          => 'New variation attribute map, e.g. {"size":"M","color":"black"}.',
                            'additionalProperties' => array(
                                'type' => 'string',
                            ),
                        ),
                        'regular_price' => array(
                            'type'        => 'string',
                            'description' => 'Optional new regular price as a decimal string.',
                        ),
                        'sale_price' => array(
                            'type'        => 'string',
                            'description' => 'Optional new sale price as a decimal string.',
                        ),
                        'stock_quantity' => array(
                            'type'        => 'integer',
                            'description' => 'Optional starting stock quantity for the new variation.',
                        ),
                        'manage_stock' => array(
                            'type'        => 'boolean',
                            'description' => 'Whether this variation manages stock quantity. If omitted and stock_quantity is absent, stock is status-only.',
                        ),
                        'stock_status' => array(
                            'type'        => 'string',
                            'enum'        => array( 'instock', 'outofstock', 'onbackorder' ),
                            'description' => 'Status-only stock value when no exact stock quantity is known.',
                        ),
                        'backorders' => array(
                            'type'        => 'string',
                            'enum'        => array( 'no', 'notify', 'yes' ),
                            'description' => 'Backorder policy for this variation.',
                        ),
                        'sku' => array(
                            'type'        => 'string',
                            'description' => 'Optional SKU for the new variation.',
                        ),
                        'image_url' => array(
                            'type'        => 'string',
                            'description' => 'Public image URL to import and assign to this variation.',
                        ),
                        'attachment_id' => array(
                            'type'        => 'integer',
                            'description' => 'Existing Media Library attachment ID to assign as this variation image.',
                        ),
                        'set_image' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true to assign image_url or attachment_id as the variation image.',
                        ),
                    ),
                    'required'   => array( 'variation_attributes' ),
                )
            ),
            $this->build_function_tool(
                'bulk_update_variations',
                'Update ALL variations of a WooCommerce variable product at once. Use when the founder wants the same price, stock, or other value applied to every variation of a product.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact variable product ID when already known from memory_search or a previous lookup.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Variable product name, slug, SKU, or ID.',
                        ),
                        'regular_price' => array(
                            'type'        => 'string',
                            'description' => 'New regular price for ALL variations.',
                        ),
                        'sale_price' => array(
                            'type'        => 'string',
                            'description' => 'New sale price for ALL variations.',
                        ),
                        'clear_sale_price' => array(
                            'type'        => 'boolean',
                            'description' => 'Set true to remove sale price from ALL variations.',
                        ),
                        'stock_quantity' => array(
                            'type'        => 'integer',
                            'description' => 'New stock quantity for ALL variations.',
                        ),
                    ),
                    'required'   => array(),
                )
            ),
            $this->build_function_tool(
                'remove_product_variation',
                'Remove a specific WooCommerce product variation by moving it to trash. Use variation_id directly if known (from woo_variation_lookup results), OR use product_query + variation_attributes to find the variation. After deleting, the tool automatically removes the deleted variation attribute options from the parent product when no remaining active variation still uses those options.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'variation_id' => array(
                            'type'        => 'integer',
                            'description' => 'Direct variation ID if known (from woo_variation_lookup). When provided, product_query and variation_attributes are not needed.',
                        ),
                        'product_id' => array(
                            'type'        => 'integer',
                            'description' => 'Exact variable product ID when already known from memory_search or a previous lookup. Optional when variation_id is provided.',
                        ),
                        'product_query' => array(
                            'type'        => 'string',
                            'description' => 'Variable product name, slug, SKU, or ID. Not needed when variation_id is provided.',
                        ),
                        'variation_attributes' => array(
                            'type'                 => 'object',
                            'description'          => 'Variation selector as attribute name to option value map. Not needed when variation_id is provided.',
                            'additionalProperties' => array(
                                'type' => 'string',
                            ),
                        ),
                    ),
                    'required'   => array(),
                )
            ),
            $this->build_function_tool(
                'update_front_page_title',
                'Update the front page WordPress title AND the first structured visible homepage heading candidate when one exists. This is for direct homepage headline changes; it does not rely on language keywords. The result reports visible_headline_updated and locations, so do not claim the frontend changed if visible_headline_updated is false.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'new_title' => array(
                            'type'        => 'string',
                            'description' => 'New front page title text.',
                        ),
                    ),
                    'required'   => array( 'new_title' ),
                )
            ),
            $this->build_function_tool(
                'replace_text_in_post',
                'Replace text inside any post field or page builder meta. Works with ALL page builders (Elementor, WPBakery, Divi, Gutenberg, Beaver Builder, etc.). WORKFLOW: (1) call search_text_in_post to find WHERE the text is stored, (2) use the "field_for_replace" value from search results as the "field" parameter here. The find_text must match exactly what is stored.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'target_type' => array(
                            'type'        => 'string',
                            'enum'        => array( 'front_page', 'post_id', 'slug', 'title_search' ),
                            'description' => 'How to locate the target post.',
                        ),
                        'target_value' => array(
                            'type'        => 'string',
                            'description' => 'Post ID, slug, or search title. Leave empty for front_page.',
                        ),
                        'field'        => array(
                            'type'        => 'string',
                            'description' => 'Field to update. Standard fields: post_title, post_content, post_excerpt, elementor_data. For ANY post meta key: use meta:KEY_NAME (e.g. meta:_elementor_data, meta:_fl_builder_data, meta:_et_builder_data). Use the field_for_replace value from search_text_in_post results.',
                        ),
                        'find_text'    => array(
                            'type'        => 'string',
                            'description' => 'Exact text to replace. Must match exactly what is stored in the field. For structured data (JSON, serialized PHP), the text is matched inside all string values. Use search_text_in_post or read_post_field with extract_text=true to discover the exact text.',
                        ),
                        'replace_text' => array(
                            'type'        => 'string',
                            'description' => 'Replacement text.',
                        ),
                    ),
                    'required'   => array( 'target_type', 'field', 'find_text', 'replace_text' ),
                )
            ),
            $this->build_function_tool(
                'elementor_restore_or_repair',
                'Restore Elementor page data from automatic backup, or repair corrupted Elementor JSON. Use this when _elementor_data is broken/invalid JSON and replace_text_in_post cannot operate. Mode "restore_backup" restores from the last automatic backup made before a Niwa edit. Mode "repair" attempts to fix common corruption patterns (stripped backslashes, literal newlines in JSON keys) and validates the result before writing.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'target_type' => array(
                            'type'        => 'string',
                            'enum'        => array( 'front_page', 'post_id', 'slug', 'title_search' ),
                            'description' => 'How to locate the target post.',
                        ),
                        'target_value' => array(
                            'type'        => 'string',
                            'description' => 'Post ID, slug, or search title. Leave empty for front_page.',
                        ),
                        'mode' => array(
                            'type'        => 'string',
                            'enum'        => array( 'restore_backup', 'repair' ),
                            'description' => 'restore_backup: restore from _niwa_elementor_data_backup. repair: attempt automatic JSON repair of corrupted data.',
                        ),
                    ),
                    'required'   => array( 'target_type', 'mode' ),
                )
            ),
            $this->build_function_tool(
                'swap_page_image',
                'Swap an image on a page by replacing the old image URL with a new one across post_content and ALL post meta values. Works with ANY page builder. The old image can be identified by URL, filename, or attachment ID. The new image can be a URL (will be uploaded to Media Library) or an attachment ID.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'target_type' => array(
                            'type'        => 'string',
                            'enum'        => array( 'front_page', 'post_id', 'slug', 'title_search' ),
                            'description' => 'How to locate the target post.',
                        ),
                        'target_value' => array(
                            'type'        => 'string',
                            'description' => 'Post ID, slug, or search title. Leave empty for front_page.',
                        ),
                        'find_image' => array(
                            'type'        => 'string',
                            'description' => 'The image to find and replace. Can be: a full URL (https://example.com/wp-content/uploads/image.jpg), a filename (image.jpg), or an attachment ID (numeric).',
                        ),
                        'new_image' => array(
                            'type'        => 'string',
                            'description' => 'The replacement image. Can be: an external URL (will be downloaded and uploaded to Media Library), or an attachment ID (numeric, must already exist in Media Library).',
                        ),
                    ),
                    'required'   => array( 'target_type', 'find_image', 'new_image' ),
                )
            ),
            $this->build_function_tool(
                'update_wordpress_core',
                'Update WordPress core to the latest available version.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'target_version' => array(
                            'type'        => 'string',
                            'description' => 'Optional target version. Leave empty for latest available update.',
                        ),
                    ),
                )
            ),
            $this->build_function_tool(
                'update_wordpress_plugins',
                'Update WordPress plugins through the native WordPress upgrader. Use when the founder wants plugin updates, all-plugin maintenance, active-plugin maintenance, or updates for named plugin slugs. This is the native tool; do not create a runtime self-extension for normal plugin updates.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'scope' => array(
                            'type'        => 'string',
                            'enum'        => array( 'all', 'active', 'inactive', 'specific' ),
                            'description' => 'Plugin update scope. Use all unless the founder narrowed the request.',
                        ),
                        'plugin_slugs' => array(
                            'type'        => 'array',
                            'description' => 'Specific plugin slugs, plugin files, or plugin names when scope=specific.',
                            'items'       => array(
                                'type' => 'string',
                            ),
                        ),
                        'create_backup' => array(
                            'type'        => 'boolean',
                            'description' => 'Create a filesystem backup of selected plugins before updating. Default true.',
                        ),
                    ),
                )
            ),
            $this->build_function_tool(
                'manage_wordpress_plugin_activation',
                'Activate or deactivate one installed WordPress plugin by slug, plugin file, or plugin name using native WordPress core plugin APIs. Use when the founder asks to turn a plugin on or off; do not inspect code/files for this normal WordPress maintenance action.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'action' => array(
                            'type'        => 'string',
                            'enum'        => array( 'activate', 'deactivate' ),
                            'description' => 'Whether to activate or deactivate the plugin.',
                        ),
                        'plugin' => array(
                            'type'        => 'string',
                            'description' => 'Plugin slug, plugin file, or installed plugin name, for example kirki or kirki/kirki.php.',
                        ),
                        'network_wide' => array(
                            'type'        => 'boolean',
                            'description' => 'Optional multisite network-wide activation/deactivation. Defaults to false.',
                        ),
                    ),
                    'required'   => array( 'action', 'plugin' ),
                )
            ),
            $this->build_function_tool(
                'inventory_cleanup',
                'Draft out-of-stock WooCommerce products and set _za_noindex=1.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'limit' => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => self::MAX_INVENTORY_LIMIT,
                            'description' => 'Maximum number of out-of-stock products to process.',
                        ),
                    ),
                )
            ),
            $this->build_function_tool(
                'skill_create',
                'Create a new custom skill that Niwa can use as a reusable workflow. Use when the founder asks to remember, save, or learn a procedure as a skill.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'name'        => array( 'type' => 'string', 'description' => 'Human-readable skill name.' ),
                        'description' => array( 'type' => 'string', 'description' => 'Short description of what the skill does and when to use it.' ),
                        'content'     => array( 'type' => 'string', 'description' => 'Full workflow instructions in Markdown.' ),
                        'triggers'    => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Trigger keywords or phrases that should activate this skill.',
                        ),
                        'scope'       => array(
                            'type'        => 'string',
                            'enum'        => array( 'general', 'code', 'site', 'catalog', 'database', 'frontend' ),
                            'description' => 'Which subagent scope can use this skill. Use "frontend" for customer-facing skills. Defaults to general.',
                        ),
                    ),
                    'required' => array( 'name', 'description', 'content' ),
                )
            ),
            $this->build_function_tool(
                'skill_delete',
                'Delete a custom skill by its slug identifier.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'slug' => array( 'type' => 'string', 'description' => 'The skill slug to delete.' ),
                    ),
                    'required' => array( 'slug' ),
                )
            ),
			$this->build_function_tool(
				'skill_import',
				'Import a custom skill from raw SKILL.md text content or a base64 encoded ZIP skill package.',
				array(
					'type'       => 'object',
					'properties' => array(
						'content'        => array( 'type' => 'string', 'description' => 'Raw SKILL.md content with YAML frontmatter and markdown body.' ),
						'archive_base64' => array( 'type' => 'string', 'description' => 'Base64 encoded ZIP package containing SKILL.md and optional references/scripts/assets files.' ),
						'filename'       => array( 'type' => 'string', 'description' => 'Optional original filename hint for slug generation.' ),
					),
					'required' => array(),
				)
			),
            $this->build_function_tool(
                'skill_import_from_github',
                'Import a public GitHub/skills.sh skill after founder approval. This fetches SKILL.md and supported references/scripts/assets through WordPress HTTP API, then imports it into Niwa skills. Search and preview first with external_skill_search/external_skill_get.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'source' => array( 'type' => 'string', 'description' => 'GitHub repo in owner/repo form.' ),
                        'skill'  => array( 'type' => 'string', 'description' => 'Skill id or slug inside the repo.' ),
                        'id'     => array( 'type' => 'string', 'description' => 'skills.sh result id, usually owner/repo/skill.' ),
                        'branch' => array( 'type' => 'string', 'description' => 'Optional Git branch. Defaults to repo default branch.' ),
                        'reason' => array( 'type' => 'string', 'description' => 'Why this remote skill is needed for the current founder task.' ),
                    ),
                    'required' => array(),
                )
            ),
            $this->build_function_tool(
                'skill_update',
                'Update an existing custom skill. Only provided fields are changed; omitted fields keep their current values. Version is auto-incremented.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'slug'        => array( 'type' => 'string', 'description' => 'The skill slug to update.' ),
                        'name'        => array( 'type' => 'string', 'description' => 'Optional new human-readable name.' ),
                        'description' => array( 'type' => 'string', 'description' => 'Optional new description.' ),
                        'content'     => array( 'type' => 'string', 'description' => 'Optional new workflow instructions in Markdown.' ),
                        'triggers'    => array(
                            'type' => 'array', 'items' => array( 'type' => 'string' ),
                            'description' => 'Optional new trigger keywords.',
                        ),
                        'scope'       => array(
                            'type' => 'string',
                            'enum' => array( 'general', 'code', 'site', 'catalog', 'database', 'frontend' ),
                            'description' => 'Optional new scope.',
                        ),
                    ),
                    'required' => array( 'slug' ),
                )
            ),
            $this->build_function_tool(
                'skill_eval_create',
                'Create a test case (eval) for a skill. Evals define sample prompts and success criteria to test skill behavior.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'slug'              => array( 'type' => 'string', 'description' => 'The skill slug.' ),
                        'prompt'            => array( 'type' => 'string', 'description' => 'Sample user message that should trigger this skill.' ),
                        'expected_behavior' => array( 'type' => 'string', 'description' => 'Description of what the agent should do.' ),
                        'success_criteria'  => array(
                            'type' => 'array', 'items' => array( 'type' => 'string' ),
                            'description' => 'List of criteria that must be met for the eval to pass.',
                        ),
                    ),
                    'required' => array( 'slug', 'prompt', 'expected_behavior', 'success_criteria' ),
                )
            ),
            $this->build_function_tool(
                'skill_eval_run',
                'Run eval test cases for a skill. Uses the LLM to simulate a conversation and then judge results against success criteria. Returns pass/fail with reasoning.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'slug'    => array( 'type' => 'string', 'description' => 'The skill slug.' ),
                        'eval_id' => array( 'type' => 'string', 'description' => 'Optional specific eval ID. If omitted, runs all evals.' ),
                    ),
                    'required' => array( 'slug' ),
                )
            ),
            $this->build_function_tool(
                'skill_benchmark',
                'Compare current skill version against a proposed new version by running all evals against both. Returns comparative results.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'slug'             => array( 'type' => 'string', 'description' => 'The skill slug.' ),
                        'proposed_content' => array( 'type' => 'string', 'description' => 'The proposed new version of the skill content to compare against.' ),
                    ),
                    'required' => array( 'slug', 'proposed_content' ),
                )
            ),
            $this->build_function_tool(
                'skill_optimize_description',
                'Analyze a skill description and triggers, test them against sample prompts, and suggest improvements for better matching. Does not auto-apply changes.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'slug'             => array( 'type' => 'string', 'description' => 'The skill slug.' ),
                        'sample_prompts'   => array(
                            'type' => 'array', 'items' => array( 'type' => 'string' ),
                            'description' => 'Prompts that SHOULD trigger this skill.',
                        ),
                        'negative_prompts' => array(
                            'type' => 'array', 'items' => array( 'type' => 'string' ),
                            'description' => 'Optional prompts that should NOT trigger this skill.',
                        ),
                    ),
                    'required' => array( 'slug', 'sample_prompts' ),
                )
            ),
            $this->build_function_tool(
                'skill_file_write',
                'Write a file to a skill directory (references/, scripts/, or assets/ subdirectories). Creates the subdirectory if needed.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'slug'    => array( 'type' => 'string', 'description' => 'The skill slug.' ),
                        'path'    => array( 'type' => 'string', 'description' => 'Relative path within the skill directory (e.g., references/REFERENCE.md).' ),
                        'content' => array( 'type' => 'string', 'description' => 'File content to write.' ),
                    ),
                    'required' => array( 'slug', 'path', 'content' ),
                )
            ),
            $this->build_function_tool(
                'skill_file_edit_patch',
                'Apply a bounded patch to a file inside a skill directory (references/, scripts/, or assets/). Use this to edit skill helper files instead of editing theme files.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'slug'    => array( 'type' => 'string', 'description' => 'The skill slug.' ),
                        'path'    => array( 'type' => 'string', 'description' => 'Relative path within the skill directory (e.g., scripts/helper.php).' ),
                        'patches' => array(
                            'type'        => 'array',
                            'description' => 'Ordered list of bounded text patches.',
                            'items'       => array(
                                'type'       => 'object',
                                'properties' => array(
                                    'find'             => array( 'type' => 'string', 'description' => 'Exact text to find. Use empty string only with insert_after or insert_before.' ),
                                    'replace'          => array( 'type' => 'string', 'description' => 'Replacement text.' ),
                                    'insert_after'     => array( 'type' => 'string', 'description' => 'Optional insertion anchor.' ),
                                    'insert_before'    => array( 'type' => 'string', 'description' => 'Optional insertion anchor.' ),
                                    'replace_once'     => array( 'type' => 'boolean', 'description' => 'Only change the first matched occurrence.' ),
                                    'expected_matches' => array( 'type' => 'integer', 'description' => 'Expected number of matches in scope.' ),
                                    'start_line'       => array( 'type' => 'integer', 'description' => 'Optional 1-based start line.' ),
                                    'end_line'         => array( 'type' => 'integer', 'description' => 'Optional 1-based end line.' ),
                                ),
                            ),
                        ),
                    ),
                    'required' => array( 'slug', 'path', 'patches' ),
                )
            ),

            /* --------------------------------------------------------
             *  BLOG POST MANAGEMENT TOOLS
             * -------------------------------------------------------- */
            $this->build_function_tool(
                'blog_posts_list',
                'List WordPress blog posts with optional filtering by status, category, tag, or search query. Returns post ID, title, status, date, categories, and excerpt preview.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'status'   => array(
                            'type'        => 'string',
                            'enum'        => array( 'publish', 'draft', 'pending', 'private', 'any' ),
                            'description' => 'Filter by post status. Defaults to "any".',
                        ),
                        'category' => array(
                            'type'        => 'string',
                            'description' => 'Filter by category name or slug.',
                        ),
                        'tag'      => array(
                            'type'        => 'string',
                            'description' => 'Filter by tag name or slug.',
                        ),
                        'search'   => array(
                            'type'        => 'string',
                            'description' => 'Search query to match against post title or content.',
                        ),
                        'limit'    => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => 20,
                            'description' => 'Max number of posts to return. Defaults to 10.',
                        ),
                    ),
                    'required' => array(),
                )
            ),
            $this->build_function_tool(
                'blog_post_get',
                'Get full details of a single blog post including title, content, excerpt, categories, tags, featured image, status, date, slug, and SEO meta description.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'post_id' => array(
                            'type'        => 'integer',
                            'description' => 'WordPress post ID.',
                        ),
                        'slug'    => array(
                            'type'        => 'string',
                            'description' => 'Post slug. Used if post_id is not provided.',
                        ),
                    ),
                    'required' => array(),
                )
            ),
            $this->build_function_tool(
                'create_blog_post',
                'Compatibility adapter for HTML blog input. It runs the same canonical Niwa normalization, taxonomy governance, link/content/render audit, asset generation, metadata persistence, and draft-first quality gate as create_niwa_blog. It cannot bypass a failed gate or publish directly.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'title'             => array(
                            'type'        => 'string',
                            'description' => 'Blog post title.',
                        ),
                        'content'           => array(
                            'type'        => 'string',
                            'description' => 'Full blog post content in HTML. Supports WordPress block editor HTML or classic HTML.',
                        ),
                        'excerpt'           => array(
                            'type'        => 'string',
                            'description' => 'Short excerpt / summary of the post.',
                        ),
                        'categories'        => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Existing category names. Maximum two; one primary category is preferred. Missing terms are skipped unless allow_new_terms is explicitly true.',
                        ),
                        'tags'              => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Existing tag names. Maximum three. Missing tags are skipped unless allow_new_terms is explicitly true.',
                        ),
                        'featured_image_id' => array(
                            'type'        => 'integer',
                            'description' => 'WordPress attachment ID to use as featured image. Upload first with upload_image_to_media if needed.',
                        ),
                        'slug'              => array(
                            'type'        => 'string',
                            'description' => 'URL slug for the post. Auto-generated from title if not provided.',
                        ),
                        'meta_description'  => array(
                            'type'        => 'string',
                            'description' => 'SEO meta description for Yoast SEO or RankMath.',
                        ),
						'focus_keyword' => array( 'type' => 'string', 'description' => 'Primary query; derived from the title when omitted.' ),
						'search_intent' => array( 'type' => 'string', 'description' => 'The search intent this article answers.' ),
						'audience' => array( 'type' => 'string', 'description' => 'The intended reader and problem context.' ),
						'money_page_url' => array( 'type' => 'string', 'description' => 'Optional real published conversion target that must be linked naturally in the article.' ),
						'language' => array( 'type' => 'string', 'description' => 'Optional BCP 47 article language tag.' ),
						'evidence_urls' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'description' => 'Supplied sources for exact external claims.' ),
						'author_id' => array( 'type' => 'integer', 'description' => 'Optional real WordPress author ID. Uses the configured/current valid user when omitted.' ),
						'allow_new_terms' => array( 'type' => 'boolean', 'description' => 'Explicit opt-in to create missing governed taxonomy terms during execution only. Preview is always side-effect free.' ),
                    ),
                    'required' => array( 'title', 'content' ),
                )
            ),
            $this->build_function_tool(
                'update_blog_post',
                'Update a blog through the canonical Niwa pipeline. Content, headings, taxonomy, links, assets, schema, SEO metadata, source hash, and quality report are validated and regenerated together. It cannot publish directly; use publish_blog_post.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'post_id'           => array(
                            'type'        => 'integer',
                            'description' => 'WordPress post ID to update.',
                        ),
                        'title'             => array(
                            'type'        => 'string',
                            'description' => 'New post title.',
                        ),
						'content'           => array(
							'type'        => 'string',
							'description' => 'New post content in HTML or Markdown as selected by content_format.',
                        ),
                        'excerpt'           => array(
                            'type'        => 'string',
                            'description' => 'New post excerpt.',
                        ),
                        'categories'        => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Replace with up to two existing categories. Missing terms require allow_new_terms=true.',
                        ),
                        'tags'              => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Replace with up to three existing tags. Missing terms require allow_new_terms=true.',
                        ),
                        'featured_image_id' => array(
                            'type'        => 'integer',
                            'description' => 'New featured image attachment ID. Use 0 to remove.',
                        ),
                        'status'            => array(
                            'type'        => 'string',
                            'enum'        => array( 'draft', 'pending', 'private' ),
                            'description' => 'Optional non-published status. Publishing has a separate guarded transition.',
                        ),
                        'slug'              => array(
                            'type'        => 'string',
                            'description' => 'New URL slug.',
                        ),
                        'meta_description'  => array(
                            'type'        => 'string',
                            'description' => 'New SEO meta description for Yoast SEO or RankMath.',
                        ),
						'content_format' => array( 'type' => 'string', 'enum' => array( 'html', 'markdown' ), 'description' => 'Format of a supplied content replacement. Default: html.' ),
						'focus_keyword' => array( 'type' => 'string' ),
						'search_intent' => array( 'type' => 'string' ),
						'audience' => array( 'type' => 'string' ),
						'money_page_url' => array( 'type' => 'string' ),
						'language' => array( 'type' => 'string', 'description' => 'Optional BCP 47 article language tag.' ),
						'evidence_urls' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ),
						'author_id' => array( 'type' => 'integer', 'description' => 'Optional real WordPress author ID.' ),
						'allow_new_terms' => array( 'type' => 'boolean', 'description' => 'Explicit execution-time opt-in for missing governed terms.' ),
                    ),
                    'required' => array( 'post_id' ),
                )
            ),
            $this->build_function_tool(
                'publish_blog_post',
                'Approve and publish the exact current Niwa-managed revision. Preview freezes post ID, expected status, content/source hash, post_modified_gmt, and quality-policy version; execution reloads and reruns the current-state quality gate before the status transition.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'post_id' => array(
                            'type'        => 'integer',
                            'description' => 'WordPress post ID to publish.',
                        ),
                    ),
                    'required' => array( 'post_id' ),
                )
            ),

            /* --------------------------------------------------------
             *  BLOGING TOOLS (Niwa Bloging skill support)
             * -------------------------------------------------------- */
            $this->build_function_tool(
                'search_stock_images',
				'Search stock image APIs (Pexels, Unsplash, Lummi) for blog post featured images. When source is "auto", providers are tried in a stable fallback order. Requires API keys configured in Niwa Bloging settings.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'query' => array(
                            'type'        => 'string',
                            'description' => 'Search term for finding images (e.g., post title + category).',
                        ),
                        'source' => array(
                            'type'        => 'string',
                            'enum'        => array( 'auto', 'pexels', 'unsplash', 'lummi' ),
							'description' => 'Image source. "auto" uses the stable provider fallback order. Default: auto.',
                        ),
                        'orientation' => array(
                            'type'        => 'string',
                            'enum'        => array( 'landscape', 'portrait', 'square' ),
                            'description' => 'Image orientation. Default: landscape.',
                        ),
                        'count' => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => 10,
                            'description' => 'Number of results to return. Default: 5.',
                        ),
                    ),
                    'required' => array( 'query' ),
                )
            ),
            $this->build_function_tool(
                'create_niwa_blog',
                'Primary canonical blog creator. Takes Markdown, applies deterministic safe fixes, validates intent/cannibalization/copy/structure/contextual internal links/taxonomy/media/SEO/rendering, then creates a managed WordPress draft only when critical checks pass. CSS, progress JavaScript, schema, source hash, selected links, and the structured quality report are stored outside post_content.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'title'   => array( 'type' => 'string', 'description' => 'Blog post title (35-95 characters recommended).' ),
                        'content' => array( 'type' => 'string', 'description' => 'Blog post content in Markdown. Use H2 sections and H3 subsections, never a body H1. Full posts normally target 800 useful words and 3-6 verified contextual internal links.' ),
                        'excerpt' => array( 'type' => 'string', 'description' => 'Meta description / excerpt (50-160 characters). Must be unique, not copied from first paragraph.' ),
                        'slug'    => array( 'type' => 'string', 'description' => 'URL slug. Auto-generated from title with Serbian transliteration if not provided.' ),
                        'category' => array( 'type' => 'string', 'description' => 'Existing primary category. Missing terms are skipped unless allow_new_terms=true.' ),
                        'tags'    => array(
                            'type'  => 'array',
                            'items' => array( 'type' => 'string' ),
                            'description' => 'Zero to three existing useful tags. Missing terms are skipped unless allow_new_terms=true.',
                        ),
                        'focus_keyword' => array( 'type' => 'string', 'description' => 'Primary SEO keyword. Used for audit scoring.' ),
                        'accent_color'  => array( 'type' => 'string', 'description' => 'Accent color for blog styling (default: #3b82f6).' ),
                        'language'      => array(
							'type' => 'string',
							'description' => 'Optional BCP 47 language tag for the article. WordPress translations own generated UI labels.',
                        ),
                        'featured_image_id' => array( 'type' => 'integer', 'description' => 'WordPress attachment ID for featured image. Only set if a valid image exists — never force a placeholder.' ),
						'search_intent' => array( 'type' => 'string', 'description' => 'Clear search intent; conservatively derived when omitted.' ),
						'audience' => array( 'type' => 'string', 'description' => 'Intended reader and problem context; conservatively derived when omitted.' ),
						'money_page_url' => array( 'type' => 'string', 'description' => 'Optional real published destination money page which must appear as a natural contextual link.' ),
						'evidence_urls' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'description' => 'Sources supplied for exact external claims.' ),
						'author_id' => array( 'type' => 'integer', 'description' => 'Optional real WordPress author ID. Uses the configured/current valid user when omitted.' ),
						'allow_new_terms' => array( 'type' => 'boolean', 'description' => 'Explicit opt-in to create missing terms at execution time. Preview never creates terms.' ),
                    ),
					'required' => array( 'title', 'content' ),
                )
            ),
            $this->build_function_tool(
                'scan_site_for_linking',
                'Scan all published content on the WordPress site and suggest internal links for a blog post. Scores existing pages/posts/products by keyword overlap. Also validates existing internal links in the content for broken links.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'post_content' => array(
                            'type'        => 'string',
                            'description' => 'Blog post content text to find internal links for.',
                        ),
                        'post_title' => array(
                            'type'        => 'string',
                            'description' => 'Blog post title for keyword matching.',
                        ),
                        'post_slug' => array(
                            'type'        => 'string',
                            'description' => 'Blog post slug to exclude from link suggestions.',
                        ),
                        'max_suggestions' => array(
                            'type'        => 'integer',
                            'minimum'     => 1,
                            'maximum'     => 10,
                            'description' => 'Maximum number of link suggestions. Default: 5.',
                        ),
                        'priority_paths' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'string' ),
                            'description' => 'Paths to boost in scoring (money pages, e.g., /kontakt, /usluge).',
                        ),
                    ),
                    'required' => array( 'post_content' ),
                )
            ),

            $this->build_function_tool(
                'bulk_set_individual_prices',
                'Set unique individual prices for multiple products and/or variations in one operation. Each item gets its own specific price. Use after reading a price list file via read_uploaded_spreadsheet, or when the user provides per-product prices. This is the preferred sale-only and per-variation pricing path when the founder wants SALE prices without touching regular price. For variable products: if all variations share the same price, new price applies to all; if prices differ, the tool will ask for per-variation clarification.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'items' => array(
                            'type'        => 'array',
                            'description' => 'Array of items, each with its own target identifier and price.',
                            'maxItems'    => self::MAX_BULK_CATALOG_TARGETS,
                            'items'       => array(
                                'type'       => 'object',
                                'properties' => array(
                                    'product_id'    => array( 'type' => 'integer', 'description' => 'WooCommerce product ID (simple or variable parent). Use for exact lookups and to avoid fuzzy matching.' ),
                                    'variation_id'  => array( 'type' => 'integer', 'description' => 'Specific variation ID (for targeting a single variation). Use when the exact variation is already known.' ),
                                    'sku'           => array( 'type' => 'string', 'description' => 'Product or variation SKU — resolved to ID if product_id/variation_id not given.' ),
                                    'product_query' => array( 'type' => 'string', 'description' => 'Product name or search query — resolved via memory search if product_id/variation_id/sku not given or failed. Prefer exact IDs when available.' ),
                                    'regular_price' => array( 'type' => 'string', 'description' => 'New regular price as decimal string. Leave empty for sale-only updates.' ),
                                    'sale_price'    => array( 'type' => 'string', 'description' => 'New sale price as decimal string (optional). Use alone for sale-only updates.' ),
                                    'clear_sale_price' => array( 'type' => 'boolean', 'description' => 'Remove sale price if true.' ),
                                ),
                            ),
                        ),
                    ),
                    'required' => array( 'items' ),
                )
            ),

            /* ================================================================
             * Users Management
             * ============================================================= */
            $this->build_function_tool(
                'search_users',
                'Search and list WordPress users with filtering by role, search term, and pagination.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'search'   => array( 'type' => 'string', 'description' => 'Search term to match against username, email, or display name.' ),
                        'role'     => array( 'type' => 'string', 'enum' => array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ), 'description' => 'Filter by user role.' ),
                        'per_page' => array( 'type' => 'integer', 'description' => 'Results per page (1-100, default 10).' ),
                        'page'     => array( 'type' => 'integer', 'description' => 'Page number (default 1).' ),
                        'orderby'  => array( 'type' => 'string', 'enum' => array( 'id', 'name', 'email', 'registered' ), 'description' => 'Order by field.' ),
                        'order'    => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ), 'description' => 'Sort direction.' ),
                    ),
                    'required' => array(),
                )
            ),
            $this->build_function_tool(
                'get_user',
                'Get detailed information about a specific WordPress user by ID.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'user_id' => array( 'type' => 'integer', 'description' => 'WordPress user ID.' ),
                    ),
                    'required' => array( 'user_id' ),
                )
            ),
            $this->build_function_tool(
                'create_user',
                'Create a new WordPress user account with specified role and profile details.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'username'     => array( 'type' => 'string', 'description' => 'Login username (required, cannot be changed later).' ),
                        'email'        => array( 'type' => 'string', 'description' => 'User email address (required).' ),
                        'password'     => array( 'type' => 'string', 'description' => 'Password. Auto-generated if empty.' ),
                        'role'         => array( 'type' => 'string', 'enum' => array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ), 'description' => 'User role (default: subscriber).' ),
                        'first_name'   => array( 'type' => 'string', 'description' => 'First name.' ),
                        'last_name'    => array( 'type' => 'string', 'description' => 'Last name.' ),
                        'display_name' => array( 'type' => 'string', 'description' => 'Display name.' ),
                        'description'  => array( 'type' => 'string', 'description' => 'User bio/description.' ),
                        'url'          => array( 'type' => 'string', 'description' => 'User website URL.' ),
                    ),
                    'required' => array( 'username', 'email' ),
                )
            ),
            $this->build_function_tool(
                'update_user',
                'Update an existing WordPress user profile (email, name, role, bio, etc.).',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'user_id'      => array( 'type' => 'integer', 'description' => 'WordPress user ID to update.' ),
                        'email'        => array( 'type' => 'string', 'description' => 'New email address.' ),
                        'first_name'   => array( 'type' => 'string', 'description' => 'New first name.' ),
                        'last_name'    => array( 'type' => 'string', 'description' => 'New last name.' ),
                        'display_name' => array( 'type' => 'string', 'description' => 'New display name.' ),
                        'nickname'     => array( 'type' => 'string', 'description' => 'New nickname.' ),
                        'description'  => array( 'type' => 'string', 'description' => 'New bio/description.' ),
                        'url'          => array( 'type' => 'string', 'description' => 'New website URL.' ),
                        'role'         => array( 'type' => 'string', 'enum' => array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ), 'description' => 'New role.' ),
                        'password'     => array( 'type' => 'string', 'description' => 'New password.' ),
                    ),
                    'required' => array( 'user_id' ),
                )
            ),
            $this->build_function_tool(
                'delete_user',
                'Permanently delete a WordPress user. DESTRUCTIVE — cannot be undone.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'user_id'     => array( 'type' => 'integer', 'description' => 'User ID to delete.' ),
                        'reassign_to' => array( 'type' => 'integer', 'description' => 'Optional user ID to reassign posts to.' ),
                    ),
                    'required' => array( 'user_id' ),
                )
            ),
            $this->build_function_tool(
                'get_current_user',
                'Get profile information about the currently logged-in WordPress user.',
                array(
                    'type'       => 'object',
                    'properties' => array(),
                    'required'   => array(),
                )
            ),
            $this->build_function_tool(
                'update_current_user',
                'Update the currently logged-in user profile.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'email'        => array( 'type' => 'string', 'description' => 'New email.' ),
                        'first_name'   => array( 'type' => 'string', 'description' => 'New first name.' ),
                        'last_name'    => array( 'type' => 'string', 'description' => 'New last name.' ),
                        'display_name' => array( 'type' => 'string', 'description' => 'New display name.' ),
                        'nickname'     => array( 'type' => 'string', 'description' => 'New nickname.' ),
                        'description'  => array( 'type' => 'string', 'description' => 'New bio.' ),
                        'url'          => array( 'type' => 'string', 'description' => 'New website URL.' ),
                    ),
                    'required' => array(),
                )
            ),

            /* ================================================================
             * Taxonomy — Categories & Tags
             * ============================================================= */
            $this->build_function_tool( 'list_categories', 'List WordPress post categories with pagination, search, and parent filtering.', array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string', 'description' => 'Search term.' ), 'parent' => array( 'type' => 'integer', 'description' => 'Filter by parent category ID.' ), 'per_page' => array( 'type' => 'integer', 'description' => 'Results per page (1-100, default 20).' ), 'page' => array( 'type' => 'integer', 'description' => 'Page number.' ), 'orderby' => array( 'type' => 'string', 'enum' => array( 'name', 'id', 'count', 'slug' ) ), 'order' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ) ), 'hide_empty' => array( 'type' => 'boolean', 'description' => 'Hide empty categories.' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'get_category', 'Get a single WordPress category by ID.', array( 'type' => 'object', 'properties' => array( 'category_id' => array( 'type' => 'integer', 'description' => 'Category term ID.' ) ), 'required' => array( 'category_id' ) ) ),
            $this->build_function_tool( 'create_category', 'Create a new WordPress post category.', array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string', 'description' => 'Category name (required).' ), 'slug' => array( 'type' => 'string', 'description' => 'URL slug.' ), 'description' => array( 'type' => 'string', 'description' => 'Category description.' ), 'parent' => array( 'type' => 'integer', 'description' => 'Parent category ID for nesting.' ) ), 'required' => array( 'name' ) ) ),
            $this->build_function_tool( 'update_category', 'Update an existing WordPress category.', array( 'type' => 'object', 'properties' => array( 'category_id' => array( 'type' => 'integer', 'description' => 'Category ID to update.' ), 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'parent' => array( 'type' => 'integer' ) ), 'required' => array( 'category_id' ) ) ),
            $this->build_function_tool( 'delete_category', 'Delete a WordPress category. Cannot delete default category.', array( 'type' => 'object', 'properties' => array( 'category_id' => array( 'type' => 'integer', 'description' => 'Category ID to delete.' ) ), 'required' => array( 'category_id' ) ) ),
            $this->build_function_tool( 'list_tags', 'List WordPress post tags with pagination and search.', array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string', 'description' => 'Search term.' ), 'per_page' => array( 'type' => 'integer', 'description' => 'Results per page (1-100, default 20).' ), 'page' => array( 'type' => 'integer' ), 'orderby' => array( 'type' => 'string', 'enum' => array( 'name', 'id', 'count', 'slug' ) ), 'order' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ) ), 'hide_empty' => array( 'type' => 'boolean' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'get_tag', 'Get a single WordPress tag by ID.', array( 'type' => 'object', 'properties' => array( 'tag_id' => array( 'type' => 'integer', 'description' => 'Tag term ID.' ) ), 'required' => array( 'tag_id' ) ) ),
            $this->build_function_tool( 'create_tag', 'Create a new WordPress post tag.', array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string', 'description' => 'Tag name (required).' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ) ), 'required' => array( 'name' ) ) ),
            $this->build_function_tool( 'update_tag', 'Update an existing WordPress tag.', array( 'type' => 'object', 'properties' => array( 'tag_id' => array( 'type' => 'integer', 'description' => 'Tag ID to update.' ), 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ) ), 'required' => array( 'tag_id' ) ) ),
            $this->build_function_tool( 'delete_tag', 'Delete a WordPress tag.', array( 'type' => 'object', 'properties' => array( 'tag_id' => array( 'type' => 'integer', 'description' => 'Tag ID to delete.' ) ), 'required' => array( 'tag_id' ) ) ),

            /* ================================================================
             * WordPress Settings & Site Info
             * ============================================================= */
            $this->build_function_tool( 'get_wp_settings', 'Get all WordPress general settings (title, description, timezone, date format, etc.).', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'update_wp_settings', 'Update WordPress general settings. Supports: title, description, admin_email, timezone, date/time format, language, posts_per_page, comment/ping status.', array( 'type' => 'object', 'properties' => array( 'title' => array( 'type' => 'string', 'description' => 'Site title (blogname).' ), 'description' => array( 'type' => 'string', 'description' => 'Site tagline.' ), 'admin_email' => array( 'type' => 'string', 'description' => 'Admin email address.' ), 'timezone_string' => array( 'type' => 'string', 'description' => 'Timezone (e.g. Europe/Belgrade).' ), 'date_format' => array( 'type' => 'string', 'description' => 'Date format (e.g. Y-m-d).' ), 'time_format' => array( 'type' => 'string', 'description' => 'Time format (e.g. H:i).' ), 'start_of_week' => array( 'type' => 'integer', 'description' => '0=Sunday, 1=Monday, ..., 6=Saturday.' ), 'language' => array( 'type' => 'string', 'description' => 'Site language code.' ), 'posts_per_page' => array( 'type' => 'integer', 'description' => 'Posts per page (1-100).' ), 'default_comment_status' => array( 'type' => 'string', 'enum' => array( 'open', 'closed' ) ), 'default_ping_status' => array( 'type' => 'string', 'enum' => array( 'open', 'closed' ) ) ), 'required' => array() ) ),
            $this->build_function_tool(
                'niwa_settings',
                'Discover, read and precisely update Niwa product settings through native validated schemas. Covers booking services, team members and working hours; Lead Form creation, appointment fields and conditional branching; frontend theme and agent profile; attention capture; conversion boosters; lead capture and code-email reminders; and cart-recovery follow-up templates. Use operation=catalog first when the domain is unknown, then get before update.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'operation' => array(
                            'type'        => 'string',
                            'enum'        => array( 'catalog', 'get', 'update' ),
                            'description' => 'catalog lists supported domains and related native tools; get returns canonical settings plus revision; update applies a top-level patch after validation.',
                        ),
                        'domain' => array(
                            'type' => 'string',
                            'enum' => array( 'scheduler', 'lead_form', 'frontend_ui', 'frontend_agent', 'attention_capture', 'conversion_busters', 'lead_capture', 'cart_recovery' ),
                        ),
                        'settings' => array(
                            'type'                 => 'object',
                            'description'          => 'Top-level settings patch. Collection values replace their complete collection; preserve wanted items from get.',
                            'additionalProperties' => true,
                        ),
                        'expected_revision' => array(
                            'type'        => 'string',
                            'description' => 'Exact revision returned by the latest get for this domain. Required for update.',
                        ),
                    ),
                    'required' => array( 'operation' ),
                )
            ),
            $this->build_function_tool( 'get_site_info', 'Get comprehensive WordPress site information: version, plugins, theme, user stats, content counts, front-page setup, and homepage heading candidates from rendered HTML plus active theme source.', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'update_operative_heartbeat_config', 'Atomically update the operative heartbeat configuration and, when task_brief is provided, write the matching heartbeat brief in the same tool call. Admin operative heartbeat reads OPERATIVE_HEARTBEAT.md; stock-manager operative heartbeat reads STOCK_OPERATIVE_HEARTBEAT.md. When enabling without an existing non-empty brief, task_brief is required unless execution_mode="static_exact_message" with static_message is provided. Use only for exact recurring message delivery, true heartbeat-style automation, or multi-tick continuation tasks. Do not use heartbeat as a generic future date/time checker; concrete future site mutations and delayed reversals belong in schedule_admin_plan, and future reasoning/script/code work belongs in schedule_self_wake.', array( 'type' => 'object', 'properties' => array( 'target_role' => array( 'type' => 'string', 'enum' => array( 'admin', 'stock_manager' ), 'description' => 'Which operative heartbeat channel to configure. Use admin for founder/admin workflows and stock_manager for stock-manager catalog/inventory workflows.' ), 'interval_minutes' => array( 'type' => 'number', 'minimum' => 0.5, 'description' => 'How often (in minutes) the operative heartbeat executes true recurring or continuation work. Minimum 0.5 (30 seconds). Use schedule_admin_plan or schedule_reminder for one-time future dates.' ), 'scheduled_hours' => array( 'type' => 'array', 'items' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 23 ), 'description' => 'Optional local-time hour slots when this heartbeat may start an LLM run. Use for fixed daily recurring checks that need judgement/reporting, e.g. [9,15]. Non-slot ticks are skipped in PHP before the model starts.' ), 'scheduled_minute' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 59, 'description' => 'Minute within each scheduled_hours slot. Default 0.' ), 'clear_scheduled_hours' => array( 'type' => 'boolean', 'description' => 'Clear structural scheduled hour gating and return to pure interval-based heartbeat execution.' ), 'enabled' => array( 'type' => 'boolean', 'description' => 'Set true to enable operative heartbeat, false to disable.' ), 'execution_mode' => array( 'type' => 'string', 'enum' => array( 'dynamic_agent', 'static_exact_message' ), 'description' => 'Use static_exact_message only when each due tick should send exactly static_message without LLM/tool execution. Use dynamic_agent for continuation work or judgement.' ), 'static_message' => array( 'type' => 'string', 'description' => 'Exact message to send on every due tick when execution_mode is static_exact_message.' ), 'task_brief' => array( 'type' => 'string', 'description' => 'Durable operating brief to write before enabling. Include objective, exact user-facing message or action, cadence expectation, current checkpoint, next step, stop condition, duplicate/conflict policy, and what counts as done.' ), 'stop_condition' => array( 'type' => 'string', 'description' => 'Explicit stop condition for the recurring or continuation task.' ), 'end_at_local' => array( 'type' => 'string', 'description' => 'Optional hard end datetime in the site/local timezone, format YYYY-MM-DD HH:MM. Use when the founder says this recurring task should stop at a concrete time.' ), 'end_at_utc' => array( 'type' => 'string', 'description' => 'Optional hard end datetime in UTC, format YYYY-MM-DD HH:MM:SS. Prefer end_at_local for founder-local requests.' ), 'clear_end_at' => array( 'type' => 'boolean', 'description' => 'Clear the stored hard end time.' ) ) ) ),
            $this->build_function_tool( 'manage_whatsapp_access_numbers', 'Add, remove, or list explicit WhatsApp access numbers for Niwa. Roles: admin has full founder access, stock_manager can manage WooCommerce products, and lead can chat as a customer/lead without admin tools. Public lead/customer WhatsApp access is explicit: add * as a lead number; remove * from leads to close it.', array( 'type' => 'object', 'properties' => array( 'action' => array( 'type' => 'string', 'enum' => array( 'add', 'remove', 'list' ), 'description' => 'Whether to add, remove, or list WhatsApp access numbers.' ), 'role' => array( 'type' => 'string', 'enum' => array( 'admin', 'stock_manager', 'lead' ), 'description' => 'Access role for the number.' ), 'phone' => array( 'type' => 'string', 'description' => 'Phone number to add or remove, e.g. +381691234567. Use * only for explicit public lead/customer access.' ), 'label' => array( 'type' => 'string', 'description' => 'Optional human label for the number.' ) ), 'required' => array( 'action' ) ) ),
            $this->build_function_tool( 'send_whatsapp_lead_outreach', 'Send an outbound WhatsApp message to a saved lead/customer or founder-approved phone number. Saves the contact, adds the number as a WhatsApp lead, and queues the message through the Niwa gateway. Use for lead/customer outreach, not founder/admin delivery.', array( 'type' => 'object', 'properties' => array( 'contact_id' => array( 'type' => 'integer', 'description' => 'Existing Niwa contact id. Preferred when known.' ), 'phone' => array( 'type' => 'string', 'description' => 'Lead/customer WhatsApp phone number when no contact id is known.' ), 'query' => array( 'type' => 'string', 'description' => 'Search saved Niwa contacts by name, email, or phone. Use only when contact_id or phone is not known.' ), 'name' => array( 'type' => 'string', 'description' => 'Optional lead/customer name for a new direct phone target.' ), 'email' => array( 'type' => 'string', 'description' => 'Optional lead/customer email for a new direct phone target.' ), 'message' => array( 'type' => 'string', 'description' => 'Exact WhatsApp message body to send.' ), 'source' => array( 'type' => 'string', 'description' => 'Structured source tag such as founder_approved_outreach, post_purchase_review, lead_form_followup, stock_waitlist, or cart_recovery_whatsapp.' ), 'workflow_key' => array( 'type' => 'string', 'description' => 'Optional stable workflow identifier for scheduled/follow-up jobs.' ), 'dry_run' => array( 'type' => 'boolean', 'description' => 'When true, validate and sync the lead target without queueing the WhatsApp message.' ) ), 'required' => array( 'message' ) ) ),

            /* ================================================================
             * Pages CRUD
             * ============================================================= */
            $this->build_function_tool( 'search_pages', 'Search WordPress pages with filtering by status, parent, and pagination.', array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string', 'description' => 'Search term for page titles.' ), 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'draft', 'pending', 'private', 'trash', 'any' ), 'description' => 'Page status filter.' ), 'per_page' => array( 'type' => 'integer', 'description' => 'Results per page (1-100).' ), 'page' => array( 'type' => 'integer' ), 'parent' => array( 'type' => 'integer', 'description' => 'Filter by parent page ID.' ), 'orderby' => array( 'type' => 'string', 'enum' => array( 'date', 'title', 'modified', 'menu_order', 'id' ) ), 'order' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ) ) ), 'required' => array() ) ),
            $this->build_function_tool( 'get_page', 'Get a single WordPress page by ID with full content.', array( 'type' => 'object', 'properties' => array( 'page_id' => array( 'type' => 'integer', 'description' => 'Page post ID.' ) ), 'required' => array( 'page_id' ) ) ),
            $this->build_function_tool( 'create_page', 'Create a new WordPress page.', array( 'type' => 'object', 'properties' => array( 'title' => array( 'type' => 'string', 'description' => 'Page title (required).' ), 'content' => array( 'type' => 'string', 'description' => 'Page content (HTML).' ), 'excerpt' => array( 'type' => 'string' ), 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'draft', 'pending', 'private' ), 'description' => 'Default: draft.' ), 'parent' => array( 'type' => 'integer', 'description' => 'Parent page ID.' ), 'template' => array( 'type' => 'string', 'description' => 'Page template filename.' ), 'menu_order' => array( 'type' => 'integer' ), 'featured_image_id' => array( 'type' => 'integer' ), 'comment_status' => array( 'type' => 'string', 'enum' => array( 'open', 'closed' ) ) ), 'required' => array( 'title' ) ) ),
            $this->build_function_tool( 'update_page', 'Update an existing WordPress page. Use title for the stored/internal page title used by admin lists, title-tag/SEO sources when the theme/plugin reads post_title, breadcrumbs, lists/search results, slug-identification workflows, and fallback page-title rendering. Do not use title alone when the founder asked for a visible homepage hero/headline; use update_front_page_title or page-content replacement for that.', array( 'type' => 'object', 'properties' => array( 'page_id' => array( 'type' => 'integer', 'description' => 'Page ID to update.' ), 'title' => array( 'type' => 'string', 'description' => 'Stored WordPress page title/post_title. This is internal/admin/SEO/list/fallback title storage, not necessarily visible hero copy.' ), 'content' => array( 'type' => 'string' ), 'excerpt' => array( 'type' => 'string' ), 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'draft', 'pending', 'private' ) ), 'slug' => array( 'type' => 'string' ), 'parent' => array( 'type' => 'integer' ), 'template' => array( 'type' => 'string' ), 'menu_order' => array( 'type' => 'integer' ), 'featured_image_id' => array( 'type' => 'integer' ), 'comment_status' => array( 'type' => 'string', 'enum' => array( 'open', 'closed' ) ) ), 'required' => array( 'page_id' ) ) ),
            $this->build_function_tool( 'delete_page', 'Delete a WordPress page (moves to trash, or permanently with force=true). Cannot delete front page.', array( 'type' => 'object', 'properties' => array( 'page_id' => array( 'type' => 'integer', 'description' => 'Page ID to delete.' ), 'force' => array( 'type' => 'boolean', 'description' => 'Permanently delete (default: false, moves to trash).' ) ), 'required' => array( 'page_id' ) ) ),

            /* ================================================================
             * Media Management
             * ============================================================= */
            $this->build_function_tool( 'search_media', 'Search WordPress media library by title, type, author, or parent post.', array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string', 'description' => 'Search in titles, captions, descriptions.' ), 'media_type' => array( 'type' => 'string', 'enum' => array( 'image', 'video', 'audio', 'application' ), 'description' => 'Filter by media type.' ), 'mime_type' => array( 'type' => 'string', 'description' => 'Specific MIME type (e.g. image/jpeg).' ), 'author' => array( 'type' => 'integer', 'description' => 'Filter by user ID.' ), 'parent' => array( 'type' => 'integer', 'description' => 'Filter by parent post ID.' ), 'per_page' => array( 'type' => 'integer' ), 'page' => array( 'type' => 'integer' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'get_media_details', 'Get full details of a media library item including metadata, sizes, and file info.', array( 'type' => 'object', 'properties' => array( 'media_id' => array( 'type' => 'integer', 'description' => 'Attachment post ID.' ) ), 'required' => array( 'media_id' ) ) ),
            $this->build_function_tool( 'update_media_metadata', 'Update media item title, alt text, caption, or description.', array( 'type' => 'object', 'properties' => array( 'media_id' => array( 'type' => 'integer', 'description' => 'Attachment ID to update.' ), 'title' => array( 'type' => 'string' ), 'alt_text' => array( 'type' => 'string', 'description' => 'Image alt text for accessibility.' ), 'caption' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ) ), 'required' => array( 'media_id' ) ) ),
            $this->build_function_tool( 'delete_media', 'Delete or detach a media library item. Default: detaches from parent post. Force: permanently deletes file and DB entry.', array( 'type' => 'object', 'properties' => array( 'media_id' => array( 'type' => 'integer', 'description' => 'Attachment ID.' ), 'force' => array( 'type' => 'boolean', 'description' => 'Permanently delete (default: false, just detaches).' ) ), 'required' => array( 'media_id' ) ) ),

            /* ================================================================
             * Elementor Widget Manipulation
             * ============================================================= */
            $this->build_function_tool( 'elementor_list_pages', 'List all pages/posts built with Elementor.', array( 'type' => 'object', 'properties' => array( 'post_type' => array( 'type' => 'string', 'enum' => array( 'page', 'post', 'any' ), 'description' => 'Filter by post type (default: any).' ), 'post_status' => array( 'type' => 'string', 'enum' => array( 'publish', 'draft', 'pending', 'private', 'any' ) ), 'limit' => array( 'type' => 'integer', 'description' => 'Max results (1-100, default 50).' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'elementor_get_page_structure', 'Get the widget structure tree of an Elementor page.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'max_depth' => array( 'type' => 'integer', 'description' => 'Max tree depth (1-20, default 10).' ), 'include_settings' => array( 'type' => 'boolean', 'description' => 'Include widget settings in output.' ) ), 'required' => array( 'post_id' ) ) ),
            $this->build_function_tool( 'elementor_get_page_data', 'Read raw _elementor_data for an Elementor page with SHA-256 metadata. Use before elementor_update_page_data when a full no-shell structural edit is needed.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'include_raw' => array( 'type' => 'boolean', 'description' => 'Return the raw JSON string. Default true.' ), 'max_chars' => array( 'type' => 'integer', 'description' => 'Maximum raw JSON characters to return, 1000-500000. Default 120000.' ) ), 'required' => array( 'post_id' ) ) ),
            $this->build_function_tool( 'elementor_get_active_kit', 'Get the active Elementor design kit with global colors and typography.', array( 'type' => 'object', 'properties' => array( 'include_colors' => array( 'type' => 'boolean', 'description' => 'Include color definitions (default: true).' ), 'include_typography' => array( 'type' => 'boolean', 'description' => 'Include typography (default: true).' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'elementor_get_kit_by_id', 'Get a specific Elementor kit by ID.', array( 'type' => 'object', 'properties' => array( 'kit_id' => array( 'type' => 'integer', 'description' => 'Elementor kit post ID.' ), 'include_colors' => array( 'type' => 'boolean' ), 'include_typography' => array( 'type' => 'boolean' ) ), 'required' => array( 'kit_id' ) ) ),
            $this->build_function_tool( 'elementor_find_widgets', 'Find widgets by type in an Elementor page (e.g. all headings, buttons, images).', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'widget_types' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'description' => 'Widget types to find: heading, button, image, text-editor, etc.' ), 'max_depth' => array( 'type' => 'integer' ), 'include_settings' => array( 'type' => 'boolean' ) ), 'required' => array( 'post_id', 'widget_types' ) ) ),
            $this->build_function_tool( 'elementor_get_widget_by_id', 'Get a specific Elementor widget by its unique ID.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'widget_id' => array( 'type' => 'string', 'description' => 'Elementor widget unique ID.' ) ), 'required' => array( 'post_id', 'widget_id' ) ) ),
            $this->build_function_tool( 'elementor_update_widget_content', 'Update the text content of an Elementor widget (heading, button, or text-editor).', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'widget_id' => array( 'type' => 'string', 'description' => 'Widget unique ID.' ), 'content' => array( 'type' => 'string', 'description' => 'New text content.' ) ), 'required' => array( 'post_id', 'widget_id', 'content' ) ) ),
            $this->build_function_tool( 'elementor_update_widget_image', 'Update the image of an Elementor image widget.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer' ), 'widget_id' => array( 'type' => 'string' ), 'image_url' => array( 'type' => 'string', 'description' => 'New image URL.' ), 'image_id' => array( 'type' => 'integer', 'description' => 'Optional WP attachment ID.' ), 'alt_text' => array( 'type' => 'string' ) ), 'required' => array( 'post_id', 'widget_id', 'image_url' ) ) ),
            $this->build_function_tool( 'elementor_update_widget_link', 'Update the link URL of an Elementor button widget.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer' ), 'widget_id' => array( 'type' => 'string' ), 'url' => array( 'type' => 'string', 'description' => 'New link URL.' ), 'is_external' => array( 'type' => 'boolean', 'description' => 'Open in new tab.' ), 'nofollow' => array( 'type' => 'boolean', 'description' => 'Add nofollow.' ) ), 'required' => array( 'post_id', 'widget_id', 'url' ) ) ),
            $this->build_function_tool( 'elementor_update_widget_styles', 'Update styles of an Elementor widget (colors, typography, padding, margin).', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer' ), 'widget_id' => array( 'type' => 'string' ), 'styles' => array( 'type' => 'object', 'description' => 'Style object with optional: colors (text_color, background_color, etc.), typography (font_family, font_size, font_weight), padding/margin (top, right, bottom, left, unit).' ) ), 'required' => array( 'post_id', 'widget_id', 'styles' ) ) ),
            $this->build_function_tool( 'elementor_update_widget_settings', 'Precisely patch any Elementor element settings object, including advanced/responsive controls, nested link/image objects, __globals__, motion, layout, and widget-specific keys. Uses Niwa preview/execute rollback; does not require full page JSON replacement.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'widget_id' => array( 'type' => 'string', 'description' => 'Elementor element ID.' ), 'settings' => array( 'type' => 'object', 'description' => 'Settings patch to merge or replace. Supports nested objects and Elementor raw setting keys.' ), 'unset_keys' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'description' => 'Optional setting keys or dotted nested paths to remove.' ), 'mode' => array( 'type' => 'string', 'enum' => array( 'merge', 'replace' ), 'description' => 'merge keeps existing settings and overlays supplied keys. replace resets settings to the supplied object first. Default merge.' ) ), 'required' => array( 'post_id', 'widget_id' ) ) ),
            $this->build_function_tool( 'elementor_duplicate_element', 'Duplicate an Elementor section/container/column/widget next to the original with fresh IDs for the cloned subtree. Uses full-page rollback entry so the duplicate can be undone in one step.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'element_id' => array( 'type' => 'string', 'description' => 'Elementor element ID to duplicate.' ), 'position' => array( 'type' => 'string', 'enum' => array( 'before', 'after' ), 'description' => 'Where to place the clone relative to the original. Default after.' ) ), 'required' => array( 'post_id', 'element_id' ) ) ),
            $this->build_function_tool( 'elementor_update_kit_settings', 'Patch Elementor active kit page settings such as system/custom colors, typography, viewport breakpoints, site settings, and global style controls. Defaults to the active kit and supports rollback.', array( 'type' => 'object', 'properties' => array( 'kit_id' => array( 'type' => 'integer', 'description' => 'Optional Elementor kit post ID. Defaults to elementor_active_kit.' ), 'settings' => array( 'type' => 'object', 'description' => 'Kit _elementor_page_settings patch to merge or replace.' ), 'unset_keys' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'description' => 'Optional setting keys or dotted nested paths to remove.' ), 'mode' => array( 'type' => 'string', 'enum' => array( 'merge', 'replace' ), 'description' => 'merge keeps existing kit settings and overlays supplied keys. replace resets settings first. Default merge.' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'elementor_update_page_data', 'Replace the full _elementor_data JSON for an Elementor page using native WordPress APIs, with optional post_content fallback update and SHA-256 guard. Use only after elementor_get_page_data or elementor_get_page_structure confirms the target tree and the new JSON has been validated.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'elementor_data_json' => array( 'type' => 'string', 'description' => 'Full replacement _elementor_data JSON string.' ), 'expected_current_sha256' => array( 'type' => 'string', 'description' => 'Optional SHA-256 of the current raw _elementor_data read immediately before editing; prevents overwriting concurrent changes.' ), 'post_content' => array( 'type' => 'string', 'description' => 'Optional replacement post_content fallback when the visible/fallback HTML should be synchronized.' ) ), 'required' => array( 'post_id', 'elementor_data_json' ) ) ),
            $this->build_function_tool( 'elementor_search_text', 'Search ALL Elementor pages on the site for a text string. Finds the exact page ID, widget ID, widget type, and text context — even when text contains HTML tags or special formatting. Use this FIRST when you need to find or replace text on the site but do not know which page it is on. After finding the widget, use elementor_update_widget_content to change it.', array( 'type' => 'object', 'properties' => array( 'search_text' => array( 'type' => 'string', 'description' => 'Text to search for (case-insensitive, partial match). HTML tags are stripped before matching.' ), 'strip_html' => array( 'type' => 'boolean', 'description' => 'Strip HTML tags before matching (default: true). Set false to search raw HTML.' ) ), 'required' => array( 'search_text' ) ) ),

            $this->build_function_tool( 'elementor_build_page_from_url', 'Create or rebuild a page from a public reference URL as real Elementor containers and native widgets. This fetches the URL, extracts a section blueprint, optionally lets Niwa LLM refine hierarchy, and writes editable _elementor_data. Use this for whole-page reference builds, Angie-like landing page rebuilds, or when the founder provides a URL and asks Niwa to recreate it element-by-element. Output must not be a shortcode or html widget block.', array( 'type' => 'object', 'properties' => array( 'url' => array( 'type' => 'string', 'description' => 'Public http/https reference URL to inspect.' ), 'post_id' => array( 'type' => 'integer', 'description' => 'Optional existing Elementor page/post ID. Required for replace or append.' ), 'title' => array( 'type' => 'string', 'description' => 'Title for a newly created page, or label for the rebuild.' ), 'status' => array( 'type' => 'string', 'enum' => array( 'draft', 'publish', 'pending', 'private' ), 'description' => 'Status for a newly created page. Default draft.' ), 'mode' => array( 'type' => 'string', 'enum' => array( 'create', 'replace', 'append' ), 'description' => 'create makes a new Elementor page; replace overwrites an existing page; append adds sections to an existing page.' ), 'instructions' => array( 'type' => 'string', 'description' => 'Founder/site-specific adaptation notes: keep same section order, localize copy, adapt brand colors, replace assets, no protected third-party asset copying unless authorized, etc.' ) ), 'required' => array( 'url' ) ) ),

            /* ================================================================
             * Template Library & Generated Snippets
             * ============================================================= */
            $this->build_function_tool( 'template_library_list', 'List Niwa Template Library cards and generated snippets. Use before Elementor/template work to discover reusable template ideas, snippet IDs, shortcode values, and whether Elementor is active.', array( 'type' => 'object', 'properties' => array( 'type' => array( 'type' => 'string', 'enum' => array( 'featured', 'elementor-widget', 'wordpress-admin-snippet', 'website-snippet', 'visual-app' ), 'description' => 'Optional template type filter.' ), 'limit' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, 'description' => 'Maximum templates/snippets to return. Default 40.' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'template_library_create_template', 'Create a new reusable Niwa template card in the local library. Include agent_prompt and elementor_schema so Niwa can later reason from the card and build real Elementor elements, not only snippets.', array( 'type' => 'object', 'properties' => array( 'title' => array( 'type' => 'string', 'description' => 'Template title.' ), 'subtitle' => array( 'type' => 'string', 'description' => 'Short card subtitle.' ), 'type' => array( 'type' => 'string', 'enum' => array( 'featured', 'elementor-widget', 'wordpress-admin-snippet', 'website-snippet', 'visual-app' ) ), 'prompt' => array( 'type' => 'string', 'description' => 'Implementation prompt/requirements for generated snippets and Elementor blocks.' ), 'agent_prompt' => array( 'type' => 'string', 'description' => 'Instructions for Niwa on when to use this template, what to ask/check, and how to adapt it from chat.' ), 'elementor_schema' => array( 'type' => 'object', 'description' => 'Optional Elementor manifest: kind, title, items/plans/tabs/html/settings. Niwa uses this as the structured builder seed.', 'additionalProperties' => true ), 'manifest' => array( 'type' => 'object', 'description' => 'Optional capability manifest for inputs, outputs, constraints, expected widgets, and fallback behavior.', 'additionalProperties' => true ), 'preview_prompt' => array( 'type' => 'string', 'description' => 'Image-generation prompt for the template card preview.' ), 'preview_url' => array( 'type' => 'string', 'description' => 'Optional existing preview image URL.' ) ), 'required' => array( 'title' ) ) ),
            $this->build_function_tool( 'template_library_generate_snippet', 'Use Niwa LLM to generate a local snippet artifact from a template card. The template prompt, agent_prompt, manifest, Elementor schema, and founder instructions are passed to the model. If LLM is unavailable, Niwa falls back to a deterministic safe artifact.', array( 'type' => 'object', 'properties' => array( 'template_id' => array( 'type' => 'integer', 'description' => 'Template card ID.' ), 'instructions' => array( 'type' => 'string', 'description' => 'Founder-specific customization instructions.' ), 'title' => array( 'type' => 'string', 'description' => 'Optional snippet title override.' ), 'force_shortcode' => array( 'type' => 'boolean', 'description' => 'Force shortcode fallback even if Elementor is installed.' ), 'publish' => array( 'type' => 'boolean', 'description' => 'Validate and publish immediately when safe.' ) ), 'required' => array( 'template_id' ) ) ),
            $this->build_function_tool( 'template_library_insert_elementor', 'Use a Niwa template as a real Elementor builder block: pass the template prompt/manifest to Niwa LLM, build Elementor JSON elements, append/prepend them to an Elementor page through the existing page-data rollback flow, and update post_content fallback. Use this when the founder asks Niwa to build or insert a template on an Elementor page. If Elementor is inactive or force_shortcode_fallback=true, it publishes a shortcode snippet instead.', array( 'type' => 'object', 'properties' => array( 'template_id' => array( 'type' => 'integer', 'description' => 'Template card ID.' ), 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID where the generated block should be inserted.' ), 'instructions' => array( 'type' => 'string', 'description' => 'Founder-specific adaptation request: copy, colors, product/service context, section placement, images, CTA, etc.' ), 'position' => array( 'type' => 'string', 'enum' => array( 'after', 'before' ), 'description' => 'Append after existing sections or prepend before them. Default after.' ), 'force_shortcode_fallback' => array( 'type' => 'boolean', 'description' => 'Publish shortcode snippet instead of editing Elementor JSON.' ) ), 'required' => array( 'template_id', 'post_id' ) ) ),
            $this->build_function_tool( 'template_library_publish_snippet', 'Validate and publish a generated Niwa snippet. Publishes live files under wp-content/niwa-snippets/prod and returns the shortcode for Elementor or normal pages.', array( 'type' => 'object', 'properties' => array( 'snippet_id' => array( 'type' => 'integer', 'description' => 'Generated snippet ID.' ) ), 'required' => array( 'snippet_id' ) ) ),
            $this->build_function_tool( 'template_library_generate_preview', 'Generate a new Media Library preview image for a Niwa template card and attach it to that card. Use this for template-gallery preview images, including variants inspired by an uploaded/reference UI screenshot.', array( 'type' => 'object', 'properties' => array( 'template_id' => array( 'type' => 'integer', 'description' => 'Template card ID.' ), 'prompt' => array( 'type' => 'string', 'description' => 'Optional image prompt. Defaults to the template preview_prompt.' ), 'title' => array( 'type' => 'string', 'description' => 'Media Library image title.' ), 'alt_text' => array( 'type' => 'string' ), 'quality' => array( 'type' => 'string', 'enum' => array( 'low', 'medium', 'high' ) ), 'size' => array( 'type' => 'string', 'enum' => array( '1024x1024', '1536x1024', '1024x1536' ) ), 'reference_image_urls' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'description' => 'Optional reference image URLs.' ), 'force_regenerate' => array( 'type' => 'boolean' ) ), 'required' => array( 'template_id' ) ) ),

            /* ================================================================
             * WooCommerce Orders & Reports
             * ============================================================= */
            $this->build_function_tool(
                'posta_epk_submit_orders',
                'Submit WooCommerce orders through the Posta EPK Connector plugin. Use when the founder asks to send one or more order IDs to Posta/kurir, create/print Posta labels/adresnice, submit buffered orders, or notify the buyer after courier handoff. For multiple orders the tool processes chunks of up to 4 and attempts one batch PDF label email per chunk.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'order_ids' => array(
                            'type'        => 'array',
                            'items'       => array( 'type' => 'integer' ),
                            'minItems'    => 1,
                            'maxItems'    => 40,
                            'description' => 'WooCommerce order IDs to submit. Preserve the founder-provided order.',
                        ),
                        'force_resend' => array(
                            'type'        => 'boolean',
                            'description' => 'Only true when the founder explicitly asks to resend/retry an order that may already have _posta_epk_status=success.',
                        ),
                        'dry_run' => array(
                            'type'        => 'boolean',
                            'description' => 'Preview what would be submitted without sending to Posta EPK.',
                        ),
                    ),
                    'required' => array( 'order_ids' ),
                )
            ),
            $this->build_function_tool( 'search_woo_orders', 'Search WooCommerce orders with filtering by status, customer, date range.', array( 'type' => 'object', 'properties' => array( 'status' => array( 'type' => 'string', 'enum' => array( 'pending', 'processing', 'on-hold', 'completed', 'cancelled', 'refunded', 'failed', 'any' ), 'description' => 'Order status filter.' ), 'customer' => array( 'type' => 'integer', 'description' => 'Customer user ID.' ), 'per_page' => array( 'type' => 'integer' ), 'page' => array( 'type' => 'integer' ), 'after' => array( 'type' => 'string', 'description' => 'Orders after date (Y-m-d).' ), 'before' => array( 'type' => 'string', 'description' => 'Orders before date (Y-m-d).' ), 'orderby' => array( 'type' => 'string', 'enum' => array( 'date', 'id', 'total' ) ), 'order' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ) ) ), 'required' => array() ) ),
            $this->build_function_tool( 'inspect_order_attribution', 'Read-only investigation of concrete WooCommerce order attribution using order meta, Niwa traffic conversions, sessions, touchpoints, and recent events.', array( 'type' => 'object', 'properties' => array( 'order_ids' => array( 'type' => 'array', 'items' => array( 'type' => 'integer' ), 'description' => 'Exact WooCommerce order IDs to inspect.' ), 'customer_names' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), 'description' => 'Structured customer names to match against recent WooCommerce billing names when order IDs are not known.' ), 'after' => array( 'type' => 'string', 'description' => 'Orders after date (Y-m-d).' ), 'before' => array( 'type' => 'string', 'description' => 'Orders before date (Y-m-d).' ), 'limit' => array( 'type' => 'integer', 'description' => 'Maximum matching orders to inspect when using customer_names. Max 20.' ), 'include_events' => array( 'type' => 'boolean', 'description' => 'Include bounded recent traffic events for the matched session. Default true.' ), 'event_limit' => array( 'type' => 'integer', 'description' => 'Maximum traffic events per order. Max 20.' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'woo_report_sales', 'Get WooCommerce sales report for a period (week/month/year) or date range.', array( 'type' => 'object', 'properties' => array( 'period' => array( 'type' => 'string', 'enum' => array( 'week', 'month', 'last_month', 'year' ), 'description' => 'Report period.' ), 'date_min' => array( 'type' => 'string', 'description' => 'Start date (Y-m-d).' ), 'date_max' => array( 'type' => 'string', 'description' => 'End date (Y-m-d).' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'woo_report_orders_totals', 'Get WooCommerce order counts grouped by status.', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'woo_report_products_totals', 'Get WooCommerce product counts grouped by type (simple, variable, etc.).', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'woo_report_customers_totals', 'Get WooCommerce customer counts (paying vs non-paying).', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'woo_report_coupons_totals', 'Get WooCommerce coupon usage totals.', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'woo_report_reviews_totals', 'Get WooCommerce product review counts by rating.', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),

            /* ================================================================
             * LiteSpeed Cache Management
             * ============================================================= */
            $this->build_function_tool( 'cache_settings_get', 'Get current LiteSpeed Cache settings (cache, minification, optimization flags).', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'cache_flush', 'Flush all LiteSpeed caches (page cache, CSS/JS, object cache).', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'accelerator_cache_refresh', 'Delete the active Seraphinite Accelerator cache and start its native Cleanup old process.', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'cache_presets_list', 'List available LiteSpeed Cache presets with risk levels.', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'cache_preset_apply', 'Apply a LiteSpeed Cache preset (essentials, basic, advanced, aggressive, extreme).', array( 'type' => 'object', 'properties' => array( 'preset' => array( 'type' => 'string', 'enum' => array( 'essentials', 'basic', 'advanced', 'aggressive', 'extreme' ), 'description' => 'Preset name to apply.' ) ), 'required' => array( 'preset' ) ) ),

            /* ================================================================
             * WooCommerce Product Taxonomy (Categories, Tags, Brands)
             * ============================================================= */
            $this->build_function_tool( 'list_wc_categories', 'List WooCommerce product categories with pagination and search.', array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string' ), 'parent' => array( 'type' => 'integer', 'description' => 'Filter by parent category ID.' ), 'per_page' => array( 'type' => 'integer' ), 'page' => array( 'type' => 'integer' ), 'orderby' => array( 'type' => 'string', 'enum' => array( 'name', 'id', 'count', 'slug' ) ), 'order' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ) ), 'hide_empty' => array( 'type' => 'boolean' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'create_wc_category', 'Create a new WooCommerce product category.', array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string', 'description' => 'Category name.' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'parent' => array( 'type' => 'integer', 'description' => 'Parent category ID.' ), 'image_id' => array( 'type' => 'integer', 'description' => 'Thumbnail attachment ID.' ) ), 'required' => array( 'name' ) ) ),
            $this->build_function_tool( 'update_wc_category', 'Update a WooCommerce product category.', array( 'type' => 'object', 'properties' => array( 'category_id' => array( 'type' => 'integer' ), 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'parent' => array( 'type' => 'integer' ), 'image_id' => array( 'type' => 'integer' ) ), 'required' => array( 'category_id' ) ) ),
            $this->build_function_tool( 'delete_wc_category', 'Delete a WooCommerce product category. Cannot delete default category.', array( 'type' => 'object', 'properties' => array( 'category_id' => array( 'type' => 'integer' ), 'force' => array( 'type' => 'boolean', 'description' => 'Must be true to confirm deletion.' ) ), 'required' => array( 'category_id' ) ) ),
            $this->build_function_tool( 'list_wc_tags', 'List WooCommerce product tags.', array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string' ), 'per_page' => array( 'type' => 'integer' ), 'page' => array( 'type' => 'integer' ), 'orderby' => array( 'type' => 'string', 'enum' => array( 'name', 'id', 'count', 'slug' ) ), 'order' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ) ), 'hide_empty' => array( 'type' => 'boolean' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'create_wc_tag', 'Create a new WooCommerce product tag.', array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ) ), 'required' => array( 'name' ) ) ),
            $this->build_function_tool( 'update_wc_tag', 'Update a WooCommerce product tag.', array( 'type' => 'object', 'properties' => array( 'tag_id' => array( 'type' => 'integer' ), 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ) ), 'required' => array( 'tag_id' ) ) ),
            $this->build_function_tool( 'delete_wc_tag', 'Delete a WooCommerce product tag.', array( 'type' => 'object', 'properties' => array( 'tag_id' => array( 'type' => 'integer' ), 'force' => array( 'type' => 'boolean', 'description' => 'Must be true to confirm deletion.' ) ), 'required' => array( 'tag_id' ) ) ),
            $this->build_function_tool( 'list_wc_brands', 'List WooCommerce product brands (requires WooCommerce Brands plugin).', array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string' ), 'per_page' => array( 'type' => 'integer' ), 'page' => array( 'type' => 'integer' ), 'orderby' => array( 'type' => 'string', 'enum' => array( 'name', 'id', 'count', 'slug' ) ), 'order' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ) ), 'hide_empty' => array( 'type' => 'boolean' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'create_wc_brand', 'Create a new WooCommerce product brand.', array( 'type' => 'object', 'properties' => array( 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'image_id' => array( 'type' => 'integer' ) ), 'required' => array( 'name' ) ) ),
            $this->build_function_tool( 'update_wc_brand', 'Update a WooCommerce product brand.', array( 'type' => 'object', 'properties' => array( 'brand_id' => array( 'type' => 'integer' ), 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'image_id' => array( 'type' => 'integer' ) ), 'required' => array( 'brand_id' ) ) ),
            $this->build_function_tool( 'delete_wc_brand', 'Delete a WooCommerce product brand.', array( 'type' => 'object', 'properties' => array( 'brand_id' => array( 'type' => 'integer' ), 'force' => array( 'type' => 'boolean', 'description' => 'Must be true to confirm deletion.' ) ), 'required' => array( 'brand_id' ) ) ),

            /* ================================================================
             * WP Extras & Backup Tools
             * ============================================================= */
            $this->build_function_tool( 'list_custom_post_types', 'List all registered WordPress custom post types with metadata.', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),
            $this->build_function_tool( 'get_media_file', 'Get media file URL and metadata for a specific size (thumbnail/medium/large/full).', array( 'type' => 'object', 'properties' => array( 'media_id' => array( 'type' => 'integer', 'description' => 'Attachment ID.' ), 'size' => array( 'type' => 'string', 'enum' => array( 'thumbnail', 'medium', 'large', 'full' ), 'description' => 'Image size (default: full).' ) ), 'required' => array( 'media_id' ) ) ),
            $this->build_function_tool( 'elementor_assign_global_color', 'Assign a global color from the Elementor kit to a widget property (replaces direct color with kit reference).', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Elementor page/post ID.' ), 'widget_id' => array( 'type' => 'string', 'description' => 'Widget unique ID.' ), 'property' => array( 'type' => 'string', 'enum' => array( 'title_color', 'text_color', 'background_color', 'button_background_color', 'button_text_color', 'border_color', 'hover_color', 'icon_color', 'description_color', 'heading_color' ), 'description' => 'Color property to assign.' ), 'global_color_id' => array( 'type' => 'string', 'description' => 'Global color ID from active kit.' ) ), 'required' => array( 'post_id', 'widget_id', 'property', 'global_color_id' ) ) ),
            $this->build_function_tool( 'wp_search_posts', 'Backup tool — generic WordPress post search by title, type, status, category, tag with pagination. Use when specific tools are unavailable.', array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string', 'description' => 'Search term.' ), 'post_type' => array( 'type' => 'string', 'description' => 'Post type slug (default: post).' ), 'status' => array( 'type' => 'string', 'enum' => array( 'publish', 'draft', 'pending', 'private', 'trash', 'any' ) ), 'per_page' => array( 'type' => 'integer' ), 'page' => array( 'type' => 'integer' ), 'orderby' => array( 'type' => 'string', 'enum' => array( 'date', 'title', 'modified', 'id' ) ), 'order' => array( 'type' => 'string', 'enum' => array( 'asc', 'desc' ) ), 'category' => array( 'type' => 'integer', 'description' => 'Category term ID.' ), 'tag' => array( 'type' => 'string', 'description' => 'Tag slug.' ) ), 'required' => array() ) ),
            $this->build_function_tool( 'wp_get_post', 'Backup tool — get any WordPress post/page/CPT by ID with full content and custom fields. Use when specific tools are unavailable.', array( 'type' => 'object', 'properties' => array( 'post_id' => array( 'type' => 'integer', 'description' => 'Post ID.' ) ), 'required' => array( 'post_id' ) ) ),

            /* ================================================================
             * Rollback
             * ============================================================= */
            $this->build_function_tool( 'rollback_last_action', 'Undo/revert the last successfully executed action. Use ONLY when the user explicitly asks to undo, revert, or rollback a previous change. Do NOT use this when the user asks to remove, delete, or change something — those are new actions, not rollback.', array( 'type' => 'object', 'properties' => array(), 'required' => array() ) ),

            /* ================================================================
             * File Read + WP Option Get
             * ============================================================= */
            $this->build_function_tool( 'file_read', 'Read the contents of a file on the WordPress server. Uses file guard boundaries and secret redaction; WordPress root files, theme/plugin files, functions.php, and wp-content/debug.log are readable. Use tail_lines for large logs. Use this before main_coding_workspace_prepare/main_coding_file_edit when code context is needed.', array( 'type' => 'object', 'properties' => array( 'relative_path' => array( 'type' => 'string', 'description' => 'File path relative to WordPress root (e.g., wp-content/themes/mytheme/functions.php or wp-content/debug.log).' ), 'tail_lines' => array( 'type' => 'integer', 'description' => 'When greater than 0, return the last N lines by streaming the file. Max: 500.' ) ), 'required' => array( 'relative_path' ) ) ),
            $this->build_function_tool(
                'code_scan_run',
                'Run a read-only codebase scan command from the main planning runtime or a helper worker. Use argv, not a shell string. Intended for broad grep/rg/find/wc/stat/head/tail/cat/sed-without-in-place/git-inspection tasks. It cannot mutate files, run PHP eval/scripts, run package managers, update WordPress, remove files, or write output. Output is capped, so prefer rg --json/--count/--line-number and narrow globs.',
                array(
                    'type'       => 'object',
                    'properties' => array(
                        'argv' => array(
                            'type'        => 'array',
                            'description' => 'Read-only command as an argv array, for example ["rg","-n","#[0-9a-fA-F]{3,8}","wp-content/themes","wp-content/plugins","-g","*.css"] or ["find","wp-content/themes","-name","*.css","-maxdepth","4"]. For Niwa runtime files, use the current plugin path reported by runtime context instead of assuming a fixed plugin directory.',
                            'items'       => array( 'type' => 'string' ),
                        ),
                        'cwd' => array(
                            'type'        => 'string',
                            'description' => 'Optional working directory inside the WordPress install. Defaults to the Niwa plugin directory.',
                        ),
                        'timeout_seconds' => array(
                            'type'        => 'integer',
                            'description' => 'Timeout from 1 to 60 seconds. Default: 15.',
                        ),
                        'max_output_chars' => array(
                            'type'        => 'integer',
                            'description' => 'Combined stdout/stderr output cap from 1000 to 20000 characters. Default: 8000.',
                        ),
                    ),
                    'required'   => array( 'argv' ),
                )
            ),
            $this->build_function_tool( 'wp_option_get', 'Read any WordPress option from the database. Returns the option value (string, number, array, or object). Useful for reading plugin settings, theme mods, site configuration, etc.', array( 'type' => 'object', 'properties' => array( 'option_name' => array( 'type' => 'string', 'description' => 'WordPress option name (e.g., blogname, active_plugins, stylesheet, etc.).' ) ), 'required' => array( 'option_name' ) ) ),
        );
    }

    private function build_function_tool( $name, $description, $parameters ) {
        // Fix JSON Schema: 'properties' must be a JSON object {}, not array [].
        // PHP's json_encode(array()) produces [] but JSON Schema requires {}.
        if ( isset( $parameters['properties'] ) && is_array( $parameters['properties'] ) && empty( $parameters['properties'] ) ) {
            $parameters['properties'] = new \stdClass();
        }

        return array(
            'type'     => 'function',
            'function' => array(
                'name'        => $name,
                'description' => $description,
                'parameters'  => $parameters,
            ),
        );
    }
}
