<?php

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

trait Niwa_AI_Tools_Settings {

    /* ==================================================================
     *  GET WP SETTINGS — Read-only
     * ================================================================== */

    /**
     * Return all relevant WordPress settings as a flat array.
     */
    private function preview_get_wp_settings( $args ) {
        return array(
            'success' => true,
            'action'  => array(
                'type'             => 'get_wp_settings',
                'label'            => 'Prikazi WordPress podesavanja',
                'summary'          => 'Citanje svih relevantnih WordPress opcija (blogname, timezone, date_format, itd.).',
                'rollback_plan'    => 'Ovo je read-only operacija, nema potrebe za rollback.',
                'rollback_support' => 'none',
                'targets'          => array(),
                'args'             => array(),
            ),
        );
    }

    private function execute_get_wp_settings_action( $action ) {
        $settings = array(
            'title'                  => get_option( 'blogname' ),
            'description'            => get_option( 'blogdescription' ),
            'url'                    => get_option( 'siteurl' ),
            'home'                   => get_option( 'home' ),
            'admin_email'            => get_option( 'admin_email' ),
            'timezone_string'        => get_option( 'timezone_string' ),
            'gmt_offset'             => get_option( 'gmt_offset' ),
            'date_format'            => get_option( 'date_format' ),
            'time_format'            => get_option( 'time_format' ),
            'start_of_week'          => get_option( 'start_of_week' ),
            'language'               => get_option( 'WPLANG' ),
            'use_smilies'            => get_option( 'use_smilies' ),
            'default_category'       => get_option( 'default_category' ),
            'default_post_format'    => get_option( 'default_post_format' ),
            'posts_per_page'         => get_option( 'posts_per_page' ),
            'default_comment_status' => get_option( 'default_comment_status' ),
            'default_ping_status'    => get_option( 'default_ping_status' ),
            'permalink_structure'    => get_option( 'permalink_structure' ),
            'uploads_use_yearmonth'  => get_option( 'uploads_use_yearmonth_folders' ),
        );

        return array(
            'success'          => true,
            'summary'          => 'WordPress podesavanja ucitana.',
            'data'             => $settings,
            'executed_actions' => array(
                array(
                    'type' => 'get_wp_settings',
                ),
            ),
            'rollback_entries' => array(),
        );
    }

    /* ==================================================================
     *  UPDATE WP SETTINGS — Write with rollback
     * ================================================================== */

    /**
     * Whitelist of safe setting keys mapped to their WordPress option names.
     *
     * SAFETY: siteurl, home and permalink_structure are intentionally excluded
     * because changing them can render the site unreachable.
     */
    private function get_settings_option_map() {
        return array(
            'title'                  => 'blogname',
            'description'            => 'blogdescription',
            'admin_email'            => 'admin_email',
            'timezone_string'        => 'timezone_string',
            'date_format'            => 'date_format',
            'time_format'            => 'time_format',
            'start_of_week'          => 'start_of_week',
            'language'               => 'WPLANG',
            'posts_per_page'         => 'posts_per_page',
            'default_comment_status' => 'default_comment_status',
            'default_ping_status'    => 'default_ping_status',
            'use_smilies'            => 'use_smilies',
        );
    }

    /**
     * Validate update_wp_settings arguments.
     */
    private function validate_update_wp_settings_action_args( $args ) {
        $option_map = $this->get_settings_option_map();
        $has_change = false;

        // When called from MCP bridge after preview, args may be nested under 'changes'.
        // Preview packs original flat args into $action['args']['changes'].
        // Validate needs to handle both flat args (direct call) and nested (post-preview).
        if ( isset( $args['changes'] ) && is_array( $args['changes'] ) && ! array_intersect_key( $args, $option_map ) ) {
            $args = $args['changes'];
        }

        foreach ( $option_map as $key => $option_name ) {
            if ( array_key_exists( $key, $args ) ) {
                $has_change = true;
                break;
            }
        }

        if ( ! $has_change ) {
            return array(
                'success' => false,
                'error'   => 'Nijedna promena podesavanja nije zadata. Prosledite bar jedno od: ' . implode( ', ', array_keys( $option_map ) ) . '.',
            );
        }

        /* admin_email format. */
        if ( isset( $args['admin_email'] ) && ! is_email( (string) $args['admin_email'] ) ) {
            return array(
                'success' => false,
                'error'   => 'Admin email adresa nije validnog formata.',
            );
        }

        /* start_of_week range 0-6. */
        if ( isset( $args['start_of_week'] ) ) {
            $sow = intval( $args['start_of_week'] );
            if ( $sow < 0 || $sow > 6 ) {
                return array(
                    'success' => false,
                    'error'   => 'start_of_week mora biti izmedju 0 (nedelja) i 6 (subota).',
                );
            }
        }

        /* posts_per_page range 1-100. */
        if ( isset( $args['posts_per_page'] ) ) {
            $ppp = intval( $args['posts_per_page'] );
            if ( $ppp < 1 || $ppp > 100 ) {
                return array(
                    'success' => false,
                    'error'   => 'posts_per_page mora biti izmedju 1 i 100.',
                );
            }
        }

        /* default_comment_status enum. */
        if ( isset( $args['default_comment_status'] ) && ! in_array( (string) $args['default_comment_status'], array( 'open', 'closed' ), true ) ) {
            return array(
                'success' => false,
                'error'   => 'default_comment_status mora biti "open" ili "closed".',
            );
        }

        /* default_ping_status enum. */
        if ( isset( $args['default_ping_status'] ) && ! in_array( (string) $args['default_ping_status'], array( 'open', 'closed' ), true ) ) {
            return array(
                'success' => false,
                'error'   => 'default_ping_status mora biti "open" ili "closed".',
            );
        }

        return array( 'success' => true );
    }

    /**
     * Preview update_wp_settings — build action with rollback data.
     */
    private function preview_update_wp_settings( $args ) {
        $option_map = $this->get_settings_option_map();

        /* Validate first. */
        $validation = $this->validate_update_wp_settings_action_args( $args );
        if ( empty( $validation['success'] ) ) {
            return $validation;
        }

        /* Collect only keys that are being changed. */
        $changes          = array();
        $previous_settings = array();
        $summary_parts    = array();

        foreach ( $option_map as $key => $option_name ) {
            if ( ! array_key_exists( $key, $args ) ) {
                continue;
            }

            $current_value = get_option( $option_name );
            $new_value     = $args[ $key ];

            /* Type coercion for comparison. */
            if ( 'start_of_week' === $key || 'posts_per_page' === $key ) {
                $new_value = intval( $new_value );
            } elseif ( 'use_smilies' === $key ) {
                $new_value = $new_value ? 1 : 0;
            } else {
                $new_value = (string) $new_value;
            }

            $previous_settings[ $option_name ] = $current_value;
            $changes[ $key ]                   = $new_value;
            $summary_parts[]                   = $key . ': "' . (string) $current_value . '" → "' . (string) $new_value . '"';
        }

        if ( empty( $changes ) ) {
            return array(
                'success' => false,
                'error'   => 'Nema validnih promena za azuriranje.',
            );
        }

        $targets = array();
        foreach ( $changes as $key => $val ) {
            $targets[] = array(
                'setting' => $key,
                'option'  => isset( $option_map[ $key ] ) ? $option_map[ $key ] : $key,
                'new'     => $val,
                'old'     => isset( $previous_settings[ $option_map[ $key ] ] ) ? $previous_settings[ $option_map[ $key ] ] : '',
            );
        }

        return array(
            'success' => true,
            'action'  => array(
                'type'             => 'update_wp_settings',
                'label'            => 'Azuriraj WordPress podesavanja',
                'summary'          => 'Azuriranje ' . count( $changes ) . ' WordPress opcija: ' . implode( ', ', $summary_parts ) . '.',
                'rollback_plan'    => 'Vraca sve izmenjene opcije na prethodne vrednosti.',
                'rollback_support' => 'full',
                'targets'          => $targets,
                'args'             => array(
                    'changes'           => $changes,
                    'previous_settings' => $previous_settings,
                ),
            ),
        );
    }

    /**
     * Execute update_wp_settings — apply changes, return rollback entries.
     */
    private function execute_update_wp_settings_action( $action ) {
        $args              = isset( $action['args'] ) && is_array( $action['args'] ) ? $action['args'] : array();
        $changes           = isset( $args['changes'] ) && is_array( $args['changes'] ) ? $args['changes'] : array();
        $previous_settings = isset( $args['previous_settings'] ) && is_array( $args['previous_settings'] ) ? $args['previous_settings'] : array();
        $option_map        = $this->get_settings_option_map();

        if ( empty( $changes ) ) {
            return array(
                'success' => false,
                'error'   => 'Nema promena za primenu.',
            );
        }

        $updated   = array();
        $failures  = array();
        $unchanged = array();

        foreach ( $changes as $key => $new_value ) {
            if ( ! isset( $option_map[ $key ] ) ) {
                $failures[] = $key . ': nepoznat setting';
                continue;
            }

            $option_name   = $option_map[ $key ];
            $current_value = get_option( $option_name );

            /* Capture fresh previous value in case something changed between preview and execute. */
            if ( ! array_key_exists( $option_name, $previous_settings ) ) {
                $previous_settings[ $option_name ] = $current_value;
            }

            if ( $current_value == $new_value ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
                $unchanged[] = $key;
                continue;
            }

            $result = update_option( $option_name, $new_value );

            /*
             * update_option() returns false when the value is unchanged OR on failure.
             * Verify by reading back the option.
             */
            $readback = get_option( $option_name );

            if ( false === $result && $readback != $new_value ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
                $failures[] = $key . ' (' . $option_name . '): update_option neuspesno';
                continue;
            }

            $updated[] = array(
                'setting'  => $key,
                'option'   => $option_name,
                'old'      => isset( $previous_settings[ $option_name ] ) ? $previous_settings[ $option_name ] : '',
                'new'      => $new_value,
            );
        }

        if ( empty( $updated ) ) {
            if ( ! empty( $unchanged ) && empty( $failures ) ) {
                return array(
                    'success'            => false,
                    'error'              => 'WordPress podesavanja su vec u trazenom stanju; nema promene za snimanje.',
                    'unchanged_settings' => $unchanged,
                );
            }

            return array(
                'success' => false,
                'error'   => 'Nijedna opcija nije azurirana. ' . implode( '; ', $failures ),
            );
        }

        $summary = 'Azurirano ' . count( $updated ) . ' WordPress opcija.';
        if ( ! empty( $unchanged ) ) {
            $summary .= ' Bez promene: ' . implode( ', ', $unchanged ) . '.';
        }
        if ( ! empty( $failures ) ) {
            $summary .= ' Neuspeli: ' . implode( '; ', $failures ) . '.';
        }

        return array(
            'success'          => true,
            'summary'          => $summary,
            'executed_actions' => array(
                array(
                    'type'      => 'update_wp_settings',
                    'updated'   => $updated,
                    'unchanged' => $unchanged,
                    'count'     => count( $updated ),
                ),
            ),
            'rollback_entries' => array(
                array(
                    'type'              => 'settings_restore',
                    'previous_settings' => $previous_settings,
                ),
            ),
        );
    }

    /* ==================================================================
     *  GET SITE INFO — Read-only
     * ================================================================== */

    /**
     * Preview get_site_info — read-only, no approval needed.
     */
    private function preview_get_site_info( $args ) {
        return array(
            'success' => true,
            'action'  => array(
                'type'             => 'get_site_info',
                'label'            => 'Prikazi informacije o sajtu',
                'summary'          => 'Citanje kompletnih informacija o sajtu (verzija, pluginovi, tema, korisnici, statistika sadrzaja).',
                'rollback_plan'    => 'Ovo je read-only operacija, nema potrebe za rollback.',
                'rollback_support' => 'none',
                'targets'          => array(),
                'args'             => array(),
            ),
        );
    }

    /**
     * Execute get_site_info — gather and return comprehensive site info.
     */
    private function execute_get_site_info_action( $action ) {
        /* ── Plugins ───────────────────────────────────────── */
        if ( ! function_exists( 'get_plugins' ) ) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }

        $all_plugins    = get_plugins();
        $active_slugs   = (array) get_option( 'active_plugins', array() );
        $active_plugins  = array();
        $inactive_plugins = array();

        foreach ( $all_plugins as $plugin_file => $plugin_data ) {
            $entry = array(
                'name'    => isset( $plugin_data['Name'] ) ? (string) $plugin_data['Name'] : $plugin_file,
                'version' => isset( $plugin_data['Version'] ) ? (string) $plugin_data['Version'] : '',
            );

            if ( in_array( $plugin_file, $active_slugs, true ) ) {
                $active_plugins[] = $entry;
            } else {
                $inactive_plugins[] = $entry;
            }
        }

        /* ── Theme ─────────────────────────────────────────── */
        $current_theme = wp_get_theme();
        $theme_info    = array(
            'name'    => $current_theme->get( 'Name' ),
            'version' => $current_theme->get( 'Version' ),
            'author'  => $current_theme->get( 'Author' ),
            'parent'  => '',
        );

        if ( $current_theme->parent() ) {
            $theme_info['parent'] = $current_theme->parent()->get( 'Name' );
        }

        /* ── Users ─────────────────────────────────────────── */
        $user_count_obj = count_users();
        $total_users    = isset( $user_count_obj['total_users'] ) ? intval( $user_count_obj['total_users'] ) : 0;
        $by_role        = array();

        if ( isset( $user_count_obj['avail_roles'] ) && is_array( $user_count_obj['avail_roles'] ) ) {
            foreach ( $user_count_obj['avail_roles'] as $role => $count ) {
                if ( 'none' === $role ) {
                    continue;
                }
                $by_role[ $role ] = intval( $count );
            }
        }

        /* ── Content stats ─────────────────────────────────── */
        $post_counts       = wp_count_posts( 'post' );
        $page_counts       = wp_count_posts( 'page' );
        $attachment_counts  = wp_count_attachments();

        /* Flatten wp_count_attachments to a total. */
        $media_total = 0;
        if ( is_object( $attachment_counts ) ) {
            foreach ( $attachment_counts as $mime => $count ) {
                $media_total += intval( $count );
            }
        }

        /* ── Compose response ──────────────────────────────── */
        $site_info = array(
            'site_name'         => get_bloginfo( 'name' ),
            'site_url'          => get_site_url(),
            'site_description'  => get_bloginfo( 'description' ),
            'admin_email'       => get_bloginfo( 'admin_email' ),
            'wordpress_version' => get_bloginfo( 'version' ),
            'php_version'       => phpversion(),
            'language'          => get_bloginfo( 'language' ),
            'timezone'          => wp_timezone_string(),
            'is_multisite'      => is_multisite(),
            'front_page'        => $this->build_front_page_discovery_info(),
            'plugins'           => array(
                'active'   => $active_plugins,
                'inactive' => $inactive_plugins,
                'total'    => count( $all_plugins ),
            ),
            'theme'             => $theme_info,
            'users'             => array(
                'total'   => $total_users,
                'by_role' => $by_role,
            ),
            'content_stats'     => array(
                'posts' => $post_counts,
                'pages' => $page_counts,
                'media' => array(
                    'total'    => $media_total,
                    'by_type'  => $attachment_counts,
                ),
            ),
        );

        return array(
            'success'          => true,
            'summary'          => 'Informacije o sajtu ucitane (WP ' . get_bloginfo( 'version' ) . ', PHP ' . phpversion() . ', ' . count( $active_plugins ) . ' aktivnih pluginova).',
            'data'             => $site_info,
            'executed_actions' => array(
                array(
                    'type' => 'get_site_info',
                ),
            ),
            'rollback_entries' => array(),
        );
    }

    /**
     * Build a compact front-page discovery payload for tool consumers.
     *
     * The rendered HTML path covers normal WordPress/page-builder sites. The
     * theme source path covers React-style themes where the server response only
     * contains a root mount node and the visible copy is defined in theme code.
     *
     * @return array
     */
    private function build_front_page_discovery_info() {
        $page_id = intval( get_option( 'page_on_front', 0 ) );
        $page    = $page_id > 0 ? get_post( $page_id ) : null;

        $info = array(
            'home_url'                    => home_url( '/' ),
            'show_on_front'               => (string) get_option( 'show_on_front', 'posts' ),
            'page_on_front_id'            => $page_id,
            'page_on_front_title'         => $page instanceof WP_Post ? get_the_title( $page ) : '',
            'page_on_front_template'      => $page instanceof WP_Post ? (string) get_page_template_slug( $page->ID ) : '',
            'primary_heading_candidate'   => '',
            'primary_heading_source'      => '',
            'rendered_heading_candidates' => array(),
            'theme_heading_candidates'    => array(),
            'rendered_html_fetch'         => array(
                'success'     => false,
                'http_status' => 0,
                'error'       => '',
            ),
        );

        $rendered = $this->fetch_front_page_html_for_discovery( $info['home_url'] );
        $info['rendered_html_fetch'] = array(
            'success'     => ! empty( $rendered['success'] ),
            'http_status' => isset( $rendered['http_status'] ) ? intval( $rendered['http_status'] ) : 0,
            'error'       => isset( $rendered['error'] ) ? (string) $rendered['error'] : '',
        );

        if ( ! empty( $rendered['body'] ) ) {
            $info['rendered_heading_candidates'] = $this->extract_heading_candidates_from_text(
                (string) $rendered['body'],
                'rendered_homepage_html',
                12
            );
        }

        $info['theme_heading_candidates'] = $this->discover_theme_heading_candidates( 12 );

        $primary = $this->select_primary_heading_candidate(
            $info['rendered_heading_candidates'],
            $info['theme_heading_candidates']
        );

        if ( ! empty( $primary['text'] ) ) {
            $info['primary_heading_candidate'] = (string) $primary['text'];
            $info['primary_heading_source']    = isset( $primary['source'] ) ? (string) $primary['source'] : '';
        }

        return $info;
    }

    /**
     * Fetch front-page HTML through WordPress HTTP APIs.
     *
     * @param string $url URL to fetch.
     * @return array
     */
    private function fetch_front_page_html_for_discovery( $url ) {
        if ( '' === trim( (string) $url ) || ! function_exists( 'wp_remote_get' ) ) {
            return array(
                'success'     => false,
                'http_status' => 0,
                'error'       => 'wp_remote_get_unavailable',
                'body'        => '',
            );
        }

        $response = wp_remote_get(
            esc_url_raw( $url ),
            array(
                'timeout'     => 15,
                'redirection' => 3,
                'headers'     => array(
                    'Accept'        => 'text/html,application/xhtml+xml',
                    'Cache-Control' => 'no-cache',
                    'User-Agent'    => 'NiwaAIHomepageDiscovery/' . ( defined( 'NIWA_AI_VERSION' ) ? NIWA_AI_VERSION : 'dev' ),
                ),
            )
        );

        if ( is_wp_error( $response ) ) {
            return array(
                'success'     => false,
                'http_status' => 0,
                'error'       => $response->get_error_message(),
                'body'        => '',
            );
        }

        $status = intval( wp_remote_retrieve_response_code( $response ) );
        $body   = (string) wp_remote_retrieve_body( $response );

        return array(
            'success'     => $status >= 200 && $status < 300,
            'http_status' => $status,
            'error'       => $status >= 200 && $status < 300 ? '' : 'http_' . $status,
            'body'        => $body,
        );
    }

    /**
     * Discover heading candidates from the active theme files.
     *
     * @param int $limit Maximum number of candidates.
     * @return array
     */
    private function discover_theme_heading_candidates( $limit = 12 ) {
        $limit      = max( 1, min( 30, intval( $limit ) ) );
        $candidates = array();
        $seen_files = array();
        $roots      = array_unique( array_filter( array( get_stylesheet_directory(), get_template_directory() ) ) );

        foreach ( $roots as $root ) {
            foreach ( $this->front_page_discovery_theme_files( $root ) as $file ) {
                $real = realpath( $file );
                if ( ! $real || isset( $seen_files[ $real ] ) || ! is_readable( $real ) || filesize( $real ) > 524288 ) {
                    continue;
                }

                $seen_files[ $real ] = true;
                $contents            = (string) file_get_contents( $real );
                $source              = $this->relative_theme_discovery_source( $real, $root );
                $found               = $this->extract_heading_candidates_from_text( $contents, $source, $limit );

                foreach ( $found as $candidate ) {
                    $candidates[] = $candidate;
                    if ( count( $candidates ) >= $limit ) {
                        return $candidates;
                    }
                }
            }
        }

        return $candidates;
    }

    /**
     * Return likely front-page source files for a theme root.
     *
     * @param string $root Theme root.
     * @return array
     */
    private function front_page_discovery_theme_files( $root ) {
        $root  = rtrim( (string) $root, '/\\' );
        $files = array();

        foreach ( array(
            'front-page.php',
            'home.php',
            'index.php',
            'page.php',
            'templates/react-landing.php',
            'src/App.tsx',
            'src/App.jsx',
            'src/App.js',
            'src/main.tsx',
            'src/main.jsx',
            'src/main.js',
        ) as $relative ) {
            $path = $root . DIRECTORY_SEPARATOR . str_replace( '/', DIRECTORY_SEPARATOR, $relative );
            if ( is_file( $path ) ) {
                $files[] = $path;
            }
        }

        $src_root = $root . DIRECTORY_SEPARATOR . 'src';
        if ( is_dir( $src_root ) ) {
            $iterator = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator( $src_root, FilesystemIterator::SKIP_DOTS )
            );

            foreach ( $iterator as $file_info ) {
                if ( count( $files ) >= 80 ) {
                    break;
                }

                if ( ! $file_info->isFile() ) {
                    continue;
                }

                $extension = strtolower( $file_info->getExtension() );
                if ( ! in_array( $extension, array( 'php', 'js', 'jsx', 'ts', 'tsx' ), true ) ) {
                    continue;
                }

                $files[] = $file_info->getPathname();
            }
        }

        return array_values( array_unique( $files ) );
    }

    /**
     * Extract heading-like structural candidates from HTML/JSX/PHP source.
     *
     * @param string $text Source text.
     * @param string $source Source label.
     * @param int    $limit Maximum number of candidates.
     * @return array
     */
    private function extract_heading_candidates_from_text( $text, $source, $limit = 12 ) {
        $limit = max( 1, min( 30, intval( $limit ) ) );
        $out   = array();

        if ( ! preg_match_all( '/<h([1-3])\b[^>]*>(.*?)<\/h\1>/is', (string) $text, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE ) ) {
            return $out;
        }

        foreach ( $matches as $match ) {
            $level  = intval( $match[1][0] );
            $inner  = isset( $match[2][0] ) ? (string) $match[2][0] : '';
            $offset = isset( $match[0][1] ) ? intval( $match[0][1] ) : 0;
            $plain  = $this->normalize_discovered_heading_text( $inner );

            if ( '' === $plain ) {
                continue;
            }

            $out[] = array(
                'level'  => $level,
                'text'   => $plain,
                'source' => (string) $source,
                'line'   => substr_count( substr( (string) $text, 0, $offset ), "\n" ) + 1,
            );

            if ( count( $out ) >= $limit ) {
                break;
            }
        }

        return $out;
    }

    /**
     * Normalize discovered heading text.
     *
     * @param string $text Raw heading inner text.
     * @return string
     */
    private function normalize_discovered_heading_text( $text ) {
        $text = (string) $text;
        $text = preg_replace( '/<br\s*\/?>/i', ' ', $text );
        $text = preg_replace( '/\{\s*\/\*.*?\*\/\s*\}/s', ' ', $text );
        $text = preg_replace( '/\{[^{}]{0,240}\}/s', ' ', $text );
        $text = html_entity_decode( wp_strip_all_tags( $text, true ), ENT_QUOTES | ENT_HTML5, 'UTF-8' );
        $text = preg_replace( '/\s+/u', ' ', $text );
        $text = trim( (string) $text );

        if ( '' === $text || false !== strpos( $text, '<?' ) ) {
            return '';
        }

        if ( function_exists( 'mb_strlen' ) && mb_strlen( $text ) > 220 ) {
            return mb_substr( $text, 0, 217 ) . '...';
        }

        if ( ! function_exists( 'mb_strlen' ) && strlen( $text ) > 220 ) {
            return substr( $text, 0, 217 ) . '...';
        }

        return $text;
    }

    /**
     * Pick the most useful candidate for quick answers.
     *
     * @param array $rendered Rendered HTML candidates.
     * @param array $theme_source Theme source candidates.
     * @return array
     */
    private function select_primary_heading_candidate( $rendered, $theme_source ) {
        foreach ( array( $rendered, $theme_source, $rendered, $theme_source ) as $index => $group ) {
            foreach ( (array) $group as $candidate ) {
                $level = isset( $candidate['level'] ) ? intval( $candidate['level'] ) : 0;
                if ( $index < 2 && 1 !== $level ) {
                    continue;
                }

                if ( ! empty( $candidate['text'] ) ) {
                    return $candidate;
                }
            }
        }

        return array();
    }

    /**
     * Convert an absolute theme file path into a compact source label.
     *
     * @param string $file Absolute file path.
     * @param string $root Theme root.
     * @return string
     */
    private function relative_theme_discovery_source( $file, $root ) {
        $file = str_replace( '\\', '/', (string) $file );
        $root = rtrim( str_replace( '\\', '/', (string) $root ), '/' );

        if ( '' !== $root && 0 === strpos( $file, $root . '/' ) ) {
            return 'theme:' . substr( $file, strlen( $root ) + 1 );
        }

        return 'theme:' . basename( $file );
    }

    public function execute_manage_whatsapp_access_numbers( $args ) {
        if ( ! class_exists( 'Niwa_AI_Admin_Autonomy_WhatsApp' ) ) {
            return array(
                'success' => false,
                'ok'      => false,
                'error'   => 'WhatsApp runtime nije ucitan.',
            );
        }

        $args   = is_array( $args ) ? $args : array();
        $wa     = new Niwa_AI_Admin_Autonomy_WhatsApp();
        $action = isset( $args['action'] ) ? (string) $args['action'] : 'list';
        $role   = isset( $args['role'] ) ? (string) $args['role'] : 'admin';
        $phone  = isset( $args['phone'] ) ? (string) $args['phone'] : '';
        $label  = isset( $args['label'] ) ? (string) $args['label'] : '';

        if ( ! method_exists( $wa, 'manage_access_numbers' ) ) {
            return array(
                'success' => false,
                'ok'      => false,
                'error'   => 'WhatsApp access manager nije dostupan.',
            );
        }

        return $wa->manage_access_numbers( $action, $role, $phone, $label );
    }

    public function validate_send_whatsapp_lead_outreach_action_args( $args ) {
        $args    = is_array( $args ) ? $args : array();
        $message = isset( $args['message'] ) ? trim( (string) $args['message'] ) : '';
        if ( '' === $message ) {
            return array(
                'success' => false,
                'error'   => 'WhatsApp lead outreach message is required.',
            );
        }

        $has_target = ! empty( $args['contact_id'] ) || ! empty( $args['phone'] ) || ! empty( $args['query'] );
        if ( ! $has_target ) {
            return array(
                'success' => false,
                'error'   => 'Provide contact_id, phone, or query for WhatsApp lead outreach.',
            );
        }

        return array( 'success' => true );
    }

    public function preview_send_whatsapp_lead_outreach( $args ) {
        $args       = is_array( $args ) ? $args : array();
        $validation = $this->validate_send_whatsapp_lead_outreach_action_args( $args );
        if ( empty( $validation['success'] ) ) {
            return $validation;
        }

        $target = ! empty( $args['contact_id'] )
            ? 'contact #' . (int) $args['contact_id']
            : ( ! empty( $args['phone'] ) ? 'provided phone' : 'contact query' );

        return array(
            'success' => true,
            'action'  => array(
                'type'             => 'send_whatsapp_lead_outreach',
                'label'            => 'Send WhatsApp lead outreach',
                'summary'          => 'Queue a WhatsApp lead/customer outreach message to ' . $target . '.',
                'rollback_plan'    => 'WhatsApp messages cannot be recalled after the gateway sends them; no rollback is available.',
                'rollback_support' => 'none',
                'targets'          => array(
                    array(
                        'channel' => 'whatsapp',
                        'target'  => $target,
                    ),
                ),
                'args'             => $args,
            ),
        );
    }

    public function execute_send_whatsapp_lead_outreach_action( $action ) {
        $args = isset( $action['args'] ) && is_array( $action['args'] ) ? $action['args'] : array();
        if ( ! class_exists( 'Niwa_AI_Lead_WhatsApp_Outreach_Service' ) ) {
            return array(
                'success' => false,
                'error'   => 'whatsapp_lead_outreach_service_unavailable',
            );
        }

        $service = new Niwa_AI_Lead_WhatsApp_Outreach_Service();
        $result  = $service->send_outreach( $args );
        if ( empty( $result['success'] ) ) {
            return $result;
        }

        return array(
            'success'          => true,
            'summary'          => ! empty( $result['dry_run'] ) ? 'WhatsApp lead outreach dry run passed.' : 'WhatsApp lead outreach queued.',
            'data'             => $result,
            'executed_actions' => array(
                array(
                    'type'       => 'send_whatsapp_lead_outreach',
                    'contact_id' => isset( $result['contact_id'] ) ? (int) $result['contact_id'] : 0,
                    'phone'      => isset( $result['phone'] ) ? (string) $result['phone'] : '',
                    'queued'     => ! empty( $result['queued'] ),
                ),
            ),
            'rollback_entries' => array(),
        );
    }

    /* ==================================================================
     *  UPDATE OPERATIVE HEARTBEAT CONFIG
     * ================================================================== */

    /**
     * Execute update_operative_heartbeat_config action.
     *
     * @param array $action Action data with args.
     * @return array Result.
     */
    public function execute_update_operative_heartbeat_config( $action ) {
        $args = isset( $action['args'] ) && is_array( $action['args'] ) ? $action['args'] : array();
        $target_role = isset( $args['target_role'] ) && 'stock_manager' === sanitize_key( (string) $args['target_role'] ) ? 'stock_manager' : 'admin';
        $config_option = 'stock_manager' === $target_role ? Niwa_AI_Constants::OPT_AUTONOMY_STOCK_OPERATIVE_HB_CONFIG : Niwa_AI_Constants::OPT_AUTONOMY_OPERATIVE_HB_CONFIG;
        $task_brief = '';
        foreach ( array( 'task_brief', 'brief', 'instructions' ) as $brief_key ) {
            if ( '' === $task_brief && isset( $args[ $brief_key ] ) ) {
                $task_brief = trim( (string) $args[ $brief_key ] );
            }
        }
        $stop_condition = isset( $args['stop_condition'] ) ? trim( (string) $args['stop_condition'] ) : '';

        // Get chat agent to access heartbeat config methods.
        $chat_agent = null;
        if ( function_exists( 'niwa_ai' ) && method_exists( niwa_ai(), 'get_admin_autonomy' ) ) {
            $autonomy = niwa_ai()->get_admin_autonomy();
            if ( $autonomy && method_exists( $autonomy, 'get_chat_agent' ) ) {
                $chat_agent = $autonomy->get_chat_agent();
            }
        }

        if ( ! $chat_agent || ! method_exists( $chat_agent, 'get_operative_heartbeat_config' ) ) {
            if ( '' !== $task_brief ) {
                return array(
                    'success' => false,
                    'error'   => 'Chat agent runtime nije ucitan, pa task_brief ne moze biti upisan.',
                );
            }
            // Fallback: update option directly.
            $config = get_option( $config_option, array() );
            if ( ! is_array( $config ) ) {
                $config = array();
            }
            $config = wp_parse_args( $config, array( 'enabled' => false, 'interval_minutes' => 60.0 ) );

            $changed = array();
            $explicit_schedule_args = ! empty( $args['clear_scheduled_hours'] ) || isset( $args['scheduled_hours'] ) || isset( $args['scheduled_minute'] );
            if ( isset( $args['interval_minutes'] ) ) {
                $config['interval_minutes'] = max( 0.5, round( floatval( $args['interval_minutes'] ), 2 ) );
                $changed[] = 'interval=' . rtrim( rtrim( number_format( $config['interval_minutes'], 2, '.', '' ), '0' ), '.' ) . 'min';
            }
            if ( isset( $args['enabled'] ) ) {
                $config['enabled'] = ! empty( $args['enabled'] );
                $changed[] = 'enabled=' . ( $config['enabled'] ? 'true' : 'false' );
            }
            if ( isset( $args['execution_mode'] ) ) {
                $execution_mode = sanitize_key( (string) $args['execution_mode'] );
                $config['execution_mode'] = 'static_exact_message' === $execution_mode ? 'static_exact_message' : 'dynamic_agent';
                $changed[] = 'execution_mode=' . $config['execution_mode'];
            }
            if ( isset( $args['static_message'] ) ) {
                $config['static_message'] = sanitize_textarea_field( (string) $args['static_message'] );
                $changed[] = 'static_message=' . ( '' === $config['static_message'] ? 'cleared' : 'set' );
                if ( ! isset( $args['execution_mode'] ) && '' !== $config['static_message'] ) {
                    $config['execution_mode'] = 'static_exact_message';
                    $changed[] = 'execution_mode=static_exact_message';
                }
            }
            if ( 'static_exact_message' === ( isset( $config['execution_mode'] ) ? (string) $config['execution_mode'] : '' ) && ! $explicit_schedule_args && ! empty( $config['scheduled_hours'] ) ) {
                $config['scheduled_hours'] = array();
                $config['scheduled_minute'] = 0;
                $changed[] = 'scheduled_hours=auto_cleared_for_interval_static_message';
            }
            if ( ! empty( $args['clear_end_at'] ) ) {
                $config['end_at_utc'] = '';
                $changed[] = 'end_at=cleared';
            }
            if ( ! empty( $config['enabled'] ) && 'static_exact_message' === ( isset( $config['execution_mode'] ) ? (string) $config['execution_mode'] : '' ) && '' === trim( (string) ( isset( $config['static_message'] ) ? $config['static_message'] : '' ) ) ) {
                return array(
                    'success' => false,
                    'error'   => 'Static operative heartbeat zahteva static_message.',
                );
            }

            update_option( $config_option, $config, false );

            return array(
                'success' => true,
                'summary' => empty( $changed ) ? 'No changes.' : 'Operative heartbeat updated: ' . implode( ', ', $changed ),
                'config'  => $config,
            );
        }

        $config  = $chat_agent->get_operative_heartbeat_config( $target_role );
        $changed = array();
        $end_at_utc = method_exists( $chat_agent, 'normalize_operative_heartbeat_end_at_from_args' )
            ? $chat_agent->normalize_operative_heartbeat_end_at_from_args( $args )
            : '';
        $explicit_schedule_args = ! empty( $args['clear_scheduled_hours'] ) || isset( $args['scheduled_hours'] ) || isset( $args['scheduled_minute'] );

        if ( isset( $args['interval_minutes'] ) ) {
            $config['interval_minutes'] = max( 0.5, round( floatval( $args['interval_minutes'] ), 2 ) );
            $changed[] = 'interval=' . rtrim( rtrim( number_format( $config['interval_minutes'], 2, '.', '' ), '0' ), '.' ) . 'min';
        }
        if ( ! empty( $args['clear_scheduled_hours'] ) ) {
            $config['scheduled_hours'] = array();
            $config['scheduled_minute'] = 0;
            $changed[] = 'scheduled_hours=cleared';
        } elseif ( isset( $args['scheduled_hours'] ) ) {
            $hours = is_array( $args['scheduled_hours'] ) ? $args['scheduled_hours'] : array( $args['scheduled_hours'] );
            $config['scheduled_hours'] = array_values( array_unique( array_filter( array_map( 'intval', $hours ), function ( $hour ) {
                return $hour >= 0 && $hour <= 23;
            } ) ) );
            sort( $config['scheduled_hours'], SORT_NUMERIC );
            if ( isset( $args['scheduled_minute'] ) ) {
                $config['scheduled_minute'] = max( 0, min( 59, intval( $args['scheduled_minute'] ) ) );
            }
            $changed[] = 'scheduled_hours=' . implode( ',', $config['scheduled_hours'] );
        } elseif ( isset( $args['scheduled_minute'] ) ) {
            $config['scheduled_minute'] = max( 0, min( 59, intval( $args['scheduled_minute'] ) ) );
            $changed[] = 'scheduled_minute=' . intval( $config['scheduled_minute'] );
        }
        if ( ! empty( $args['clear_end_at'] ) ) {
            $config['end_at_utc'] = '';
            $changed[] = 'end_at=cleared';
        } elseif ( '' !== $end_at_utc ) {
            if ( strtotime( $end_at_utc . ' UTC' ) <= time() ) {
                return array(
                    'success' => false,
                    'error'   => 'end_at mora biti u buducnosti za aktivan operative heartbeat.',
                );
            }
            $config['end_at_utc'] = $end_at_utc;
            $changed[] = 'end_at_utc=' . $end_at_utc;
        }
        if ( isset( $args['enabled'] ) ) {
            $config['enabled'] = ! empty( $args['enabled'] );
            $changed[] = 'enabled=' . ( $config['enabled'] ? 'true' : 'false' );
        }
        if ( isset( $args['execution_mode'] ) ) {
            $execution_mode = sanitize_key( (string) $args['execution_mode'] );
            $config['execution_mode'] = 'static_exact_message' === $execution_mode ? 'static_exact_message' : 'dynamic_agent';
            $changed[] = 'execution_mode=' . $config['execution_mode'];
        }
        if ( isset( $args['static_message'] ) ) {
            $config['static_message'] = sanitize_textarea_field( (string) $args['static_message'] );
            $changed[] = 'static_message=' . ( '' === $config['static_message'] ? 'cleared' : 'set' );
            if ( ! isset( $args['execution_mode'] ) && '' !== $config['static_message'] ) {
                $config['execution_mode'] = 'static_exact_message';
                $changed[] = 'execution_mode=static_exact_message';
            }
        }
        if ( 'static_exact_message' === ( isset( $config['execution_mode'] ) ? (string) $config['execution_mode'] : '' ) && ! $explicit_schedule_args && ! empty( $config['scheduled_hours'] ) ) {
            $config['scheduled_hours'] = array();
            $config['scheduled_minute'] = 0;
            $changed[] = 'scheduled_hours=auto_cleared_for_interval_static_message';
        }

        $brief_written = false;
        if ( '' !== $task_brief ) {
            if ( ! method_exists( $chat_agent, 'write_operative_heartbeat_task_brief' ) ) {
                return array(
                    'success' => false,
                    'error'   => 'Operative heartbeat brief writer nije dostupan.',
                );
            }

            $brief_result = $chat_agent->write_operative_heartbeat_task_brief(
                $task_brief,
                $target_role,
                $config,
                array(
                    'stop_condition' => $stop_condition,
                    'source_channel'  => 'admin_tool',
                )
            );
            if ( empty( $brief_result['success'] ) ) {
                return $brief_result;
            }
            $brief_written = true;
            $changed[]     = 'brief=written';
        }

        if ( ! empty( $config['enabled'] ) && 'static_exact_message' === ( isset( $config['execution_mode'] ) ? (string) $config['execution_mode'] : '' ) && '' === trim( (string) ( isset( $config['static_message'] ) ? $config['static_message'] : '' ) ) ) {
            return array(
                'success' => false,
                'error'   => 'Static operative heartbeat zahteva static_message.',
            );
        }

        if ( ! empty( $config['enabled'] ) && 'static_exact_message' !== ( isset( $config['execution_mode'] ) ? (string) $config['execution_mode'] : '' ) && ! $brief_written && method_exists( $chat_agent, 'has_operative_heartbeat_instructions' ) && ! $chat_agent->has_operative_heartbeat_instructions( $target_role ) ) {
            return array(
                'success' => false,
                'error'   => 'Operative heartbeat ne moze biti aktiviran bez task_brief ili postojeceg nepraznog brief-a.',
            );
        }

        $chat_agent->save_operative_heartbeat_config( $config, $target_role );
        $verified_config = $chat_agent->get_operative_heartbeat_config( $target_role );
        $has_brief       = method_exists( $chat_agent, 'has_operative_heartbeat_instructions' ) ? $chat_agent->has_operative_heartbeat_instructions( $target_role ) : $brief_written;
        $static_ready    = isset( $verified_config['execution_mode'], $verified_config['static_message'] )
            && 'static_exact_message' === sanitize_key( (string) $verified_config['execution_mode'] )
            && '' !== trim( (string) $verified_config['static_message'] );
        $scheduler_sync  = method_exists( $chat_agent, 'sync_operative_heartbeat_scheduler_interval' ) ? $chat_agent->sync_operative_heartbeat_scheduler_interval() : array();

        return array(
            'success' => true,
            'summary' => empty( $changed ) ? 'No changes.' : 'Operative heartbeat updated: ' . implode( ', ', $changed ),
            'config'  => $verified_config,
            'brief_written' => $brief_written,
            'has_brief' => $has_brief,
            'static_ready' => $static_ready,
            'verified_enabled' => ! empty( $verified_config['enabled'] ),
            'scheduler_sync' => $scheduler_sync,
            'postconditions' => array(
                array(
                    'id'    => 'operative_heartbeat_' . $target_role . '_config_saved',
                    'ok'    => true,
                    'label' => 'Operative heartbeat config was saved.',
                ),
                array(
                    'id'    => 'operative_heartbeat_' . $target_role . '_enabled_verified',
                    'ok'    => empty( $config['enabled'] ) || ! empty( $verified_config['enabled'] ),
                    'label' => 'Enabled heartbeat config must verify as enabled after save.',
                ),
                array(
                    'id'    => 'operative_heartbeat_' . $target_role . '_brief_or_static_ready',
                    'ok'    => empty( $config['enabled'] ) || ! empty( $has_brief ) || ! empty( $static_ready ),
                    'label' => 'Enabled operative heartbeat must have either a durable task brief or a static exact message.',
                ),
            ),
        );
    }
}
