<?php

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

/**
 * Admin Autonomy Chat — Facade.
 *
 * All domain logic lives in traits under includes/admin-autonomy/traits/.
 * This class composes them, owns properties/constructor, and exposes the
 * public API (respond, build_turn_runtime, execute_turn_tool_calls, etc.).
 */
class Niwa_AI_Admin_Autonomy_Chat {

    use Niwa_AI_Chat_Tool_Definitions;
    use Niwa_AI_Chat_System_Instructions;
    use Niwa_AI_Chat_Heartbeat;
    use Niwa_AI_Chat_Metrics;
    use Niwa_AI_Chat_Site_Profile;
    use Niwa_AI_Chat_Onboarding;
    use Niwa_AI_Chat_Session;
    use Niwa_AI_Chat_Tool_Executor;
    use Niwa_AI_Chat_Media;
    use Niwa_AI_Chat_Content_Access;
    use Niwa_AI_Chat_Code_Search;
    use Niwa_AI_Chat_Daily_Reports;
    use Niwa_AI_Chat_Context;
    use Niwa_AI_Tools_Settings;

    /* ================================================================
     * Constants & Properties
     * ============================================================= */

    private const MAIN_HISTORY_MESSAGE_LIMIT = 20;
    private const SUBAGENT_HISTORY_MESSAGE_LIMIT = 16;

    /** @var Niwa_AI_Auth_Provider_Interface */
    private $auth_manager;
    /** @var Niwa_AI_AI_Client_Interface */
    private $openai_client;
    private $model_router;
    private $store_overview;
    private $memory_searcher;
    /** @var Niwa_AI_Content_Store_Interface|null */
    private $content_store;
    private $telegram;
    private $whatsapp;
    private $store;
    private $site_discovery;
    private $db_intelligence;
    private $code_intelligence;
    private $workspace;
    private $email_service;
    private $admin_tools;
    private $scheduler;
    private $turn_engine;
    private $session_state;
    private $subagent_manager;
    private $skills_manager;
    /** @var Niwa_AI_Conversation_Store|null */
    private $conversation_store;
    private $companion_memory_manager;
    private $session_memory_store;
    private $turn_runtime_policy;
    private $runtime_trace_logger;
    private $runtime_tool_registry_cache = array();
    private $runtime_tool_map_cache      = array();
    private $daily_report_dispatcher;

    /* ================================================================
     * Constructor & Dependency Injection
     * ============================================================= */

    public function __construct( Niwa_AI_Auth_Provider_Interface $auth_manager, Niwa_AI_AI_Client_Interface $openai_client, $model_router, $store_overview = null, $memory_searcher = null, ?Niwa_AI_Content_Store_Interface $content_store = null, $telegram = null, $store = null, $site_discovery = null, $db_intelligence = null, $code_intelligence = null, $workspace = null, $admin_tools = null, $scheduler = null, $turn_engine = null, $session_state = null, $subagent_manager = null, $skills_manager = null, $whatsapp = null ) {
        $this->auth_manager      = $auth_manager;
        $this->openai_client     = $openai_client;
        $this->model_router      = $model_router;
        $this->store_overview    = $store_overview;
        $this->memory_searcher   = $memory_searcher;
        $this->content_store     = $content_store;
        $this->telegram          = $telegram;
        $this->whatsapp          = $whatsapp;
        $this->store             = $store;
        $this->site_discovery    = $site_discovery;
        $this->db_intelligence   = $db_intelligence;
        $this->code_intelligence = $code_intelligence;
        $this->workspace         = $workspace;
        $this->admin_tools       = $admin_tools;
        $this->scheduler         = $scheduler;
        $this->turn_engine       = $turn_engine;
        $this->session_state     = $session_state;
        $this->subagent_manager  = $subagent_manager;
        $this->skills_manager    = $skills_manager;

        if ( function_exists( 'add_action' ) ) {
            add_action( 'niwa_ai_inbound_email_poll', array( $this, 'handle_inbound_email_poll_cron' ) );
        }

        if ( method_exists( $this, 'ensure_inbound_email_cron_scheduled' ) ) {
            $this->ensure_inbound_email_cron_scheduled();
        }
    }

    public function set_email_service( $email_service ) {
        $this->email_service = $email_service;
    }

    public function set_conversation_store( $conversation_store ) {
        $this->conversation_store = $conversation_store;
    }

    public function set_companion_memory_manager( $companion_memory_manager ) {
        $this->companion_memory_manager = $companion_memory_manager;
    }

    public function set_session_memory_store( $session_memory_store ) {
        $this->session_memory_store = $session_memory_store;
    }

    public function set_daily_report_dispatcher( $dispatcher ) {
        $this->daily_report_dispatcher = is_callable( $dispatcher ) ? $dispatcher : null;
    }

    public function get_turn_engine() {
        return $this->turn_engine;
    }

    private function log_runtime_trace( $event, $context = array(), $event_level = '' ) {
        if ( null === $this->runtime_trace_logger && class_exists( 'Niwa_AI_Admin_Autonomy_Runtime_Trace_Logger' ) ) {
            $this->runtime_trace_logger = new Niwa_AI_Admin_Autonomy_Runtime_Trace_Logger();
        }
        if ( $this->runtime_trace_logger && method_exists( $this->runtime_trace_logger, 'log' ) ) {
            $this->runtime_trace_logger->log( $event, is_array( $context ) ? $context : array( 'value' => $context ), $event_level );
        }
    }

    /* ================================================================
     * Auth helpers
     * ============================================================= */

    public function get_auth_mode() {
        return $this->auth_manager ? $this->auth_manager->get_mode() : 'api_key';
    }

    public function get_auth_credentials() {
        return $this->auth_manager ? $this->auth_manager->get_credentials() : array();
    }

    public function get_openai_client_instance() {
        return $this->openai_client;
    }

    /**
     * Get the stored OpenAI API key directly, regardless of auth mode.
     * Needed for Whisper API which does not support Codex OAuth tokens.
     *
     * @return string API key or ''.
     */
    public function get_api_key_direct() {
        return $this->auth_manager ? $this->auth_manager->get_api_key() : '';
    }

    private function get_founder_model( $mode = 'main' ) {
        $mode = 'subagent' === (string) $mode ? 'subagent' : 'main';
        if ( ! $this->model_router ) {
            return 'gpt-5.4-mini';
        }

        if ( 'subagent' === $mode && method_exists( $this->model_router, 'route_for_founder_subagent' ) ) {
            return (string) $this->model_router->route_for_founder_subagent();
        }

        if ( method_exists( $this->model_router, 'route_for_founder_agent' ) ) {
            return (string) $this->model_router->route_for_founder_agent();
        }

        return (string) $this->model_router->route_for_agent();
    }

    /* ================================================================
     * Core public API
     * ============================================================= */

    public function prepare_context( $session_id, $history = array() ) {
        $context = array(
            'memory_summary' => $this->maybe_refresh_memory_summary( $session_id, $history ),
            'seed_notes'     => $this->get_seed_notes(),
            'site_context_blob' => $this->build_site_context_blob(),
            'recent_work_notes' => $this->build_recent_work_notes(),
        );

        if ( $this->companion_memory_manager && method_exists( $this->companion_memory_manager, 'build_prompt_summary' ) ) {
            $context['companion_memory_summary'] = (string) $this->companion_memory_manager->build_prompt_summary( $session_id, 1600 );
        }

        if ( $this->session_state && method_exists( $this->session_state, 'get_context_blocks' ) ) {
            $context = array_merge( $context, (array) $this->session_state->get_context_blocks( $session_id ) );
        }

        return $context;
    }

    public function respond( $prompt, $context = array() ) {
        $prompt = trim( (string) $prompt );
        if ( '' === $prompt ) {
            // Founder may have sent media without text (sticker, GIF/animation,
            // photo without caption, voice that failed to transcribe, etc.).
            // Don't bail — synthesize a prompt from the media that IS in
            // context so the agent actually reasons about what was sent.
            $synthesized = $this->synthesize_prompt_from_media_context( $context );
            if ( '' !== $synthesized ) {
                $prompt = $synthesized;
            } else {
                return array(
                    'success' => false,
                    'error'   => 'Prazna poruka.',
                );
            }
        }

        if ( ! $this->auth_manager || ! $this->openai_client || ! $this->turn_engine ) {
            return array(
                'success' => false,
                'error'   => 'Founder chat nije dostupan jer agent runtime nije ucitan.',
            );
        }

        $is_continuation = ! empty( $context['continuation_context'] );
        $is_subagent = ! empty( $context['is_subagent'] );
        if ( $is_continuation && is_array( $context['continuation_context'] ) ) {
            $continuation_seed = Niwa_AI_Agent_Context::from_continuation( $context['continuation_context'] );
            $context['continuation_context'] = array_merge( $context['continuation_context'], $continuation_seed );
            $continued_prompt = $continuation_seed['current_user_prompt']
                ?? ( $continuation_seed['turn_objective'] ?? '' );
            $continued_prompt = class_exists( 'Niwa_AI_Admin_Autonomy_Task_Integrity' )
                ? Niwa_AI_Admin_Autonomy_Task_Integrity::canonicalize_content( $continued_prompt )
                : trim( (string) $continued_prompt );
            if ( '' !== $continued_prompt ) {
                $prompt = $continued_prompt;
            }
        }

	        $context['current_prompt'] = $prompt;
	        $turn_options = array(
	            'mode'                      => $is_subagent ? 'subagent' : 'main',
	            'allow_writes'              => $is_subagent ? ! empty( $context['subagent_allow_writes'] ) : true,
	            'force_action'              => ! empty( $context['force_action'] ),
	            'channel'                   => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : 'telegram',
	        );
		if ( ! empty( $context['request_id'] ) ) {
			$turn_options['request_id'] = sanitize_text_field( (string) $context['request_id'] );
		}
		if ( ! empty( $context['admin_ui_job_id'] ) ) {
			$turn_options['admin_ui_job_id'] = sanitize_text_field( (string) $context['admin_ui_job_id'] );
			$turn_options['admin_ui_pre_delivery'] = true;
		}
        if ( ! empty( $context['soft_deadline'] ) ) {
            $turn_options['soft_deadline'] = intval( $context['soft_deadline'] );
        }
        if ( ! empty( $context['cancel_probe'] ) && is_callable( $context['cancel_probe'] ) ) {
            $turn_options['cancel_probe'] = $context['cancel_probe'];
        }
        if ( ! empty( $context['turn_progress_callback'] ) && is_callable( $context['turn_progress_callback'] ) ) {
            $turn_options['progress_callback'] = $context['turn_progress_callback'];
        }

        return $this->turn_engine->run(
            $this,
            $prompt,
            $context,
            $turn_options
        );
    }

    public function build_turn_runtime( $prompt, $context = array(), $mode = 'main', $options = array() ) {
        if ( ! $this->auth_manager || ! $this->openai_client ) {
            return array(
                'success' => false,
                'error'   => 'Founder chat nema AI dependencies.',
            );
        }

        $auth_mode = $this->auth_manager->get_mode();
        $creds     = $this->auth_manager->get_credentials();
        $user_role = isset( $context['user_role'] ) ? (string) $context['user_role'] : 'admin';
        $model     = $this->get_founder_model( 'subagent' === $mode ? 'subagent' : 'main' );
        $continuation_seed = isset( $context['continuation_context'] ) && is_array( $context['continuation_context'] )
            ? $context['continuation_context']
            : array();
        if ( ! empty( $continuation_seed['current_user_prompt'] ) ) {
            $context['current_prompt'] = (string) $continuation_seed['current_user_prompt'];
        } elseif ( ! empty( $continuation_seed['turn_objective'] ) ) {
            $context['current_prompt'] = (string) $continuation_seed['turn_objective'];
        }
        $input     = $this->build_turn_input_content( $prompt, $context );

        $mode             = 'subagent' === $mode ? 'subagent' : 'main';
        $allow_writes     = 'subagent' === $mode ? ! empty( $options['allow_writes'] ) : true;
        $runtime_policy   = $this->resolve_turn_runtime_policy( $context, $mode, $options );
        $context['turn_runtime_policy'] = $runtime_policy;
        $context['turn_runtime_kind']   = isset( $runtime_policy['kind'] ) ? (string) $runtime_policy['kind'] : 'execute';
        $options['turn_runtime_policy'] = $runtime_policy;

        if ( 'lead' === $user_role && 'main' === $mode ) {
            $instructions = $this->build_whatsapp_lead_system_instructions( $context, $options );
        } elseif ( 'stock_manager' === $user_role && 'main' === $mode ) {
            $instructions = $this->build_stock_manager_system_instructions( $context, $options );
        } else {
            $instructions = 'subagent' === $mode
                ? $this->build_subagent_system_instructions( $context, $options )
                : $this->build_system_instructions( $context, $options );
        }

        $dynamic_instruction_context = '';
        // Proactive memory retrieval for founder sessions — scoped to current session only.
        $memory_session_id = isset( $context['session_id'] ) ? (string) $context['session_id'] : '';
        if ( 'main' === $mode && ! in_array( $user_role, array( 'stock_manager', 'lead' ), true ) && $this->memory_searcher && '' !== $memory_session_id ) {
            $memory_results = $this->memory_searcher->search_conversation_memory(
                $input,
                $memory_session_id,
                3,
                $this->conversation_store
            );
            if ( ! empty( $memory_results ) ) {
                $memory_context = "\n\n[PAST CONTEXT — relevant snippets from previous conversations]\n";
                foreach ( $memory_results as $mr ) {
                    $memory_context .= '- ' . $mr['text'] . "\n";
                }
                $memory_context .= "[/PAST CONTEXT]\n";
                $dynamic_instruction_context .= $memory_context;
            }
        }

        // ── Continuation context: resume from a previous soft-deadline cancellation ──
        if ( 'main' === $mode && ! in_array( $user_role, array( 'stock_manager', 'lead' ), true ) && $this->companion_memory_manager && '' !== $memory_session_id && method_exists( $this->companion_memory_manager, 'search' ) ) {
            $companion_results = (array) $this->companion_memory_manager->search( $input, $memory_session_id, 2 );
            if ( ! empty( $companion_results ) ) {
                $companion_context = "\n\n[COMPANION RECALL — diary, decisions, and companion memory]\n";
                foreach ( $companion_results as $result ) {
                    if ( ! is_array( $result ) || empty( $result['text'] ) ) {
                        continue;
                    }
                    $companion_context .= '- ' . trim( (string) $result['text'] );
                    if ( ! empty( $result['path'] ) ) {
                        $companion_context .= "\n  Source: " . (string) $result['path'];
                    }
                    $companion_context .= "\n";
                }
                $companion_context .= "[/COMPANION RECALL]\n";
                $dynamic_instruction_context .= $companion_context;
            }
        }

        $continuation = isset( $context['continuation_context'] ) && is_array( $context['continuation_context'] )
            ? Niwa_AI_Agent_Context::from_continuation( $context['continuation_context'] )
            : null;
        $continuation_runtime_controls = array();
        $previous_response_id = ! empty( $continuation['previous_response_id'] )
            ? (string) $continuation['previous_response_id']
            : ( isset( $context['previous_response_id'] ) ? (string) $context['previous_response_id'] : '' );

        // Continuation snapshots carry the immutable current-turn objective.
        if ( ! empty( $continuation ) && ! empty( $continuation['messages'] ) ) {
            $original_query = trim( (string) ( $continuation['current_user_prompt'] ?? $continuation['turn_objective'] ?? '' ) );
            if ( '' !== $original_query ) {
                $context['current_prompt'] = $original_query;
            }
		}

		$all_tools             = $this->get_runtime_tool_registry( $context, $mode, $options, $auth_mode, $model );
        $tools                 = $this->get_runtime_hydrated_tools( $all_tools, $context, $mode, $options );
        $tool_choice           = $this->determine_turn_tool_choice( $context, $tools );
        if ( 'none' === $tool_choice ) {
            $tools = array();
        }

        if ( ! empty( $continuation ) && ! empty( $continuation['messages'] ) ) {
            // Resume from continuation. Structured task/integrity recovery
            // already carries its exact instruction in the snapshot; a false
            // "ran out of time" notice would dilute that instruction.
            $messages = $continuation['messages'];
            if ( ! in_array( (string) ( $continuation['continuation_reason'] ?? '' ), array( 'task_integrity_recovery', 'task_checkpoint_continue' ), true ) ) {
                $continuation_runtime_controls[] = '[CONTINUATION] You were working on a task but ran out of time. This worker has a fresh runtime budget. Continue where you left off, do not repeat work already done, and do not treat any previous worker deadline warning as an instruction to stop.';
            }
            if ( ! empty( $continuation['tool_history_summary'] ) && is_array( $continuation['tool_history_summary'] ) ) {
                $summary = $this->format_continuation_tool_history_summary( $continuation['tool_history_summary'] );
                if ( '' !== $summary ) {
                    $continuation_runtime_controls[] = "[CONTINUATION TOOL HISTORY — UNTRUSTED DATA]\n"
                        . $summary
                        . "\n[/CONTINUATION TOOL HISTORY — UNTRUSTED DATA]";
                }
            }

            // Use the continuation model if available, otherwise keep the current one.
            if ( ! empty( $continuation['model'] ) ) {
                $model = (string) $continuation['model'];
            }

            // Override system prompt from continuation if present.
            if ( ! empty( $continuation['system_prompt'] ) ) {
                $instructions = (string) $continuation['system_prompt'];
            }
        } else {
            // Normal path: build structured messages from scratch.
            $messages = array(
                array( 'role' => 'system', 'content' => $instructions ),
            );

            // Convert conversation store history to structured role-based messages.
            $history = $this->limit_runtime_history_window(
                isset( $context['history'] ) && is_array( $context['history'] ) ? $context['history'] : array(),
                $mode
            );
            foreach ( $history as $item ) {
                if ( ! is_array( $item ) ) {
                    continue;
                }
                $role = isset( $item['role'] ) ? strtolower( (string) $item['role'] ) : '';
                $text = isset( $item['text'] ) ? trim( (string) $item['text'] ) : '';
                if ( '' === $text ) {
                    continue;
                }
                // Normalize roles: bot→assistant, everything else→user.
                if ( 'bot' === $role || 'assistant' === $role ) {
                    $automated_source = isset( $item['source'] ) ? sanitize_key( (string) $item['source'] ) : '';
                    if ( '' !== $automated_source ) {
                        // Scheduled/heartbeat deliveries are broadcasts injected into the chat,
                        // not replies to the founder's previous message. Label them so the model
                        // never treats the founder's next message as a reaction it has to satisfy
                        // inside that automated context.
                        $text = '[AUTOMATED ' . strtoupper( $automated_source ) . ' DELIVERY — this message was pushed into the chat by an automated scheduled/heartbeat run. It is NOT a direct reply to the founder. Do not assume the founder\'s next message refers to it unless they clearly do.]' . "\n" . $text;
                    }
                    $messages[] = array( 'role' => 'assistant', 'content' => $text );
                } else {
                    $messages[] = array( 'role' => 'user', 'content' => $text );
                }
            }

            // Current user prompt as the final message.
            $messages[] = array( 'role' => 'user', 'content' => $input );
        }

        $instructions .= $this->build_tool_runtime_prompt_block( $all_tools, $tools, $context, $mode, $options );
        if ( '' !== $dynamic_instruction_context ) {
            $instructions .= $dynamic_instruction_context;
        }
        if ( ! empty( $continuation ) ) {
            $instructions = $this->lift_continuation_runtime_controls(
                $instructions,
                $messages,
                $continuation_runtime_controls,
                'task_integrity_recovery' === (string) ( $continuation['continuation_reason'] ?? '' )
            );
        }
        if ( ! empty( $messages ) && isset( $messages[0]['role'] ) && 'system' === (string) $messages[0]['role'] ) {
            $messages[0]['content'] = $instructions;
        }

        $tool_names = array();
        foreach ( (array) $tools as $tool ) {
            if ( is_array( $tool ) && ! empty( $tool['function']['name'] ) ) {
                $tool_names[] = sanitize_key( (string) $tool['function']['name'] );
            }
        }
        $this->log_runtime_trace(
            'turn_runtime_built',
            array(
                'channel'             => isset( $context['channel'] ) ? sanitize_key( (string) $context['channel'] ) : '',
                'session_id'          => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'chat_id'             => isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '',
                'message_id'          => isset( $context['message_id'] ) ? intval( $context['message_id'] ) : 0,
                'mode'                => $mode,
                'model'               => $model,
                'auth_mode'           => $auth_mode,
                'runtime_policy'      => $runtime_policy,
                'tool_choice'         => $tool_choice,
                'tool_names'          => $tool_names,
                'instruction_bytes'   => strlen( $instructions ),
                'message_count'       => count( $messages ),
                'previous_response_id' => '' !== $previous_response_id,
                'prompt_preview'      => $input,
            ),
            class_exists( 'Niwa_AI_Admin_Autonomy_Runtime_Trace_Logger' )
                ? Niwa_AI_Admin_Autonomy_Runtime_Trace_Logger::EVENT_LEVEL_RUNTIME
                : 'runtime'
        );

        return array(
            'success'              => true,
            'model'                => $model,
            'auth_mode'            => $auth_mode,
            'creds'                => $creds,
            'input'                => $input,
            'instructions'         => $instructions,
            'tools'                => $tools,
            'tool_choice'          => $tool_choice,
            'messages'             => $messages,
            'max_rounds'           => 30,
            'previous_response_id'  => $previous_response_id,
            'turn_objective'       => ! empty( $continuation['turn_objective'] )
                ? (string) $continuation['turn_objective']
                : (
                    class_exists( 'Niwa_AI_Admin_Autonomy_Task_Integrity' )
                        ? Niwa_AI_Admin_Autonomy_Task_Integrity::canonicalize_content( $input )
                        : ( is_string( $input ) ? $input : (string) wp_json_encode( $input ) )
                ),
            'turn_frame'           => ! empty( $continuation['turn_frame'] ) && is_array( $continuation['turn_frame'] ) ? $continuation['turn_frame'] : array(),
            'tool_evidence'        => ! empty( $continuation['tool_evidence'] ) && is_array( $continuation['tool_evidence'] ) ? $continuation['tool_evidence'] : array(),
            'execution_provenance' => ! empty( $continuation['execution_provenance'] ) && is_array( $continuation['execution_provenance'] ) ? $continuation['execution_provenance'] : array(),
        );
    }

    /**
     * Move internal continuation controls into the leading instructions.
     *
     * The message validator intentionally drops trailing system messages.
     * Runtime controls therefore cannot remain in the chronological trace.
     * Tool history is explicitly bounded as untrusted data before it reaches
     * this method; founder/tool content is never promoted by this path.
     *
     * @param string $instructions Existing leading instructions.
     * @param array  $messages Chronological continuation messages, by reference.
     * @param array  $extra_controls Controls generated for this handoff.
     * @param bool   $lift_legacy_trailing_system Backward compatibility for
     *                                            already-saved integrity snapshots.
     * @return string
     */
    private function lift_continuation_runtime_controls( $instructions, &$messages, $extra_controls = array(), $lift_legacy_trailing_system = false ) {
        $instructions = (string) $instructions;
        $messages     = is_array( $messages ) ? array_values( $messages ) : array();
        $controls     = array();
        $kept         = array();

        foreach ( $messages as $message ) {
            if (
                is_array( $message )
                && 'system' === (string) ( $message['role'] ?? '' )
                && ! empty( $message['niwa_runtime_control'] )
            ) {
                $content = trim( (string) ( $message['content'] ?? '' ) );
                if ( '' !== $content ) {
                    $controls[] = $content;
                }
                continue;
            }
            $kept[] = $message;
        }
        $messages = $kept;

        if ( $lift_legacy_trailing_system ) {
            $legacy = array();
            while ( ! empty( $messages ) ) {
                $last = end( $messages );
                if ( ! is_array( $last ) || 'system' !== (string) ( $last['role'] ?? '' ) ) {
                    break;
                }
                array_pop( $messages );
                $content = trim( (string) ( $last['content'] ?? '' ) );
                if ( '' !== $content ) {
                    array_unshift( $legacy, $content );
                }
            }
            $controls = array_merge( $legacy, $controls );
        }

        foreach ( (array) $extra_controls as $control ) {
            $control = trim( (string) $control );
            if ( '' !== $control ) {
                $controls[] = $control;
            }
        }
        if ( empty( $controls ) ) {
            return $instructions;
        }

        return rtrim( $instructions )
            . "\n\n[CONTINUATION RUNTIME CONTROL]\n"
            . implode( "\n\n", $controls )
            . "\n[/CONTINUATION RUNTIME CONTROL]";
    }

    private function format_continuation_tool_history_summary( $summary ) {
        $summary = is_array( $summary ) ? $summary : array();
        $total   = isset( $summary['total_tool_calls'] ) ? intval( $summary['total_tool_calls'] ) : 0;
        $counts  = isset( $summary['counts'] ) && is_array( $summary['counts'] ) ? $summary['counts'] : array();
        $recent  = isset( $summary['recent'] ) && is_array( $summary['recent'] ) ? $summary['recent'] : array();
        $prepared = isset( $summary['prepared_workspaces'] ) && is_array( $summary['prepared_workspaces'] ) ? $summary['prepared_workspaces'] : array();

        if ( $total < 1 && empty( $counts ) && empty( $recent ) && empty( $prepared ) ) {
            return '';
        }

        $parts = array( '[CONTINUATION TOOL HISTORY] Tool calls already executed before this handoff: ' . $total . '.' );
        if ( ! empty( $counts ) ) {
            $top = array();
            foreach ( array_slice( $counts, 0, 10, true ) as $name => $count ) {
                $top[] = sanitize_key( (string) $name ) . '=' . intval( $count );
            }
            if ( ! empty( $top ) ) {
                $parts[] = 'Top tools: ' . implode( ', ', $top ) . '.';
            }
        }
        if ( ! empty( $recent ) ) {
            $clean_recent = array();
            foreach ( array_slice( $recent, -12 ) as $name ) {
                $name = sanitize_key( (string) $name );
                if ( '' !== $name ) {
                    $clean_recent[] = $name;
                }
            }
            if ( ! empty( $clean_recent ) ) {
                $parts[] = 'Recent tools: ' . implode( ' -> ', $clean_recent ) . '.';
            }
        }
        if ( ! empty( $prepared ) ) {
            $workspace_lines = array();
            foreach ( array_slice( $prepared, -5 ) as $item ) {
                if ( ! is_array( $item ) ) {
                    continue;
                }
                $operation_id = isset( $item['operation_id'] ) ? sanitize_text_field( (string) $item['operation_id'] ) : '';
                if ( '' === $operation_id ) {
                    continue;
                }
                $targets = isset( $item['target_paths'] ) && is_array( $item['target_paths'] ) ? array_slice( $item['target_paths'], 0, 4 ) : array();
                $workspace_lines[] = $operation_id . ( ! empty( $targets ) ? ' targets=' . implode( ', ', array_map( 'sanitize_text_field', $targets ) ) : '' );
            }
            if ( ! empty( $workspace_lines ) ) {
                $parts[] = 'Prepared main-coding workspaces still relevant: ' . implode( ' | ', $workspace_lines ) . '. Reuse their operation_id/work_path values before preparing duplicates.';
            }
        }
        $parts[] = 'Use this as completed work. Do not restart broad file-reading; proceed to edit/apply or give a concrete blocker.';
        $parts[] = '[/CONTINUATION TOOL HISTORY]';

        return implode( "\n", $parts );
    }

    public function execute_turn_tool_calls( $tool_calls, $query, $context = array(), $mode = 'main', $options = array() ) {
        return $this->execute_founder_tool_calls( $tool_calls, $query, $context, $mode, $options );
    }

    public function get_mcp_runtime_tool_definitions( $context = array(), $mode = 'main', $options = array() ) {
        $context = $this->build_mcp_runtime_context( $context );
        $mode    = 'subagent' === (string) $mode ? 'subagent' : 'main';
        $options = is_array( $options ) ? $options : array();
        if ( ! array_key_exists( 'allow_writes', $options ) ) {
            $options['allow_writes'] = true;
        }

        return $this->dedupe_mcp_function_tool_definitions(
            $this->get_runtime_tool_registry( $context, $mode, $options )
        );
    }

    public function execute_mcp_runtime_tool_call( $name, $arguments, $context = array(), $options = array() ) {
        $name      = trim( (string) $name );
        $arguments = is_array( $arguments ) ? $arguments : array();
        if ( '' === $name ) {
            return array(
                'success' => false,
                'ok'      => false,
                'error'   => 'MCP tool name is required.',
            );
        }

        $context = $this->build_mcp_runtime_context( $context );
        $options = is_array( $options ) ? $options : array();
        if ( ! array_key_exists( 'allow_writes', $options ) ) {
            $options['allow_writes'] = true;
        }
        if ( empty( $options['turn_id'] ) ) {
            $options['turn_id'] = 'mcp-' . gmdate( 'Ymd-His' ) . '-' . substr( md5( $name . microtime( true ) ), 0, 8 );
        }

        $query   = isset( $context['current_prompt'] ) && '' !== trim( (string) $context['current_prompt'] )
            ? (string) $context['current_prompt']
            : 'MCP tool call: ' . $name;
        $call_id = 'mcp_' . substr( md5( $name . wp_json_encode( $arguments ) . microtime( true ) ), 0, 16 );

        $payloads = $this->execute_founder_tool_calls(
            array(
                array(
                    'id'        => $call_id,
                    'name'      => $name,
                    'arguments' => $arguments,
                ),
            ),
            $query,
            $context,
            'main',
            $options
        );

        foreach ( (array) $payloads as $payload ) {
            if ( ! is_array( $payload ) || ( isset( $payload['tool_call_id'] ) && $call_id !== (string) $payload['tool_call_id'] ) ) {
                continue;
            }

            $content = isset( $payload['content'] ) ? (string) $payload['content'] : '';
            $decoded = '' !== $content ? json_decode( $content, true ) : array();
            if ( ! is_array( $decoded ) ) {
                $decoded = array(
                    'success' => true,
                    'ok'      => true,
                    'text'    => $content,
                );
            }

            $error = isset( $decoded['error'] ) ? (string) $decoded['error'] : '';
            if ( 0 === strpos( $error, 'Unsupported founder tool:' ) ) {
                $decoded['mcp_runtime_unsupported'] = true;
            }

            return $decoded;
        }

        return array(
            'success' => false,
            'ok'      => false,
            'error'   => 'MCP runtime returned no payload for tool: ' . $name,
        );
    }

    public function is_mcp_runtime_write_tool( $tool_name ) {
        $tool_name = trim( (string) $tool_name );
        if ( '' === $tool_name ) {
            return true;
        }

        if ( in_array( $tool_name, $this->get_mcp_runtime_read_only_tool_names(), true ) ) {
            return false;
        }

        if ( in_array( $tool_name, $this->get_direct_admin_action_tool_names(), true ) ) {
            return true;
        }

        if ( in_array( $tool_name, $this->get_mcp_runtime_mutating_tool_names(), true ) ) {
            return true;
        }

        return true;
    }

    private function build_mcp_runtime_context( $context ) {
        $context = is_array( $context ) ? $context : array();
        $user_id = function_exists( 'get_current_user_id' ) ? intval( get_current_user_id() ) : 0;
        $login   = '';
        if ( function_exists( 'wp_get_current_user' ) ) {
            $user = wp_get_current_user();
            if ( is_object( $user ) && isset( $user->user_login ) ) {
                $login = (string) $user->user_login;
            }
        }

        if ( empty( $context['session_id'] ) ) {
            $context['session_id'] = 'mcp-' . ( $user_id > 0 ? (string) $user_id : 'anonymous' );
        }
        if ( empty( $context['user_role'] ) ) {
            $context['user_role'] = 'admin';
        }
        if ( empty( $context['channel'] ) ) {
            $context['channel'] = 'mcp';
        }
        if ( empty( $context['source'] ) ) {
            $context['source'] = 'mcp';
        }
        if ( ! isset( $context['admin_user_id'] ) ) {
            $context['admin_user_id'] = $user_id;
        }
        if ( '' !== $login && empty( $context['admin_user_login'] ) ) {
            $context['admin_user_login'] = $login;
        }
        if ( empty( $context['current_prompt'] ) ) {
            $context['current_prompt'] = 'MCP external tool call.';
        }

        return $context;
    }

    private function dedupe_mcp_function_tool_definitions( $tools ) {
        $unique = array();
        $seen   = array();

        foreach ( (array) $tools as $tool ) {
            if ( empty( $tool['function']['name'] ) ) {
                continue;
            }

            $name = (string) $tool['function']['name'];
            if ( '' === $name || isset( $seen[ $name ] ) ) {
                continue;
            }

            $seen[ $name ] = true;
            $unique[]      = $tool;
        }

        return $unique;
    }

    private function get_mcp_runtime_read_only_tool_names() {
        return array(
            'tool_search',
            'tool_active_list',
            'memory_recall_all',
            'memory_search',
            'memory_get',
            'agent_memory_search',
            'agent_memory_entry_reader',
            'companion_memory_search',
            'companion_memory_recall',
            'companion_memory_review',
            'session_recall',
            'image_analyze',
            'site_image_lookup',
            'extract_partner_product_from_url',
            'woo_product_lookup',
            'woo_variation_lookup',
            'wp_environment_capabilities_get',
            'debug_log_tail_read',
            'code_search',
            'file_read',
            'file_list',
            'batch_read',
            'code_scan_run',
            'main_coding_edit_dry_run',
            'read_post_field',
            'search_text_in_post',
            'blog_posts_list',
            'blog_post_get',
            'founder_notes_get',
            'session_status_get',
            'sessions_list',
            'session_history_get',
            'metrics_snapshot',
            'frontend_questions_log',
            'site_discovery_get',
            'db_schema_get',
            'code_intelligence_get',
            'workspace_bootstrap_get',
            'workspace_list_artifacts',
            'email_contact_lookup',
            'inbound_email_cron_status',
            'email_inbox_read',
            'email_followup_candidates',
            'appointment_contact_lookup',
            'scheduled_tasks_list',
            'skill_search',
            'skill_list',
            'skill_get',
            'skill_file_read',
            'skill_files_list',
            'read_uploaded_spreadsheet',
            'search_users',
            'get_user',
            'get_current_user',
            'list_categories',
            'get_category',
            'list_tags',
            'get_tag',
            'get_wp_settings',
            'get_site_info',
            'search_stock_images',
            'scan_site_for_linking',
            'search_pages',
            'get_page',
            'search_media',
            'get_media_details',
            'elementor_list_pages',
            'elementor_get_page_structure',
            'elementor_get_page_data',
            'elementor_get_active_kit',
            'elementor_get_kit_by_id',
            'elementor_find_widgets',
            'elementor_get_widget_by_id',
            'elementor_search_text',
            'search_woo_orders',
            'inspect_order_attribution',
            'woo_report_sales',
            'woo_report_orders_totals',
            'woo_report_products_totals',
            'woo_report_customers_totals',
            'woo_report_coupons_totals',
            'woo_report_reviews_totals',
            'cache_settings_get',
            'cache_presets_list',
            'list_wc_categories',
            'list_wc_tags',
            'list_wc_brands',
            'list_custom_post_types',
            'get_media_file',
            'wp_search_posts',
            'wp_get_post',
            'wp_option_get',
        );
    }

    private function get_proc_open_required_tool_names() {
        return array(
            'code_scan_run',
            'main_coding_shell_run',
            'main_coding_test_run',
        );
    }

    private function get_mcp_runtime_mutating_tool_names() {
        return array(
            'tool_load',
            'tool_unload',
            'extension_request_create',
            'extension_approval_record',
            'main_coding_shell_run',
            'main_coding_edit_dry_run',
            'main_coding_file_edit',
            'main_coding_workspace_prepare',
            'main_coding_workspace_apply',
            'main_coding_workspace_rollback',
            'main_coding_git_diff',
            'main_coding_lint_check',
            'main_coding_test_run',
            'clone_partner_variable_product_from_url',
            'founder_identity_save',
            'companion_memory_capture',
            'telegram_send_image',
            'telegram_send_sticker',
            'telegram_send_animation',
            'subagent_spawn',
            'sessions_spawn',
            'sessions_send',
            'coding_workflow_start',
            'subagents_control',
            'session_compact',
            'session_note_add',
            'session_reset',
            'update_heartbeat_config',
            'update_operative_heartbeat_config',
            'manage_whatsapp_access_numbers',
            'send_whatsapp_lead_outreach',
            'workspace_bootstrap_write',
            'workspace_export_orders_xlsx',
            'configure_imap_mailbox_from_existing_smtp',
            'schedule_reminder',
            'schedule_admin_plan',
            'schedule_self_wake',
            'schedule_orders_export_report',
            'scheduled_tasks_cancel',
            'debug_log_clear',
            'debug_log_trim_tail',
            'debug_log_append_line',
        );
    }

    public function enforce_turn_execution_policy( $prompt, $context = array(), $options = array(), $result = array() ) {
        unset( $prompt, $options );
        $context = is_array( $context ) ? $context : array();
        $result = is_array( $result ) ? $result : array();
        $runtime_policy = $this->resolve_turn_runtime_policy( $context, 'main', array() );
        if ( empty( $runtime_policy['enforce_execution_contract'] ) ) {
            return $result;
        }

        $contract = isset( $result['execution_contract'] ) && is_array( $result['execution_contract'] ) ? $result['execution_contract'] : array();
        $pending  = isset( $contract['pending_count'] ) ? intval( $contract['pending_count'] ) : 0;
        if ( $pending > 0 && $this->turn_result_is_extension_approval_ask( $context, $result ) ) {
            // The turn ended by asking the founder to approve a self-extension.
            // Blanking the reply here would destroy the approval question, so
            // the founder would never learn the task is parked on approval.
            $result['extension_pending_approval'] = true;
            $result['task_completion_state']      = 'pending_extension_approval';
            $result['original_task_completed']    = false;
            $result['execution_policy_warning']   = 'Execution contract left open items, but the turn is a self-extension approval ask; reply preserved for founder approval.';

            return $result;
        }
        if ( $pending > 0 ) {
            $items = array();
            if ( ! empty( $contract['pending'] ) && is_array( $contract['pending'] ) ) {
                foreach ( array_slice( $contract['pending'], 0, 5 ) as $entry ) {
                    if ( is_array( $entry ) && ! empty( $entry['label'] ) ) {
                        $items[] = (string) $entry['label'];
                    }
                }
            }
            $error = 'Agent stopped before completing required execution steps.';
            if ( ! empty( $items ) ) {
                $error .= ' Pending: ' . implode( '; ', $items );
            }
            $result['execution_incomplete'] = true;
            $result['execution_policy_warning'] = $error;
            $result['success'] = false;
            $result['reply'] = '';
            $result['error'] = $error;
        }

        return $result;
    }

    private function turn_result_is_extension_approval_ask( $context, $result ) {
        $result = is_array( $result ) ? $result : array();
        if ( '' === trim( isset( $result['reply'] ) ? (string) $result['reply'] : '' ) ) {
            return false;
        }

        $executed_tools = isset( $result['executed_tools'] ) && is_array( $result['executed_tools'] ) ? $result['executed_tools'] : array();
        if ( ! in_array( 'extension_request_create', array_map( 'sanitize_key', $executed_tools ), true ) ) {
            return false;
        }

        $context    = is_array( $context ) ? $context : array();
        $session_id = isset( $context['session_id'] ) ? trim( (string) $context['session_id'] ) : '';
        if ( '' === $session_id || ! $this->session_state || ! method_exists( $this->session_state, 'get_extension_workflow_state' ) ) {
            return false;
        }

        $workflow = (array) $this->session_state->get_extension_workflow_state( $session_id );
        $status   = isset( $workflow['status'] ) ? sanitize_key( (string) $workflow['status'] ) : '';

        return 'awaiting_approval' === $status;
    }

    public function process_background_subagents( $limit = 1 ) {
        if ( ! $this->subagent_manager || ! method_exists( $this->subagent_manager, 'process_queue' ) ) {
            return array(
                'success'   => true,
                'processed' => 0,
                'announced' => 0,
            );
        }

        return $this->subagent_manager->process_queue( $this, $limit );
    }

    public function enqueue_scheduled_subagent( $message, $job = array() ) {
        if ( ! $this->subagent_manager || ! method_exists( $this->subagent_manager, 'enqueue_scheduled' ) ) {
            return array( 'success' => false, 'error' => 'subagent_runtime_unavailable' );
        }

        return $this->subagent_manager->enqueue_scheduled( $this, $message, $job );
    }

    public function has_pending_background_subagents() {
        return $this->subagent_manager
            && method_exists( $this->subagent_manager, 'has_pending_jobs' )
            && ! empty( $this->subagent_manager->has_pending_jobs() );
    }

    private function get_runtime_tool_registry( $context = array(), $mode = 'main', $options = array(), $auth_mode = '', $model = '' ) {
        $context   = is_array( $context ) ? $context : array();
        $options   = is_array( $options ) ? $options : array();
        $mode      = 'subagent' === (string) $mode ? 'subagent' : 'main';
        $auth_mode = '' !== $auth_mode ? (string) $auth_mode : $this->get_auth_mode();
        $model     = '' !== $model ? (string) $model : $this->get_founder_model( $mode );
        $user_role = isset( $context['user_role'] ) ? (string) $context['user_role'] : 'admin';
        $is_untrusted_email = $this->is_untrusted_inbound_email_context( $context );
        $cache_key = $this->build_runtime_tool_cache_key( $context, $mode, $options, $auth_mode, $model );

        if ( isset( $this->runtime_tool_registry_cache[ $cache_key ] ) ) {
            return $this->runtime_tool_registry_cache[ $cache_key ];
        }

        if ( 'stock_manager' === $user_role && 'main' === $mode ) {
            $tools = $this->get_stock_manager_tool_definitions( $auth_mode, $model );
        } elseif ( 'lead' === $user_role && 'main' === $mode ) {
            $tools = $this->filter_lead_tool_definitions(
                $this->get_founder_tool_definitions(
                    true,
                    isset( $context['subagent_depth'] ) ? intval( $context['subagent_depth'] ) : 0,
                    $auth_mode,
                    $model
                )
            );
        } elseif ( 'subagent' === $mode ) {
            $tools = $this->get_subagent_tool_definitions(
                ! empty( $options['allow_writes'] ),
                isset( $context['subagent_depth'] ) ? intval( $context['subagent_depth'] ) : 0,
                isset( $context['subagent_scope'] ) ? (string) $context['subagent_scope'] : ( isset( $options['subagent_scope'] ) ? (string) $options['subagent_scope'] : 'general' ),
                $auth_mode,
                $model
            );
        } else {
            $tools = $this->get_founder_tool_definitions(
                'subagent' === $mode ? ! empty( $options['allow_writes'] ) : true,
                isset( $context['subagent_depth'] ) ? intval( $context['subagent_depth'] ) : 0,
                $auth_mode,
                $model
            );
        }

        if ( $is_untrusted_email ) {
            $allowed = array_fill_keys( $this->get_untrusted_inbound_email_allowed_tool_names(), true );
            $tools   = array_values(
                array_filter(
                    (array) $tools,
                    function ( $tool ) use ( $allowed ) {
                        $name = $this->extract_tool_name_from_definition( $tool );
                        return '' !== $name && isset( $allowed[ $name ] );
                    }
                )
            );
        }

        if ( ! empty( $context['suppress_image_analysis'] ) && is_array( $tools ) ) {
            $tools = array_values(
                array_filter(
                    $tools,
                    function ( $tool ) {
                        return empty( $tool['function']['name'] ) || 'image_analyze' !== (string) $tool['function']['name'];
                    }
                )
            );
        }

        if ( is_array( $tools ) && method_exists( $this, 'is_proc_open_runtime_available' ) && ! $this->is_proc_open_runtime_available() ) {
            $blocked_process_tools = array_fill_keys( $this->get_proc_open_required_tool_names(), true );
            $tools = array_values(
                array_filter(
                    $tools,
                    function ( $tool ) use ( $blocked_process_tools ) {
                        $name = $this->extract_tool_name_from_definition( $tool );
                        return '' === $name || ! isset( $blocked_process_tools[ $name ] );
                    }
                )
            );
        }

        $tools = array_values( is_array( $tools ) ? $tools : array() );
        $this->runtime_tool_registry_cache[ $cache_key ] = $tools;

        return $tools;
    }

    private function get_runtime_tool_map( $context = array(), $mode = 'main', $options = array() ) {
        $context   = is_array( $context ) ? $context : array();
        $options   = is_array( $options ) ? $options : array();
        $mode      = 'subagent' === (string) $mode ? 'subagent' : 'main';
        $auth_mode = $this->get_auth_mode();
        $model     = $this->get_founder_model( $mode );
        $cache_key = $this->build_runtime_tool_cache_key( $context, $mode, $options, $auth_mode, $model );

        if ( isset( $this->runtime_tool_map_cache[ $cache_key ] ) ) {
            return $this->runtime_tool_map_cache[ $cache_key ];
        }

        $map = array();
        foreach ( $this->get_runtime_tool_registry( $context, $mode, $options, $auth_mode, $model ) as $tool ) {
            if ( empty( $tool['function']['name'] ) ) {
                continue;
            }
            $map[ (string) $tool['function']['name'] ] = $tool;
        }

        $this->runtime_tool_map_cache[ $cache_key ] = $map;

        return $map;
    }

    private function build_runtime_tool_cache_key( $context, $mode, $options, $auth_mode, $model ) {
        $context = is_array( $context ) ? $context : array();
        $options = is_array( $options ) ? $options : array();
        $mode    = 'subagent' === (string) $mode ? 'subagent' : 'main';
        $scope   = isset( $context['subagent_scope'] )
            ? (string) $context['subagent_scope']
            : ( isset( $options['subagent_scope'] ) ? (string) $options['subagent_scope'] : 'general' );

        return md5(
            wp_json_encode(
                array(
                    'mode'                    => $mode,
                    'auth_mode'               => (string) $auth_mode,
                    'model'                   => (string) $model,
                    'user_role'               => isset( $context['user_role'] ) ? (string) $context['user_role'] : 'admin',
                    'subagent_depth'          => isset( $context['subagent_depth'] ) ? intval( $context['subagent_depth'] ) : 0,
                    'subagent_scope'          => $scope,
                    'allow_writes'            => ! empty( $options['allow_writes'] ),
                    'suppress_image_analysis' => ! empty( $context['suppress_image_analysis'] ),
                    'untrusted_inbound_email' => $this->is_untrusted_inbound_email_context( $context ),
                    'proc_open_available'     => ! method_exists( $this, 'is_proc_open_runtime_available' ) || $this->is_proc_open_runtime_available(),
                )
            )
        );
    }

    public function resolve_turn_runtime_policy( $context = array(), $mode = 'main', $options = array() ) {
        if ( isset( $options['turn_runtime_policy'] ) && is_array( $options['turn_runtime_policy'] ) ) {
            return $options['turn_runtime_policy'];
        }
        if ( isset( $context['turn_runtime_policy'] ) && is_array( $context['turn_runtime_policy'] ) ) {
            return $context['turn_runtime_policy'];
        }
        if ( null === $this->turn_runtime_policy && class_exists( 'Niwa_AI_Admin_Autonomy_Turn_Runtime_Policy' ) ) {
            $this->turn_runtime_policy = new Niwa_AI_Admin_Autonomy_Turn_Runtime_Policy();
        }
        if ( $this->turn_runtime_policy && method_exists( $this->turn_runtime_policy, 'resolve' ) ) {
            return (array) $this->turn_runtime_policy->resolve( $context, $mode, $options );
        }

        return array(
            'kind'                             => 'execute',
            'tool_surface'                     => 'execution',
            'tool_choice'                      => 'auto',
            'allow_tools'                      => true,
            'allow_tool_catalog'               => true,
            'allow_tool_load'                  => true,
            'allow_execution_follow_up'        => true,
            'enforce_execution_contract'       => true,
            'allow_soft_deadline_continuation' => true,
        );
    }

    private function get_runtime_core_tool_names( $context = array(), $mode = 'main', $options = array() ) {
        $context   = is_array( $context ) ? $context : array();
        $user_role = isset( $context['user_role'] ) ? (string) $context['user_role'] : 'admin';
        $proc_open_available = ! method_exists( $this, 'is_proc_open_runtime_available' ) || $this->is_proc_open_runtime_available();
        $runtime_policy = $this->resolve_turn_runtime_policy( $context, $mode, $options );

        if ( $this->is_untrusted_inbound_email_context( $context ) ) {
            return $this->get_untrusted_inbound_email_allowed_tool_names();
        }

        if ( 'stock_manager' === $user_role && 'main' === (string) $mode ) {
            return array(
                'tool_search',
                'tool_load',
                'tool_unload',
                'tool_active_list',
                'memory_recall_all',
                'memory_search',
                'memory_get',
                'session_note_add',
            );
        }
        if ( 'lead' === $user_role && 'main' === (string) $mode ) {
            return array(
                'tool_search',
                'tool_load',
                'tool_unload',
                'tool_active_list',
                'memory_search',
                'memory_get',
                'site_discovery_get',
                'woo_product_lookup',
                'woo_variation_lookup',
            );
        }
        if ( 'admin' === $user_role && 'main' === (string) $mode ) {
            $names = array(
                'task_checkpoint',
                'tool_search',
                'tool_load',
                'tool_unload',
                'tool_active_list',
                'memory_recall_all',
                'session_recall',
                'woo_product_lookup',
                'extension_request_create',
                'extension_approval_record',
                'skill_search',
                'skill_list',
                'skill_get',
                'external_skill_search',
                'external_skill_get',
            );
            if ( method_exists( $this, 'is_founder_onboarding_complete' ) && ! $this->is_founder_onboarding_complete() ) {
                $names[] = 'founder_identity_save';
            }
            if ( $this->is_scheduled_self_wake_runtime_context( $context ) ) {
                $names = array_merge( $names, $this->get_scheduled_self_wake_core_tool_names( $context, $proc_open_available ) );
            }
            return array_values( array_unique( $names ) );
        }

        $core_names = array(
            'tool_search',
            'tool_load',
            'tool_unload',
            'tool_active_list',
            'memory_recall_all',
            'memory_search',
            'memory_get',
            'agent_memory_search',
            'agent_memory_entry_reader',
            'companion_memory_search',
            'companion_memory_recall',
            'companion_memory_review',
            'session_recall',
            'session_note_add',
            'founder_identity_save',
            'companion_memory_capture',
            'code_scan_run',
            'code_search',
            'file_read',
            'file_list',
            'batch_read',
            'main_coding_edit_dry_run',
            'main_coding_shell_run',
            'main_coding_file_edit',
            'main_coding_workspace_prepare',
            'main_coding_workspace_apply',
            'main_coding_workspace_rollback',
            'main_coding_git_diff',
            'main_coding_lint_check',
            'main_coding_test_run',
            'extension_request_create',
            'extension_approval_record',
            'skill_search',
            'skill_list',
            'skill_get',
            'external_skill_search',
            'external_skill_get',
        );

        if ( ! $proc_open_available ) {
            $blocked_process_tools = array_fill_keys( $this->get_proc_open_required_tool_names(), true );
            $core_names = array_values(
                array_filter(
                    $core_names,
                    static function ( $name ) use ( $blocked_process_tools ) {
                        return ! isset( $blocked_process_tools[ (string) $name ] );
                    }
                )
            );
        }
		if ( $this->is_scheduled_self_wake_runtime_context( $context ) ) {
			$core_names = array_merge( $core_names, $this->get_scheduled_self_wake_core_tool_names( $context, $proc_open_available ) );
		}

        return array_values( array_unique( $core_names ) );
    }

    private function is_untrusted_inbound_email_context( $context ) {
        $context = is_array( $context ) ? $context : array();
        $source  = isset( $context['source'] ) ? sanitize_key( (string) $context['source'] ) : '';

        return ! empty( $context['untrusted_external_input'] )
            || ( ! empty( $context['inbound_email'] ) && is_array( $context['inbound_email'] ) )
            || 'imap_idle_listener' === $source;
    }

    private function get_untrusted_inbound_email_allowed_tool_names() {
        return array( 'email_message_read' );
    }

    private function is_scheduled_self_wake_runtime_context( $context ) {
        $context = is_array( $context ) ? $context : array();
        $source  = isset( $context['source'] ) ? sanitize_key( (string) $context['source'] ) : '';

        return ! empty( $context['scheduled_self_wake_execution'] ) || 'scheduled_self_wake' === $source;
    }

    private function get_scheduled_self_wake_core_tool_names( $context, $proc_open_available = true ) {
        $context = is_array( $context ) ? $context : array();
        $intent  = isset( $context['scheduled_job_intent'] ) ? sanitize_key( (string) $context['scheduled_job_intent'] ) : 'general';

        $names = array(
            'admin_delivery_targets_get',
            'send_admin_delivery_message',
        );

        if ( 'blog_work' === $intent ) {
            $names = array_merge(
                $names,
                array(
                    'blog_posts_list',
                    'blog_post_get',
                    'wp_search_posts',
                    'publish_blog_post',
                    'update_blog_post',
                    'create_niwa_blog',
                    'create_blog_post',
                )
            );
        }

        if ( 'script_or_code_work' === $intent ) {
            $names = array_merge(
                $names,
                $proc_open_available
                    ? array( 'code_search', 'file_read', 'file_list', 'batch_read', 'main_coding_shell_run', 'main_coding_edit_dry_run', 'main_coding_workspace_prepare', 'main_coding_file_edit', 'main_coding_git_diff', 'main_coding_lint_check', 'main_coding_test_run', 'main_coding_workspace_apply', 'main_coding_workspace_rollback' )
                    : array( 'code_search', 'file_read', 'file_list', 'batch_read', 'main_coding_edit_dry_run', 'main_coding_workspace_prepare', 'main_coding_file_edit', 'main_coding_git_diff', 'main_coding_lint_check', 'main_coding_workspace_apply', 'main_coding_workspace_rollback' )
            );
        }

        return array_values( array_unique( $names ) );
    }

    private function get_runtime_blocked_tool_names( $context = array(), $mode = 'main', $options = array() ) {
        if ( ! $this->is_scheduled_self_wake_runtime_context( $context ) ) {
            return array();
        }

        $blocked = array(
            'update_heartbeat_config',
            'update_operative_heartbeat_config',
            'extension_request_create',
            'extension_approval_record',
            'schedule_reminder',
            'schedule_admin_plan',
            'schedule_self_wake',
            'schedule_orders_export_report',
			'scheduled_tasks_cancel',
        );

        return array_values( array_unique( $blocked ) );
    }

    private function get_runtime_hydrated_tools( $all_tools, $context = array(), $mode = 'main', $options = array() ) {
        $all_tools            = is_array( $all_tools ) ? $all_tools : array();
        $core_names           = $this->get_runtime_core_tool_names( $context, $mode, $options );
        $session_active_names = $this->get_session_active_tool_names_for_runtime( $context, $options );
        $selected_names       = array_fill_keys( $core_names, true );

        foreach ( $session_active_names as $name ) {
            $selected_names[ $name ] = true;
        }
        foreach ( $this->get_runtime_blocked_tool_names( $context, $mode, $options ) as $blocked_name ) {
            unset( $selected_names[ $blocked_name ] );
        }

        $hydrated   = array();
        $seen_names = array();
        foreach ( $all_tools as $tool ) {
            if ( empty( $tool['function']['name'] ) ) {
                $hydrated[] = $tool;
                continue;
            }

            $name = (string) $tool['function']['name'];
            if ( isset( $selected_names[ $name ] ) ) {
                if ( isset( $seen_names[ $name ] ) ) {
                    continue;
                }
                $seen_names[ $name ] = true;
                $hydrated[] = $tool;
            }
        }

        return array_values( $hydrated );
    }

    private function limit_runtime_history_window( $history, $mode = 'main' ) {
        $history = is_array( $history ) ? array_values( $history ) : array();
        if ( empty( $history ) ) {
            return array();
        }

        $mode     = 'subagent' === (string) $mode ? 'subagent' : 'main';
        $limit    = 'subagent' === $mode ? self::SUBAGENT_HISTORY_MESSAGE_LIMIT : self::MAIN_HISTORY_MESSAGE_LIMIT;
        $eligible = array_values(
            array_filter(
                $history,
                static function ( $item ) {
                    if ( ! is_array( $item ) || '' === trim( (string) ( $item['text'] ?? '' ) ) ) {
                        return false;
                    }
                    return in_array( strtolower( (string) ( $item['role'] ?? '' ) ), array( 'user', 'assistant', 'bot' ), true );
                }
            )
        );

        $groups = array();
        foreach ( $eligible as $item ) {
            $turn_id = trim( (string) ( $item['turn_id'] ?? '' ) );
            $role    = strtolower( (string) ( $item['role'] ?? '' ) );
            $last    = count( $groups ) - 1;

            if (
                $last < 0
                || ( '' !== $turn_id && $turn_id !== (string) $groups[ $last ]['turn_id'] )
                || ( '' === $turn_id && ( 'user' === $role || '' !== (string) $groups[ $last ]['turn_id'] ) )
            ) {
                $groups[] = array(
                    'turn_id'  => $turn_id,
                    'messages' => array(),
                );
                $last = count( $groups ) - 1;
            }

            $groups[ $last ]['messages'][] = $item;
        }

        $selected_groups = array();
        $selected_count  = 0;
        for ( $index = count( $groups ) - 1; $index >= 0; $index-- ) {
            array_unshift( $selected_groups, $groups[ $index ]['messages'] );
            $selected_count += count( $groups[ $index ]['messages'] );
            if ( $selected_count >= $limit ) {
                break;
            }
        }

        return empty( $selected_groups ) ? array() : array_merge( ...$selected_groups );
    }

    private function get_session_active_tool_names_for_runtime( $context = array(), $options = array() ) {
        $options    = is_array( $options ) ? $options : array();
        $session_id = isset( $context['session_id'] ) ? trim( (string) $context['session_id'] ) : '';
        $turn_id    = isset( $options['turn_id'] ) ? trim( (string) $options['turn_id'] ) : '';
        if ( '' === $turn_id && isset( $context['turn_id'] ) ) {
            $turn_id = trim( (string) $context['turn_id'] );
        }
        if ( '' === $session_id || '' === $turn_id || ! $this->session_state || ! method_exists( $this->session_state, 'get_turn_active_tool_names' ) ) {
            return array();
        }

        return array_values( (array) $this->session_state->get_turn_active_tool_names( $session_id, $turn_id ) );
    }

    private function build_tool_runtime_prompt_block( $all_tools, $selected_tools, $context = array(), $mode = 'main', $options = array() ) {
        $all_tools      = is_array( $all_tools ) ? $all_tools : array();
        $selected_tools = is_array( $selected_tools ) ? $selected_tools : array();
        $core_names     = $this->get_runtime_core_tool_names( $context, $mode, $options );
        $selected_names = array();
        $core_lines     = array();
        $lightweight    = array();
        $proc_open_available = ! method_exists( $this, 'is_proc_open_runtime_available' ) || $this->is_proc_open_runtime_available();

        foreach ( $selected_tools as $tool ) {
            $name = $this->extract_tool_name_from_definition( $tool );
            if ( '' !== $name ) {
                $selected_names[ $name ] = true;
            } elseif ( ! empty( $tool['type'] ) ) {
                $lightweight[] = '- ' . (string) $tool['type'] . ': native lightweight tool available in full this turn.';
            }
        }

        foreach ( $core_names as $core_name ) {
            foreach ( $selected_tools as $tool ) {
                if ( $this->extract_tool_name_from_definition( $tool ) !== $core_name ) {
                    continue;
                }
                $core_lines[] = '- ' . $core_name . ': ' . $this->truncate_tool_manifest_text(
                    isset( $tool['function']['description'] ) ? (string) $tool['function']['description'] : ''
                );
                break;
            }
        }

        $active_lines = array();
        foreach ( array_keys( $selected_names ) as $name ) {
            if ( in_array( $name, $core_names, true ) ) {
                continue;
            }
            $active_lines[] = '- ' . $name;
        }
        $lines   = array();
        $lines[] = '';
        $lines[] = '[TOOL RUNTIME]';
        if ( empty( $selected_tools ) ) {
            $lines[] = 'No tools are enabled for this turn. Do not write XML, JSON, or pseudo tool calls. Answer in normal prose only, using the information already available in this turn.';
            $lines[] = 'If the founder asks for runtime/tool status but no read-only introspection tool is enabled, say that runtime introspection was not available in this turn instead of pretending to inspect it.';
            $lines[] = '[/TOOL RUNTIME]';
            return "\n" . implode( "\n", $lines );
        }
        $lines[] = 'Full schemas are available only for core tools, tools loaded in this agent run, and lightweight native tools.';
        $lines[] = 'Call tool_load directly with exact registry tool names or exact capability route ids when you know the path. Use tool_search only when neither is known.';
        $lines[] = 'Do not tell the founder that execution is impossible just because the concrete tool is not active yet. Load the exact tool or route when known; otherwise discover it with tool_search, then continue.';
        $lines[] = 'When you finish a task or no longer need a tool family in this run, call tool_unload so the runtime stays lean.';
        if ( ! $proc_open_available ) {
            $lines[] = 'PROCESS RUNTIME: proc_open/proc_get_status/proc_close are unavailable on this server, so shell/process tools are intentionally omitted. Use the no-shell code path: code_search, file_read, file_list, batch_read, site_discovery_get, db_schema_get, wp_* read tools, then main_coding_workspace_prepare, main_coding_file_edit, main_coding_git_diff, main_coding_lint_check, and main_coding_workspace_apply. Do not write pseudo tool calls or claim you lack all tools.';
        }
        $route_lines = $this->build_runtime_capability_route_lines( $proc_open_available );
        if ( ! empty( $route_lines ) ) {
            $lines[] = '[CAPABILITY ROUTES]';
            $lines   = array_merge( $lines, $route_lines );
            $lines[] = '[/CAPABILITY ROUTES]';
        }
        if ( ! empty( $core_lines ) ) {
            $lines[] = '[CORE TOOLS]';
            $lines   = array_merge( $lines, $core_lines );
            $lines[] = '[/CORE TOOLS]';
        }
        if ( ! empty( $lightweight ) ) {
            $lines[] = '[LIGHTWEIGHT TOOLS]';
            $lines   = array_merge( $lines, $lightweight );
            $lines[] = '[/LIGHTWEIGHT TOOLS]';
        }
        $lines[] = '[ACTIVE TOOLS]';
        if ( empty( $active_lines ) ) {
            $lines[] = '- none';
        } else {
            $lines = array_merge( $lines, $active_lines );
        }
        $lines[] = '[/ACTIVE TOOLS]';
        $lines[] = '[/TOOL RUNTIME]';

        return "\n" . implode( "\n", $lines );
    }

    private function build_tool_manifest_line( $tool, $is_active = false ) {
        $name = $this->extract_tool_name_from_definition( $tool );
        if ( '' === $name ) {
            $name = ! empty( $tool['type'] ) ? (string) $tool['type'] : 'unknown_tool';
        }

        $description = isset( $tool['function']['description'] ) ? (string) $tool['function']['description'] : '';
        if ( '' === $description && ! empty( $tool['type'] ) && 'function' !== (string) $tool['type'] ) {
            $description = 'Native runtime tool.';
        }

        $params = isset( $tool['function']['parameters']['properties'] ) && is_array( $tool['function']['parameters']['properties'] )
            ? array_slice( array_keys( $tool['function']['parameters']['properties'] ), 0, 4 )
            : array();
        $family = $this->detect_tool_family( $name );
        $line   = '- ' . $name . ' [' . $family . ']: ' . $this->truncate_tool_manifest_text( $description );
        if ( ! empty( $params ) ) {
            $line .= ' params=' . implode( ',', $params );
        }
        if ( $is_active ) {
            $line .= ' (active)';
        }

        return $line;
    }

    private function build_runtime_capability_route_lines( $proc_open_available ) {
        if ( ! method_exists( $this, 'get_runtime_capability_routes' ) ) {
            return array();
        }

        $priority = array_fill_keys(
            array(
                'order_attribution_inspection',
                'simple_product_sale_window',
                'variation_sale_window',
                'future_admin_mutation',
                'future_email_delivery',
                'wordpress_plugin_updates',
                'wordpress_plugin_activation',
                'wordpress_debug_log_maintenance',
                'developer_code_or_file_work',
                'approved_runtime_self_extension',
            ),
            true
        );
        $lines = array();
        foreach ( $this->get_runtime_capability_routes() as $route ) {
            if ( ! is_array( $route ) || empty( $route['capability'] ) || empty( $route['load'] ) || ! isset( $priority[ (string) $route['capability'] ] ) ) {
                continue;
            }
            $load = array_values( array_filter( array_map( 'sanitize_key', (array) $route['load'] ) ) );
            if ( empty( $load ) ) {
                continue;
            }
            $line = '- ' . sanitize_key( (string) $route['capability'] ) . ': tool_load ' . implode( ' + ', $load );
            if ( ! empty( $route['note'] ) ) {
                $line .= '; ' . $this->truncate_tool_manifest_text( (string) $route['note'], 120 );
            }
            $lines[] = $line;
        }

        return $lines;
    }

    private function detect_tool_family( $name ) {
        $name = (string) $name;
        if ( preg_match( '/^(woo_|product_|variation_|inventory_|bulk_)/', $name ) ) {
            return 'catalog';
        }
        if ( preg_match( '/^(blog_|create_niwa_blog|create_blog_post|update_blog_post|publish_blog_post)/', $name ) ) {
            return 'blog';
        }
        if ( preg_match( '/^(code_|file_|replace_text_in_file|elementor_|wp_)/', $name ) ) {
            return 'site-code';
        }
        if ( preg_match( '/^(session_|sessions_|subagent_|subagents_)/', $name ) ) {
            return 'session';
        }
        if ( preg_match( '/^(email_|send_email_|send_followup_)/', $name ) ) {
            return 'email';
        }
        if ( preg_match( '/^(skill_)/', $name ) ) {
            return 'skills';
        }
        if ( preg_match( '/^(workspace_|founder_notes_get|memory_|companion_memory_)/', $name ) ) {
            return 'memory-workspace';
        }
        if ( preg_match( '/^(site_image_|image_|telegram_send_|upload_image_)/', $name ) ) {
            return 'media';
        }

        return 'general';
    }

    private function truncate_tool_manifest_text( $text, $limit = 140 ) {
        $text  = trim( preg_replace( '/\s+/', ' ', (string) $text ) );
        $limit = max( 48, intval( $limit ) );
        if ( '' === $text ) {
            return '';
        }

        if ( function_exists( 'mb_strlen' ) && mb_strlen( $text, 'UTF-8' ) <= $limit ) {
            return $text;
        }
        if ( ! function_exists( 'mb_strlen' ) && strlen( $text ) <= $limit ) {
            return $text;
        }

        return function_exists( 'mb_substr' )
            ? rtrim( mb_substr( $text, 0, $limit - 3, 'UTF-8' ) ) . '...'
            : rtrim( substr( $text, 0, $limit - 3 ) ) . '...';
    }

    public function sync_runtime_tools_after_tool_call( $call, $tool_payload, $agent_context, $context = array(), $mode = 'main', $options = array() ) {
        $call        = is_array( $call ) ? $call : array();
        $tool_name   = isset( $call['name'] ) ? sanitize_key( (string) $call['name'] ) : '';
        $context     = is_array( $context ) ? $context : array();
        $options     = is_array( $options ) ? $options : array();
        $session_id  = isset( $context['session_id'] ) ? trim( (string) $context['session_id'] ) : '';

        $turn_id = isset( $options['turn_id'] ) ? trim( (string) $options['turn_id'] ) : '';
        if ( '' !== $session_id && '' !== $turn_id && $this->session_state && method_exists( $this->session_state, 'touch_active_tools' ) ) {
            $this->session_state->touch_active_tools( $session_id, array( $tool_name ), null, $turn_id );
        }

        $needs_tools_refresh = in_array( $tool_name, array( 'tool_load', 'tool_unload', 'session_reset' ), true )
            || ( 'tool_search' === $tool_name && $this->tool_payload_has_auto_loaded_tools( $tool_payload ) );

        if ( $needs_tools_refresh ) {
            $all_tools = $this->get_runtime_tool_registry( $context, $mode, $options );
            $hydrated  = $this->get_runtime_hydrated_tools( $all_tools, $context, $mode, $options );
            if ( is_object( $agent_context ) && method_exists( $agent_context, 'set_tools' ) ) {
                $agent_context->set_tools( $hydrated );
            }
        }

        if ( 'main_coding_workspace_apply' === $tool_name ) {
            $payload   = ! empty( $tool_payload['content'] ) && is_string( $tool_payload['content'] ) ? json_decode( $tool_payload['content'], true ) : array();
            $succeeded = is_array( $payload ) && ( ! empty( $payload['success'] ) || ! empty( $payload['ok'] ) );
            if ( $succeeded ) {
                Niwa_AI_External_Page_Cache::refresh_seraphinite_after_change( $tool_name );
            }
        }
    }

    private function tool_payload_has_auto_loaded_tools( $tool_payload ) {
        if ( ! is_array( $tool_payload ) || empty( $tool_payload['content'] ) || ! is_string( $tool_payload['content'] ) ) {
            return false;
        }

        $decoded = json_decode( (string) $tool_payload['content'], true );

        return is_array( $decoded ) && ! empty( $decoded['auto_loaded'] );
    }

    private function extract_tool_name_from_definition( $tool ) {
        if ( ! is_array( $tool ) ) {
            return '';
        }

        if ( ! empty( $tool['function']['name'] ) ) {
            return (string) $tool['function']['name'];
        }

        if ( ! empty( $tool['name'] ) ) {
            return (string) $tool['name'];
        }

        return '';
    }

	private function determine_turn_tool_choice( $context, $tools ) {
        $runtime_policy = $this->resolve_turn_runtime_policy( $context, 'main', array() );
		if ( empty( $tools ) || ! is_array( $tools ) ) {
			return 'none';
		}

		if ( ! empty( $context['is_subagent'] ) ) {
			return 'auto';
		}

        $force_action = ! empty( $context['force_action'] );

        if ( $force_action ) {
            return 'required';
        }

        return isset( $runtime_policy['tool_choice'] ) && in_array( $runtime_policy['tool_choice'], array( 'auto', 'required', 'none' ), true ) && 'none' !== $runtime_policy['tool_choice']
            ? (string) $runtime_policy['tool_choice']
            : 'auto';
    }

    public function capture_companion_memory_exchange( $session_id, $user_text, $reply, $context = array() ) {
        $session_result = array();
        if ( $this->session_memory_store && method_exists( $this->session_memory_store, 'record_exchange' ) ) {
            $session_result = (array) $this->session_memory_store->record_exchange( $session_id, $user_text, $reply, $context );
        }

        if ( ! empty( $context['skip_companion_memory_promotion'] ) ) {
            return array(
                'success'        => ! empty( $session_result['success'] ),
                'session_memory' => $session_result,
                'companion_skipped' => true,
            );
        }

        if ( ! $this->companion_memory_manager || ! method_exists( $this->companion_memory_manager, 'capture_exchange' ) ) {
            return array(
                'success'        => ! empty( $session_result['success'] ),
                'session_memory' => $session_result,
                'error'          => 'Companion memory manager nije dostupan.',
            );
        }

        $companion_result = (array) $this->companion_memory_manager->capture_exchange( $session_id, $user_text, $reply, $context );
        $companion_result['session_memory'] = $session_result;

        return $companion_result;
    }

    public function companion_distillation_tick() {
        if ( ! $this->companion_memory_manager || ! method_exists( $this->companion_memory_manager, 'companion_distillation_tick' ) ) {
            return array(
                'success' => true,
                'skipped' => true,
                'reason'  => 'companion_memory_manager_unavailable',
            );
        }

        return (array) $this->companion_memory_manager->companion_distillation_tick();
    }

}
