<?php
/**
 * Trait Niwa_AI_Chat_Code_Search
 *
 * Code-search, file-read, payload-builder helpers extracted from
 * Niwa_AI_Admin_Autonomy_Chat.
 *
 * @package suspended
 */

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

trait Niwa_AI_Chat_Code_Search {

    private function build_site_discovery_payload( $section, $query = '' ) {
        if ( ! $this->site_discovery || ! method_exists( $this->site_discovery, 'get_snapshot' ) ) {
            return array(
                'available' => false,
                'error'     => 'Site discovery nije ucitan.',
            );
        }

        $snapshot = $this->site_discovery->get_snapshot();
        $section  = trim( strtolower( (string) $section ) );
        $query    = trim( (string) $query );

        if ( 'plugins' === $section ) {
            return array(
                'available' => true,
                'plugins'   => array_values( isset( $snapshot['plugins'] ) ? $snapshot['plugins'] : array() ),
            );
        }

        if ( 'plugin' === $section ) {
            $plugin = $this->find_plugin_in_snapshot( $snapshot, $query );
            return array(
                'available' => true,
                'plugin'    => $plugin,
            );
        }

        if ( 'theme' === $section ) {
            return array(
                'available' => true,
                'theme'     => isset( $snapshot['theme'] ) ? $snapshot['theme'] : array(),
            );
        }

        if ( 'shortcodes' === $section ) {
            return array(
                'available'  => true,
                'shortcodes' => isset( $snapshot['wordpress']['shortcodes'] ) ? $snapshot['wordpress']['shortcodes'] : array(),
            );
        }

        if ( 'post_types' === $section ) {
            return array(
                'available'  => true,
                'post_types' => isset( $snapshot['content_types']['custom_post_types'] ) ? $snapshot['content_types']['custom_post_types'] : array(),
            );
        }

        if ( 'taxonomies' === $section ) {
            return array(
                'available'  => true,
                'taxonomies' => isset( $snapshot['content_types']['taxonomies'] ) ? $snapshot['content_types']['taxonomies'] : array(),
            );
        }

        if ( 'rest' === $section ) {
            return array(
                'available' => true,
                'rest'      => isset( $snapshot['wordpress']['rest_namespaces'] ) ? $snapshot['wordpress']['rest_namespaces'] : array(),
            );
        }

        if ( 'cron' === $section ) {
            return array(
                'available' => true,
                'cron'      => isset( $snapshot['wordpress']['cron_hooks'] ) ? $snapshot['wordpress']['cron_hooks'] : array(),
            );
        }

        return array(
            'available'     => true,
            'site'          => isset( $snapshot['site'] ) ? $snapshot['site'] : array(),
            'theme'         => isset( $snapshot['theme'] ) ? $snapshot['theme'] : array(),
            'plugin_count'  => isset( $snapshot['plugins'] ) ? count( $snapshot['plugins'] ) : 0,
            'plugins'       => array_slice( isset( $snapshot['plugins'] ) ? $snapshot['plugins'] : array(), 0, 12 ),
            'content_types' => isset( $snapshot['content_types'] ) ? $snapshot['content_types'] : array(),
            'wordpress'     => isset( $snapshot['wordpress'] ) ? $snapshot['wordpress'] : array(),
            'database'      => isset( $snapshot['database'] ) ? $snapshot['database'] : array(),
            'metrics_sources' => isset( $snapshot['metrics_sources'] ) ? $snapshot['metrics_sources'] : array(),
        );
    }

    private function build_db_schema_payload( $table, $sample_limit ) {
        if ( ! $this->db_intelligence ) {
            return array(
                'available' => false,
                'error'     => 'DB intelligence nije ucitan.',
            );
        }

        $site_snapshot = $this->site_discovery && method_exists( $this->site_discovery, 'get_snapshot' )
            ? (array) $this->site_discovery->get_snapshot()
            : array();
        $plugins = isset( $site_snapshot['plugins'] ) && is_array( $site_snapshot['plugins'] )
            ? $site_snapshot['plugins']
            : array();

        if ( '' !== trim( (string) $table ) ) {
            return array(
                'available' => true,
                'table'     => $this->db_intelligence->get_table_detail( (string) $table, $plugins, $sample_limit ),
            );
        }

        return array(
            'available' => true,
            'tables'    => $this->db_intelligence->get_table_index( $plugins ),
        );
    }

    private function build_code_intelligence_payload( $query, $limit ) {
        if ( ! $this->code_intelligence || ! method_exists( $this->code_intelligence, 'get_snapshot' ) ) {
            return array(
                'available' => false,
                'error'     => 'Code intelligence nije ucitan.',
            );
        }

        if ( '' !== trim( (string) $query ) && method_exists( $this->code_intelligence, 'find_touchpoints' ) ) {
            return array(
                'available'   => true,
                'touchpoints' => $this->code_intelligence->find_touchpoints( $query, $limit ),
            );
        }

        return array(
            'available' => true,
            'snapshot'  => $this->code_intelligence->get_snapshot(),
        );
    }

    private function export_orders_xlsx_payload( $args, $context ) {
        if ( ! $this->workspace || ! method_exists( $this->workspace, 'export_orders_xlsx' ) ) {
            return array(
                'success' => false,
                'error'   => 'Workspace nije ucitan.',
            );
        }

        $args            = is_array( $args ) ? $args : array();
        $period          = isset( $args['period'] ) ? trim( strtolower( (string) $args['period'] ) ) : 'yesterday';
        $send_to_founder = ! array_key_exists( 'send_to_founder', $args ) || ! empty( $args['send_to_founder'] );
        $start_local     = isset( $args['start_local'] ) ? trim( (string) $args['start_local'] ) : '';
        $end_local       = isset( $args['end_local'] ) ? trim( (string) $args['end_local'] ) : '';
        $label           = isset( $args['label'] ) ? trim( (string) $args['label'] ) : '';

        if ( ( 'custom' === $period || ( '' !== $start_local && '' !== $end_local ) ) && method_exists( $this->workspace, 'export_orders_xlsx_window' ) ) {
            $export = $this->workspace->export_orders_xlsx_window(
                $start_local,
                $end_local,
                '' !== $label ? $label : 'custom'
            );
        } elseif ( 'all_time' === $period && method_exists( $this->workspace, 'export_orders_xlsx_all_time' ) ) {
            $export = $this->workspace->export_orders_xlsx_all_time( '' !== $label ? $label : 'all-time' );
        } else {
            $export = $this->workspace->export_orders_xlsx( $period );
        }

        if ( empty( $export['success'] ) ) {
            return $export;
        }

        $chat_id = isset( $context['chat_id'] ) ? trim( (string) $context['chat_id'] ) : '';
        if ( $send_to_founder && '' !== $chat_id && $this->telegram && method_exists( $this->telegram, 'send_document' ) ) {
            $caption = 'XLSX export: ' . ( isset( $export['filename'] ) ? (string) $export['filename'] : 'sales-report.xlsx' );
            $document = '';
            if ( ! empty( $export['path'] ) && is_file( (string) $export['path'] ) ) {
                $document = (string) $export['path'];
            } elseif ( ! empty( $export['url'] ) ) {
                $document = (string) $export['url'];
            }
            $sent = $this->telegram->send_document( $chat_id, $document, $caption );
            $export['telegram_delivery'] = $sent;
        }

        return $export;
    }

    private function schedule_reminder_payload( $args, $context, $prompt ) {
        $scheduled_guard = $this->scheduled_runtime_job_creation_guard( $context );
        if ( ! empty( $scheduled_guard ) ) {
            return $scheduled_guard;
        }

        if ( ! $this->scheduler || ! method_exists( $this->scheduler, 'create_reminder_job' ) ) {
            return array(
                'success' => false,
                'error'   => 'Scheduler runtime nije ucitan.',
            );
        }

        $args['prompt_text'] = isset( $args['prompt_text'] ) ? (string) $args['prompt_text'] : (string) $prompt;
        $created             = $this->scheduler->create_reminder_job( $args, $context );

        if ( empty( $created['success'] ) ) {
            return $created;
        }

        $job = isset( $created['job'] ) ? $created['job'] : array();

        return array(
            'success' => true,
            'job'     => $job,
            'postconditions' => $this->build_scheduled_job_postconditions( $job, 'reminder' ),
        );
    }

    private function schedule_admin_plan_payload( $args, $context, $prompt ) {
        $scheduled_guard = $this->scheduled_runtime_job_creation_guard( $context );
        if ( ! empty( $scheduled_guard ) ) {
            return $scheduled_guard;
        }

        if ( ! $this->scheduler || ! method_exists( $this->scheduler, 'create_admin_plan_job' ) ) {
            return array(
                'success' => false,
                'error'   => 'Scheduler runtime nije ucitan.',
            );
        }

        $args['prompt_text'] = isset( $args['prompt_text'] ) ? (string) $args['prompt_text'] : (string) $prompt;
        $created             = $this->scheduler->create_admin_plan_job( $args, $context, (string) $prompt );

        if ( empty( $created['success'] ) ) {
            return $created;
        }

        $job = isset( $created['job'] ) ? $created['job'] : array();

        return array(
            'success' => true,
            'job'     => $job,
            'postconditions' => $this->build_scheduled_job_postconditions( $job, 'admin_plan' ),
        );
    }

    private function schedule_self_wake_payload( $args, $context, $prompt ) {
        $scheduled_guard = $this->scheduled_runtime_job_creation_guard( $context );
        if ( ! empty( $scheduled_guard ) ) {
            return $scheduled_guard;
        }

        if ( ! $this->scheduler || ! method_exists( $this->scheduler, 'create_self_wake_job' ) ) {
            return array(
                'success' => false,
                'error'   => 'Scheduler runtime nije ucitan.',
            );
        }

		$args['prompt_text'] = isset( $args['prompt_text'] ) ? (string) $args['prompt_text'] : (string) $prompt;
		$created             = $this->scheduler->create_self_wake_job( $args, $context, (string) $prompt );

        if ( empty( $created['success'] ) ) {
            return $created;
        }

        $job = isset( $created['job'] ) ? $created['job'] : array();

        return array(
            'success' => true,
            'job'     => $job,
            'postconditions' => $this->build_scheduled_job_postconditions( $job, 'self_wake' ),
        );
    }

    private function schedule_orders_export_payload( $args, $context, $prompt ) {
        $scheduled_guard = $this->scheduled_runtime_job_creation_guard( $context );
        if ( ! empty( $scheduled_guard ) ) {
            return $scheduled_guard;
        }

        if ( ! $this->scheduler || ! method_exists( $this->scheduler, 'create_orders_export_job' ) ) {
            return array(
                'success' => false,
                'error'   => 'Scheduler runtime nije ucitan.',
            );
        }

        $args['prompt_text'] = isset( $args['prompt_text'] ) ? (string) $args['prompt_text'] : (string) $prompt;
        $created             = $this->scheduler->create_orders_export_job( $args, $context );

        if ( empty( $created['success'] ) ) {
            return $created;
        }

        $job = isset( $created['job'] ) ? $created['job'] : array();

        return array(
            'success' => true,
            'job'     => $job,
            'postconditions' => $this->build_scheduled_job_postconditions( $job, 'orders_export' ),
        );
    }

    private function scheduled_runtime_job_creation_guard( $context ) {
        $context = is_array( $context ) ? $context : array();
        $source  = isset( $context['source'] ) ? sanitize_key( (string) $context['source'] ) : '';
        if ( empty( $context['scheduled_self_wake_execution'] ) && 'scheduled_self_wake' !== $source ) {
            return array();
        }

        return array(
            'success'              => true,
            'skipped'              => true,
            'duplicate_suppressed' => true,
            'reason'               => 'scheduled_runtime_cannot_create_jobs',
            'existing_job'         => isset( $context['scheduled_job'] ) && is_array( $context['scheduled_job'] ) ? $context['scheduled_job'] : array(),
            'next_required_step'   => 'continue_current_scheduled_job',
        );
    }

    private function build_scheduled_job_postconditions( $job, $job_type = '' ) {
        $job      = is_array( $job ) ? $job : array();
        $job_type = sanitize_key( (string) $job_type );
        $prefix   = '' !== $job_type ? 'scheduled_' . $job_type : 'scheduled_job';

        return array(
            array(
                'id'    => $prefix . '_stored',
                'ok'    => ! empty( $job['id'] ),
                'label' => 'Scheduled job must be stored with a database id.',
            ),
            array(
                'id'    => $prefix . '_active',
                'ok'    => isset( $job['status'] ) && 'active' === (string) $job['status'],
                'label' => 'Scheduled job must verify as active after creation.',
            ),
            array(
                'id'    => $prefix . '_next_run_set',
                'ok'    => ! empty( $job['next_run_local'] ),
                'label' => 'Scheduled job must have a concrete next run time.',
            ),
        );
    }

    private function scheduled_tasks_list_payload( $args, $context ) {
		$scheduler_available = $this->scheduler && method_exists( $this->scheduler, 'list_jobs' );
		$user_role   = sanitize_key( (string) ( $context['user_role'] ?? '' ) );
		$is_admin    = 'admin' === $user_role;
		$chat_filter = $is_admin ? '' : ( isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '' );
		$include_prompt = ! empty( $args['include_prompt_text'] );
		$requested_status = sanitize_key( (string) ( $args['status'] ?? 'active' ) );
		$main_tasks = array();

		if ( $is_admin && method_exists( $this, 'get_heartbeat_config' ) ) {
			$config = (array) $this->get_heartbeat_config();
			$heartbeat_record = $include_prompt && $this->workspace && method_exists( $this->workspace, 'read_bootstrap_file' )
				? (array) $this->workspace->read_bootstrap_file( 'HEARTBEAT.md' )
				: array();
			$main_tasks[] = array(
				'id'              => 'heartbeat:workspace',
				'task_type'       => 'workspace_heartbeat',
				'job_type'        => 'workspace_heartbeat',
				'execution_lane'  => 'main_agent',
				'status'          => empty( $config['paused'] ) ? 'active' : 'disabled',
				'scheduled_hours' => array_values( (array) ( $config['scheduled_hours'] ?? array() ) ),
				'blog_per_day'    => intval( $config['blog_per_day'] ?? 0 ),
				'prompt_text'     => (string) ( $heartbeat_record['content'] ?? '' ),
			);
		}

		$operative_roles = $is_admin ? array( 'admin', 'stock_manager' ) : ( 'stock_manager' === $user_role ? array( 'stock_manager' ) : array() );
		if ( ! empty( $operative_roles ) && method_exists( $this, 'get_operative_heartbeat_config' ) ) {
			foreach ( $operative_roles as $heartbeat_role ) {
				$config = (array) $this->get_operative_heartbeat_config( $heartbeat_role, true );
				$main_tasks[] = array(
					'id'               => 'heartbeat:' . $heartbeat_role,
					'task_type'        => 'operative_heartbeat',
					'job_type'         => 'operative_heartbeat',
					'target_role'      => $heartbeat_role,
					'execution_lane'   => 'main_agent',
					'status'           => ! empty( $config['enabled'] ) ? 'active' : 'disabled',
					'interval_minutes' => floatval( $config['interval_minutes'] ?? 60 ),
					'scheduled_hours'  => array_values( (array) ( $config['scheduled_hours'] ?? array() ) ),
					'prompt_text'      => $include_prompt && method_exists( $this, 'get_operative_heartbeat_instructions' )
						? (string) $this->get_operative_heartbeat_instructions( $heartbeat_role )
						: '',
				);
			}
		}

		if ( $is_admin ) {
			$settings = get_option( Niwa_AI_Constants::OPT_AUTONOMY, array() );
			$settings = is_array( $settings ) ? $settings : array();
			$main_tasks[] = array(
				'id'             => 'heartbeat:daily_report',
				'task_type'      => 'daily_report',
				'job_type'       => 'daily_report',
				'execution_lane' => 'main_agent',
				'status'         => ! empty( $settings['enabled'] ) && ! empty( $settings['daily_report_enabled'] ) ? 'active' : 'disabled',
				'scheduled_hour' => intval( $settings['daily_report_hour'] ?? 8 ),
				'scheduled_minute' => intval( $settings['daily_report_minute'] ?? 30 ),
			);
		}
		if ( 'all' !== $requested_status ) {
			$main_tasks = array_values(
				array_filter(
					$main_tasks,
					function ( $task ) use ( $requested_status ) {
						return is_array( $task ) && $requested_status === (string) ( $task['status'] ?? '' );
					}
				)
			);
		}

		$scheduled_jobs = $scheduler_available
			? (array) $this->scheduler->list_jobs(
				$chat_filter,
				isset( $args['status'] ) ? (string) $args['status'] : 'active',
				isset( $args['limit'] ) ? intval( $args['limit'] ) : ( $is_admin ? 50 : 12 ),
				$include_prompt
			)
			: array();
		foreach ( $scheduled_jobs as $index => $scheduled_job ) {
			if ( is_array( $scheduled_job ) ) {
				$scheduled_jobs[ $index ]['task_type'] = 'scheduled_job';
			}
		}

		$subagent_jobs = array();
		$parent_session_id = (string) ( $context['session_id'] ?? '' );
		if ( $this->subagent_manager && method_exists( $this->subagent_manager, 'control' ) && ( $is_admin || '' !== $parent_session_id ) ) {
			$include_completed = in_array( $requested_status, array( 'all', 'completed', 'failed', 'cancelled' ), true );
			$subagent_result   = $this->subagent_manager->control(
				'list',
				array(
					'parent_session_id' => $is_admin ? '' : $parent_session_id,
					'limit'             => isset( $args['limit'] ) ? intval( $args['limit'] ) : ( $is_admin ? 50 : 12 ),
					'include_completed' => $include_completed,
				)
			);
			foreach ( (array) ( $subagent_result['jobs'] ?? array() ) as $subagent_job ) {
				if ( ! is_array( $subagent_job ) ) {
					continue;
				}
				$status = (string) ( $subagent_job['status'] ?? '' );
				if ( 'active' === $requested_status && ! in_array( $status, array( 'blocked', 'queued', 'dispatching', 'running' ), true ) ) {
					continue;
				}
				if ( ! in_array( $requested_status, array( 'active', 'all' ), true ) && $requested_status !== $status ) {
					continue;
				}
				$subagent_job['task_type']      = 'subagent_job';
				$subagent_job['execution_lane'] = 'isolated_subagent';
				$subagent_jobs[]                = $subagent_job;
			}
		}

		return array(
			'available'           => true,
			'scheduler_available' => $scheduler_available,
			'scheduled_jobs'      => $scheduled_jobs,
			'main_agent_tasks'    => $main_tasks,
			'subagent_jobs'       => $subagent_jobs,
		);
	}

    private function scheduled_tasks_cancel_payload( $args, $context ) {
		$task_type = sanitize_key( (string) ( $args['task_type'] ?? 'scheduled_job' ) );
		$task_id   = trim( (string) ( $args['job_id'] ?? '' ) );
		$user_role = sanitize_key( (string) ( $context['user_role'] ?? '' ) );
		$is_admin  = 'admin' === $user_role;
		if ( 'workspace_heartbeat' === $task_type ) {
			if ( ! $is_admin || 'heartbeat:workspace' !== $task_id || ! method_exists( $this, 'get_heartbeat_config' ) || ! method_exists( $this, 'save_heartbeat_config' ) ) {
				return array( 'success' => false, 'error' => 'workspace_heartbeat_cancel_not_allowed_or_invalid_id' );
			}
			$config           = (array) $this->get_heartbeat_config();
			$config['paused'] = true;
			$this->save_heartbeat_config( $config );
			$verified = (array) $this->get_heartbeat_config();
			return array( 'success' => ! empty( $verified['paused'] ), 'task_type' => $task_type, 'id' => $task_id, 'status' => ! empty( $verified['paused'] ) ? 'cancelled' : 'active' );
		}

		if ( 'operative_heartbeat' === $task_type ) {
			$heartbeat_roles = array(
				'heartbeat:admin'         => 'admin',
				'heartbeat:stock_manager' => 'stock_manager',
			);
			$target_role = $heartbeat_roles[ $task_id ] ?? '';
			if ( '' === $target_role || ( ! $is_admin && ! ( 'stock_manager' === $user_role && 'stock_manager' === $target_role ) ) || ! method_exists( $this, 'get_operative_heartbeat_config' ) || ! method_exists( $this, 'save_operative_heartbeat_config' ) ) {
				return array( 'success' => false, 'error' => 'operative_heartbeat_cancel_not_allowed_or_invalid_id' );
			}
			$config = (array) $this->get_operative_heartbeat_config( $target_role, true );
			$config['enabled'] = false;
			$this->save_operative_heartbeat_config( $config, $target_role );
			$verified = (array) $this->get_operative_heartbeat_config( $target_role, true );
			return array( 'success' => empty( $verified['enabled'] ), 'task_type' => $task_type, 'id' => $task_id, 'status' => empty( $verified['enabled'] ) ? 'cancelled' : 'active' );
		}

		if ( 'daily_report' === $task_type ) {
			if ( ! $is_admin || 'heartbeat:daily_report' !== $task_id ) {
				return array( 'success' => false, 'error' => 'daily_report_cancel_not_allowed_or_invalid_id' );
			}
			$settings = get_option( Niwa_AI_Constants::OPT_AUTONOMY, array() );
			$settings = is_array( $settings ) ? $settings : array();
			$settings['daily_report_enabled'] = 0;
			update_option( Niwa_AI_Constants::OPT_AUTONOMY, $settings, false );
			$verified = get_option( Niwa_AI_Constants::OPT_AUTONOMY, array() );
			return array( 'success' => empty( $verified['daily_report_enabled'] ), 'task_type' => $task_type, 'id' => $task_id, 'status' => empty( $verified['daily_report_enabled'] ) ? 'cancelled' : 'active' );
		}

		if ( 'subagent_job' === $task_type ) {
			if ( '' === $task_id || ! $this->subagent_manager || ! method_exists( $this->subagent_manager, 'control' ) ) {
				return array( 'success' => false, 'error' => 'subagent_cancel_unavailable_or_invalid_id' );
			}
			$status = (array) $this->subagent_manager->control( 'status', array( 'job_id' => $task_id ) );
			$job    = (array) ( $status['job'] ?? array() );
			$parent_session_id = (string) ( $context['session_id'] ?? '' );
			if ( empty( $status['success'] ) || ( ! $is_admin && ( '' === $parent_session_id || (string) ( $job['parent_session_id'] ?? '' ) !== $parent_session_id ) ) ) {
				return array( 'success' => false, 'error' => 'subagent_cancel_not_allowed_or_not_found' );
			}
			return $this->subagent_manager->control( 'kill', array( 'job_id' => $task_id ) );
		}

		if ( 'scheduled_job' !== $task_type ) {
			return array( 'success' => false, 'error' => 'unsupported_scheduled_task_type' );
		}
		if ( ! $this->scheduler || ! method_exists( $this->scheduler, 'cancel_job' ) ) {
			return array( 'success' => false, 'error' => 'Scheduler runtime nije ucitan.' );
		}

		$chat_filter = $is_admin ? '' : ( isset( $context['chat_id'] ) ? (string) $context['chat_id'] : '' );
		return $this->scheduler->cancel_job( $task_id, $chat_filter );
	}

    private function list_workspace_artifacts_payload( $subdir, $limit ) {
        if ( ! $this->workspace || ! method_exists( $this->workspace, 'list_artifacts' ) ) {
            return array(
                'available' => false,
                'error'     => 'Workspace nije ucitan.',
            );
        }

        return array(
            'available' => true,
            'artifacts' => $this->workspace->list_artifacts( $subdir, $limit ),
        );
    }

    private function email_contact_lookup_payload( $query, $limit ) {
        if ( ! $this->email_service || ! method_exists( $this->email_service, 'resolve_recipient' ) ) {
            return array(
                'success' => false,
                'error'   => 'Email runtime nije ucitan.',
            );
        }

        return $this->email_service->resolve_recipient( $query, '', $limit );
    }

    private function configure_imap_mailbox_from_existing_smtp_payload( $args ) {
        if ( ! $this->email_service || ! method_exists( $this->email_service, 'configure_imap_mailbox_from_existing_smtp' ) ) {
            return array(
                'success' => false,
                'error'   => 'Email runtime nije ucitan.',
            );
        }

        $args = is_array( $args ) ? $args : array();
        if ( ! empty( $args['mailbox_email'] ) || ! empty( $args['password'] ) ) {
            if ( ! method_exists( $this->email_service, 'configure_mailbox_settings' ) ) {
                return array(
                    'success' => false,
                    'error'   => 'Explicit mailbox configuration is not available in this runtime.',
                );
            }

            return $this->email_service->configure_mailbox_settings( $args );
        }

        return $this->email_service->configure_imap_mailbox_from_existing_smtp();
    }

    public function handle_inbound_email_poll_cron() {
        $this->run_inbound_email_imap_poll_to_queue(
            array(
                'source'        => 'cron',
                'limit'         => 10,
                'body_chars'    => 1200,
                'allow_network' => true,
            )
        );
    }

    private function ensure_inbound_email_cron_scheduled() {
        $hook     = 'niwa_ai_inbound_email_poll';
        $settings = $this->email_service && method_exists( $this->email_service, 'get_settings' )
            ? (array) $this->email_service->get_settings()
            : array();
        if ( empty( $settings ) && class_exists( 'Niwa_AI_Constants' ) ) {
            $stored_settings = get_option( Niwa_AI_Constants::OPT_AUTONOMY_EMAIL, array() );
            $settings = is_array( $stored_settings ) ? $stored_settings : array();
        }
        $interval = class_exists( 'Niwa_AI_Admin_Autonomy_Email' )
            ? Niwa_AI_Admin_Autonomy_Email::normalize_inbound_poll_interval_minutes( isset( $settings['inbound_poll_interval_minutes'] ) ? $settings['inbound_poll_interval_minutes'] : 15 )
            : 15;
        $schedule = class_exists( 'Niwa_AI_Admin_Autonomy_Email' )
            ? Niwa_AI_Admin_Autonomy_Email::get_inbound_poll_schedule_for_interval( $interval )
            : array( 'schedule' => 'niwa_ai_fifteen_minutes', 'seconds' => 900 );
        $schedule_slug = isset( $schedule['schedule'] ) ? (string) $schedule['schedule'] : 'niwa_ai_fifteen_minutes';
        $schedule_seconds = isset( $schedule['seconds'] ) ? max( 60, intval( $schedule['seconds'] ) ) : 900;
        $status = array(
            'hook'            => $hook,
            'scheduled'       => false,
            'next_run_utc'    => null,
            'attempted'       => false,
            'schedule'        => $schedule_slug,
            'interval_minutes' => $interval,
            'schedule_result' => null,
        );

        if ( ! function_exists( 'wp_next_scheduled' ) ) {
            $status['schedule_result'] = 'wp_cron_unavailable';
            return $status;
        }

        $next_run = wp_next_scheduled( $hook );
        if ( false !== $next_run && function_exists( 'wp_get_scheduled_event' ) && function_exists( 'wp_clear_scheduled_hook' ) ) {
            $event = wp_get_scheduled_event( $hook );
            if ( is_object( $event ) && isset( $event->schedule ) && (string) $event->schedule !== $schedule_slug ) {
                $status['attempted'] = true;
                if ( class_exists( 'Niwa_AI_Cron_Maintenance' ) ) {
                    $rescheduled = Niwa_AI_Cron_Maintenance::replace_single_recurring_event( $hook, $schedule_slug, min( 300, $schedule_seconds ) );
                    $next_run = wp_next_scheduled( $hook );
                    $status['schedule_result'] = $rescheduled
                        ? 'rescheduled_from_' . sanitize_key( (string) $event->schedule )
                        : 'reschedule_failed_from_' . sanitize_key( (string) $event->schedule );
                } else {
                    wp_clear_scheduled_hook( $hook );
                    $next_run = wp_next_scheduled( $hook );
                    $status['schedule_result'] = false === $next_run
                        ? 'rescheduled_from_' . sanitize_key( (string) $event->schedule )
                        : 'clear_failed_from_' . sanitize_key( (string) $event->schedule );
                }
            }
        }

        if ( false === $next_run && function_exists( 'wp_schedule_event' ) ) {
            $status['attempted']       = true;
            $scheduled                 = class_exists( 'Niwa_AI_Cron_Maintenance' )
                ? Niwa_AI_Cron_Maintenance::ensure_recurring_event( $hook, $schedule_slug, min( 300, $schedule_seconds ) )
                : wp_schedule_event( time() + min( 300, $schedule_seconds ), $schedule_slug, $hook );
            $status['schedule_result'] = false === $scheduled ? 'schedule_failed' : 'scheduled';
            $next_run                  = wp_next_scheduled( $hook );
        }

        $status['scheduled']    = false !== $next_run;
        $status['next_run_utc'] = false !== $next_run ? gmdate( 'c', (int) $next_run ) : null;
        return $status;
    }

    private function ensure_inbound_email_approval_queue_stub() {
        $queue = get_option( 'niwa_ai_inbound_email_pending_approval_queue', array() );
        if ( ! is_array( $queue ) ) {
            $queue = array();
        }

        $pending_count = 0;
        foreach ( $queue as $entry ) {
            if ( is_array( $entry ) && ( ! isset( $entry['status'] ) || 'pending' === $entry['status'] ) ) {
                $pending_count++;
            }
        }

        update_option( 'niwa_ai_inbound_email_pending_approval_queue', $queue, false );
        update_option( 'niwa_ai_inbound_email_pending_approval_count', $pending_count, false );
        return array(
            'available'                => true,
            'storage_option'           => 'niwa_ai_inbound_email_pending_approval_queue',
            'pending_count'            => $pending_count,
            'requires_founder_approval' => true,
            'send_capability'          => false,
            'network_calls'            => false,
        );
    }

    private function trim_inbound_email_queue_text( $value, $max_length = 500 ) {
        if ( is_array( $value ) || is_object( $value ) ) {
            $value = wp_json_encode( $value );
        }

        $text = wp_strip_all_tags( (string) $value );
        $text = preg_replace( '/\s+/', ' ', $text );
        $text = sanitize_text_field( trim( (string) $text ) );

        if ( $max_length > 0 && strlen( $text ) > $max_length ) {
            $text = substr( $text, 0, $max_length );
        }

        return $text;
    }

    private function get_inbound_email_candidate_dedupe_key( $candidate ) {
        $candidate = is_array( $candidate ) ? $candidate : array();
        $parts     = array();

        foreach ( array( 'message_id', 'uid', 'source_id' ) as $field ) {
            if ( isset( $candidate[ $field ] ) && '' !== trim( (string) $candidate[ $field ] ) ) {
                $parts[] = $field . ':' . strtolower( trim( (string) $candidate[ $field ] ) );
            }
        }

        if ( empty( $parts ) ) {
            $parts[] = 'from:' . strtolower( isset( $candidate['from_email'] ) ? trim( (string) $candidate['from_email'] ) : '' );
            $parts[] = 'subject:' . strtolower( isset( $candidate['subject'] ) ? trim( (string) $candidate['subject'] ) : '' );
            $parts[] = 'received:' . strtolower( isset( $candidate['received_at'] ) ? trim( (string) $candidate['received_at'] ) : '' );
        }

        return hash( 'sha256', 'phase4:' . implode( '|', $parts ) );
    }

    private function count_inbound_email_pending_approvals( $queue ) {
        $count = 0;
        foreach ( (array) $queue as $entry ) {
            if ( is_array( $entry ) && ( ! isset( $entry['status'] ) || 'pending' === $entry['status'] ) ) {
                $count++;
            }
        }

        return $count;
    }

    private function enqueue_inbound_email_approval_candidate( $candidate, $source = 'poll' ) {
        $this->ensure_inbound_email_approval_queue_stub();

        $candidate  = is_array( $candidate ) ? $candidate : array();
        $source     = sanitize_key( $source ? $source : 'poll' );
        $dedupe_key = $this->get_inbound_email_candidate_dedupe_key( $candidate );
        $queue      = get_option( 'niwa_ai_inbound_email_pending_approval_queue', array() );
        $queue      = is_array( $queue ) ? $queue : array();
        $seen       = get_option( 'niwa_ai_inbound_email_dedupe_seen', array() );
        $seen       = is_array( $seen ) ? $seen : array();

        foreach ( $queue as $entry ) {
            if ( is_array( $entry ) && isset( $entry['dedupe_key'] ) && hash_equals( (string) $entry['dedupe_key'], $dedupe_key ) ) {
                return array(
                    'enqueued'   => false,
                    'duplicate'  => true,
                    'dedupe_key' => $dedupe_key,
                    'reason'     => 'already_in_approval_queue',
                );
            }
        }

        if ( isset( $seen[ $dedupe_key ] ) ) {
            return array(
                'enqueued'   => false,
                'duplicate'  => true,
                'dedupe_key' => $dedupe_key,
                'reason'     => 'already_seen',
            );
        }

        $now        = gmdate( 'c' );
        $from_email = isset( $candidate['from_email'] ) ? sanitize_email( (string) $candidate['from_email'] ) : '';
        $entry      = array(
            'id'                           => 'inbound_' . substr( $dedupe_key, 0, 16 ),
            'status'                       => 'pending',
            'source'                       => $source,
            'created_at'                   => $now,
            'received_at'                  => isset( $candidate['received_at'] ) ? $this->trim_inbound_email_queue_text( $candidate['received_at'], 80 ) : $now,
            'dedupe_key'                   => $dedupe_key,
            'message_id'                   => isset( $candidate['message_id'] ) ? $this->trim_inbound_email_queue_text( $candidate['message_id'], 180 ) : '',
            'uid'                          => isset( $candidate['uid'] ) ? $this->trim_inbound_email_queue_text( $candidate['uid'], 80 ) : '',
            'from_email'                   => $from_email,
            'from_label'                   => isset( $candidate['from_label'] ) ? $this->trim_inbound_email_queue_text( $candidate['from_label'], 120 ) : '',
            'subject'                      => isset( $candidate['subject'] ) ? $this->trim_inbound_email_queue_text( $candidate['subject'], 180 ) : '(no subject)',
            'body_summary'                 => isset( $candidate['body'] ) ? $this->trim_inbound_email_queue_text( $candidate['body'], 700 ) : '',
            'suggested_reply'              => isset( $candidate['suggested_reply'] ) ? $this->trim_inbound_email_queue_text( $candidate['suggested_reply'], 900 ) : '',
            'requires_founder_approval'    => true,
            'can_send_without_approval'    => false,
            'network_activity_performed'   => false,
        );

        $queue[] = $entry;
        if ( count( $queue ) > 100 ) {
            $queue = array_values( array_slice( $queue, -100 ) );
        }

        $seen[ $dedupe_key ] = $now;
        if ( count( $seen ) > 1000 ) {
            $seen = array_slice( $seen, -1000, null, true );
        }

        update_option( 'niwa_ai_inbound_email_pending_approval_queue', $queue, false );
        update_option( 'niwa_ai_inbound_email_pending_approval_count', $this->count_inbound_email_pending_approvals( $queue ), false );
        update_option( 'niwa_ai_inbound_email_dedupe_seen', $seen, false );
        update_option( 'niwa_ai_inbound_email_dedupe_seen_count', count( $seen ), false );
        update_option( 'niwa_ai_inbound_email_poll_to_queue_bridge_available', true, false );
        update_option( 'niwa_ai_inbound_email_network_activity_performed', false, false );
        update_option( 'niwa_ai_inbound_email_sends_without_approval', false, false );

        return array(
            'enqueued'                 => true,
            'duplicate'                => false,
            'dedupe_key'               => $dedupe_key,
            'queue_id'                 => $entry['id'],
            'pending_approval_count'   => $this->count_inbound_email_pending_approvals( $queue ),
            'requires_founder_approval' => true,
            'can_send_without_approval' => false,
        );
    }

    private function get_inbound_email_phase4_dry_run_candidate( $args = array() ) {
        $args = is_array( $args ) ? $args : array();

        return array(
            'message_id'      => isset( $args['dry_run_message_id'] ) ? (string) $args['dry_run_message_id'] : '<niwa-phase4-dry-run@local>',
            'uid'             => isset( $args['dry_run_uid'] ) ? (string) $args['dry_run_uid'] : 'phase4-dry-run-uid',
            'from_email'      => isset( $args['dry_run_from_email'] ) ? (string) $args['dry_run_from_email'] : 'sender@example.com',
            'from_label'      => isset( $args['dry_run_from_label'] ) ? (string) $args['dry_run_from_label'] : 'Dry Run Sender',
            'subject'         => isset( $args['dry_run_subject'] ) ? (string) $args['dry_run_subject'] : 'Phase 4 inbound email dry run',
            'body'            => isset( $args['dry_run_body'] ) ? (string) $args['dry_run_body'] : 'Local dry-run inbound email candidate for approval queue bridge verification.',
            'suggested_reply' => '',
            'received_at'     => '2026-05-20T00:00:00+00:00',
        );
    }

    private function remove_inbound_email_phase4_dry_run_state() {
        $dry_run_candidate = $this->get_inbound_email_phase4_dry_run_candidate();
        $dry_run_key       = $this->get_inbound_email_candidate_dedupe_key( $dry_run_candidate );
        $queue             = get_option( 'niwa_ai_inbound_email_pending_approval_queue', array() );
        $queue             = is_array( $queue ) ? $queue : array();
        $filtered_queue    = array();

        foreach ( $queue as $entry ) {
            if ( is_array( $entry ) && isset( $entry['source'] ) && 'phase4_dry_run' === $entry['source'] ) {
                continue;
            }
            $filtered_queue[] = $entry;
        }

        $seen = get_option( 'niwa_ai_inbound_email_dedupe_seen', array() );
        $seen = is_array( $seen ) ? $seen : array();
        unset( $seen[ $dry_run_key ] );

        update_option( 'niwa_ai_inbound_email_pending_approval_queue', $filtered_queue, false );
        update_option( 'niwa_ai_inbound_email_pending_approval_count', $this->count_inbound_email_pending_approvals( $filtered_queue ), false );
        update_option( 'niwa_ai_inbound_email_dedupe_seen', $seen, false );
        update_option( 'niwa_ai_inbound_email_dedupe_seen_count', count( $seen ), false );
    }

    private function run_inbound_email_imap_poll_to_queue( $args = array() ) {
        $args          = is_array( $args ) ? $args : array();
        $source        = isset( $args['source'] ) ? sanitize_key( $args['source'] ) : 'manual';
        $items         = isset( $args['items'] ) && is_array( $args['items'] ) ? $args['items'] : array();
        $allow_network = ! empty( $args['allow_network'] );
        $now           = gmdate( 'c' );
        $results       = array();
        $enqueued      = 0;
        $duplicates    = 0;

        foreach ( $items as $item ) {
            if ( ! is_array( $item ) ) {
                continue;
            }

            $result    = $this->enqueue_inbound_email_approval_candidate( $item, $source );
            $results[] = $result;
            if ( ! empty( $result['enqueued'] ) ) {
                $enqueued++;
            } elseif ( ! empty( $result['duplicate'] ) ) {
                $duplicates++;
            }
        }

        $network_activity_performed = false;
        $skip_reason                 = null;
        if ( empty( $items ) ) {
            $skip_reason = $allow_network ? 'real_imap_fetch_not_enabled_in_phase4_bridge' : 'no_local_poll_items';
        }

        update_option( 'niwa_ai_inbound_email_last_poll_at', $now, false );
        update_option( 'niwa_ai_inbound_email_last_new_messages', $enqueued, false );
        update_option( 'niwa_ai_inbound_email_poll_to_queue_bridge_available', true, false );
        update_option( 'niwa_ai_inbound_email_network_activity_performed', $network_activity_performed, false );
        update_option( 'niwa_ai_inbound_email_sends_without_approval', false, false );

        $queue = get_option( 'niwa_ai_inbound_email_pending_approval_queue', array() );
        $queue = is_array( $queue ) ? $queue : array();

        return array(
            'success'                         => true,
            'phase'                           => 4,
            'status'                          => 'phase4_poll_to_queue_bridge_completed',
            'source'                          => $source,
            'poll_to_queue_bridge_available'  => true,
            'processed_local_item_count'      => count( $items ),
            'enqueued_count'                  => $enqueued,
            'duplicate_count'                 => $duplicates,
            'pending_approval_count'          => $this->count_inbound_email_pending_approvals( $queue ),
            'results'                         => $results,
            'skip_reason'                     => $skip_reason,
            'network_activity_performed'      => $network_activity_performed,
            'sends_without_approval'          => false,
            'approval_required_for_replies'   => true,
            'approval_queue_send_capability'  => false,
        );
    }

    private function run_inbound_email_dedupe_stub( $message_keys = array() ) {
        $approval_queue_stub = $this->ensure_inbound_email_approval_queue_stub();

        if ( ! is_array( $message_keys ) ) {
            $message_keys = array( $message_keys );
        }

        if ( empty( $message_keys ) ) {
            $stored_keys  = get_option( 'niwa_ai_inbound_email_mock_message_keys', array() );
            $message_keys = is_array( $stored_keys ) ? $stored_keys : array();
        }

        $seen = get_option( 'niwa_ai_inbound_email_dedupe_seen', array() );
        if ( ! is_array( $seen ) ) {
            $seen = array();
        }

        $normalized_keys = array();
        foreach ( $message_keys as $key ) {
            if ( is_array( $key ) || is_object( $key ) ) {
                continue;
            }

            $key = trim( (string) $key );
            if ( '' === $key ) {
                continue;
            }

            $normalized_keys[] = sanitize_text_field( $key );
        }

        $new_messages = 0;
        $now          = gmdate( 'c' );
        foreach ( array_values( array_unique( $normalized_keys ) ) as $key ) {
            $dedupe_key = hash( 'sha256', 'phase2:' . strtolower( $key ) );
            if ( isset( $seen[ $dedupe_key ] ) ) {
                continue;
            }

            $seen[ $dedupe_key ] = $now;
            $new_messages++;
        }

        if ( count( $seen ) > 500 ) {
            $seen = array_slice( $seen, -500, null, true );
        }

        update_option( 'niwa_ai_inbound_email_dedupe_seen', $seen, false );
        update_option( 'niwa_ai_inbound_email_dedupe_seen_count', count( $seen ), false );
        update_option( 'niwa_ai_inbound_email_last_poll_at', $now, false );
        update_option( 'niwa_ai_inbound_email_last_new_messages', $new_messages, false );
        update_option( 'niwa_ai_inbound_email_network_activity_performed', false, false );
        update_option( 'niwa_ai_inbound_email_sends_without_approval', false, false );

        return array(
            'success'                    => true,
            'phase'                      => 3,
            'status'                     => 'phase3_cron_and_approval_stub_poll_completed',
            'cron_hook'                  => 'niwa_ai_inbound_email_poll',
            'processed_local_key_count'  => count( $normalized_keys ),
            'new_messages'               => $new_messages,
            'dedupe_seen_count'          => count( $seen ),
            'last_poll_at'               => $now,
            'network_activity_performed' => false,
            'sends_without_approval'     => false,
            'approval_queue_stub_available' => ! empty( $approval_queue_stub['available'] ),
            'pending_approval_count'     => isset( $approval_queue_stub['pending_count'] ) ? max( 0, (int) $approval_queue_stub['pending_count'] ) : 0,
            'approval_queue_send_capability' => false,
        );
    }

    private function inbound_email_cron_status_payload( $args ) {
        $args = is_array( $args ) ? $args : array();

        if ( ! empty( $args['reset_stub_state'] ) ) {
            delete_option( 'niwa_ai_inbound_email_dedupe_seen' );
            delete_option( 'niwa_ai_inbound_email_dedupe_seen_count' );
            delete_option( 'niwa_ai_inbound_email_last_poll_at' );
            delete_option( 'niwa_ai_inbound_email_last_new_messages' );
            delete_option( 'niwa_ai_inbound_email_poll_to_queue_bridge_available' );
            delete_option( 'niwa_ai_inbound_email_network_activity_performed' );
            delete_option( 'niwa_ai_inbound_email_sends_without_approval' );
            $this->remove_inbound_email_phase4_dry_run_state();
        }

        $poll_result         = null;
        $phase4_poll_result  = null;
        if ( ! empty( $args['run_stub_poll'] ) ) {
            $poll_result = $this->run_inbound_email_dedupe_stub( isset( $args['mock_message_keys'] ) ? $args['mock_message_keys'] : array() );
        }
        if ( ! empty( $args['run_phase4_dry_run_enqueue'] ) || ! empty( $args['run_dry_run_enqueue'] ) ) {
            $phase4_poll_result = $this->run_inbound_email_imap_poll_to_queue(
                array(
                    'source'        => 'phase4_dry_run',
                    'allow_network' => false,
                    'items'         => array( $this->get_inbound_email_phase4_dry_run_candidate( $args ) ),
                )
            );
        }

        $approval_queue_stub = $this->ensure_inbound_email_approval_queue_stub();

        $settings = array();
        if ( $this->email_service && method_exists( $this->email_service, 'get_settings' ) ) {
            $settings = (array) $this->email_service->get_settings();
        } elseif ( defined( 'Niwa_AI_Constants::OPT_AUTONOMY_EMAIL' ) ) {
            $settings = (array) get_option( Niwa_AI_Constants::OPT_AUTONOMY_EMAIL, array() );
        }

        $hook                 = 'niwa_ai_inbound_email_poll';
        $next_run             = function_exists( 'wp_next_scheduled' ) ? wp_next_scheduled( $hook ) : false;
        $last_poll            = get_option( 'niwa_ai_inbound_email_last_poll_at', null );
        $seen                 = get_option( 'niwa_ai_inbound_email_dedupe_seen', array() );
        $dedupe_seen_count    = is_array( $seen ) ? count( $seen ) : max( 0, (int) get_option( 'niwa_ai_inbound_email_dedupe_seen_count', 0 ) );
        $cron_callback_active = function_exists( 'has_action' ) ? false !== has_action( $hook, array( $this, 'handle_inbound_email_poll_cron' ) ) : null;
        $poll_interval        = class_exists( 'Niwa_AI_Admin_Autonomy_Email' )
            ? Niwa_AI_Admin_Autonomy_Email::normalize_inbound_poll_interval_minutes( isset( $settings['inbound_poll_interval_minutes'] ) ? $settings['inbound_poll_interval_minutes'] : 15 )
            : 15;
        $poll_schedule        = class_exists( 'Niwa_AI_Admin_Autonomy_Email' )
            ? Niwa_AI_Admin_Autonomy_Email::get_inbound_poll_schedule_for_interval( $poll_interval )
            : array( 'schedule' => 'niwa_ai_fifteen_minutes' );

        return array(
            'success'                    => true,
            'capability'                 => 'inbound_email_notification_and_reply_approval_workflow',
            'phase'                      => 3,
            'status'                     => 'phase3_approval_queue_stub_available',
            'network_calls'              => false,
            'sends_without_approval'     => false,
            'workflow'                   => 'notify_read_draft_or_trash_then_require_founder_send_approval',
            'message_read_tool'          => 'email_message_read',
            'send_blocked_during_inbound_event' => true,
            'next_phase'                 => 'connect durable approval queue send execution after explicit founder approval',
            'imap_enabled'               => ! empty( $settings['imap_enabled'] ),
            'imap_host_configured'       => ! empty( $settings['imap_host'] ),
            'imap_username_configured'   => ! empty( $settings['imap_username'] ),
            'cron_hook'                  => $hook,
            'cron_callback_registered'   => $cron_callback_active,
            'cron_scheduled'             => false !== $next_run,
            'cron_next_run_utc'          => false !== $next_run ? gmdate( 'c', (int) $next_run ) : null,
            'cron_schedule'              => isset( $poll_schedule['schedule'] ) ? (string) $poll_schedule['schedule'] : 'niwa_ai_fifteen_minutes',
            'poll_interval_minutes'      => $poll_interval,
            'last_poll_at'               => is_string( $last_poll ) && '' !== trim( $last_poll ) ? sanitize_text_field( $last_poll ) : null,
            'last_poll_new_messages'     => max( 0, (int) get_option( 'niwa_ai_inbound_email_last_new_messages', 0 ) ),
            'dedupe_seen_count'          => max( 0, (int) $dedupe_seen_count ),
            'approval_queue_stub_available' => ! empty( $approval_queue_stub['available'] ),
            'approval_queue_storage_option' => isset( $approval_queue_stub['storage_option'] ) ? sanitize_key( $approval_queue_stub['storage_option'] ) : 'niwa_ai_inbound_email_pending_approval_queue',
            'approval_queue_requires_founder_approval' => true,
            'approval_queue_send_capability' => false,
            'pending_approval_count'     => isset( $approval_queue_stub['pending_count'] ) ? max( 0, (int) $approval_queue_stub['pending_count'] ) : 0,
            'network_activity_performed' => false,
            'poll_stub_result'           => $poll_result,
        );
    }

    private function email_inbox_read_payload( $args ) {
        if ( ! $this->email_service || ! method_exists( $this->email_service, 'read_inbox' ) ) {
            return array(
                'success' => false,
                'error'   => 'Email runtime nije ucitan.',
            );
        }

        return $this->email_service->read_inbox(
            isset( $args['limit'] ) ? intval( $args['limit'] ) : 5,
            ! array_key_exists( 'unread_only', $args ) || ! empty( $args['unread_only'] ),
            isset( $args['from_filter'] ) ? (string) $args['from_filter'] : '',
            isset( $args['subject_filter'] ) ? (string) $args['subject_filter'] : '',
            isset( $args['body_chars'] ) ? intval( $args['body_chars'] ) : 1200
        );
    }

    private function email_message_read_payload( $args ) {
        if ( ! $this->email_service || ! method_exists( $this->email_service, 'fetch_message_by_uid' ) ) {
            return array(
                'success' => false,
                'error'   => 'Email runtime nije ucitan.',
            );
        }

        $uid        = isset( $args['uid'] ) ? intval( $args['uid'] ) : 0;
        $body_chars = isset( $args['body_chars'] ) ? intval( $args['body_chars'] ) : 2000;

        return $this->email_service->fetch_message_by_uid(
            $uid,
            max( 300, min( 5000, $body_chars ) )
        );
    }

    private function email_followup_candidates_payload( $limit, $search_query, $days ) {
        if ( ! $this->email_service || ! method_exists( $this->email_service, 'get_followup_candidates' ) ) {
            return array(
                'success' => false,
                'error'   => 'Email runtime nije ucitan.',
            );
        }

        return $this->email_service->get_followup_candidates( $limit, $search_query, $days );
    }

	/**
	 * Read appointment and contact-level CRM context for the admin agent.
	 *
	 * @param array $args Tool arguments.
	 * @return array
	 */
	private function appointment_contact_lookup_payload( $args ) {
		if ( ! class_exists( 'Niwa_AI_Appointment_Service' ) ) {
			return array(
				'success' => false,
				'error'   => 'Scheduler runtime nije ucitan.',
			);
		}

		$args         = is_array( $args ) ? $args : array();
		$limit        = max( 1, min( 100, isset( $args['limit'] ) ? intval( $args['limit'] ) : 20 ) );
		$contact_id   = isset( $args['contact_id'] ) ? max( 0, intval( $args['contact_id'] ) ) : 0;
		$search_query = isset( $args['search_query'] ) ? sanitize_text_field( (string) $args['search_query'] ) : '';
		$date_from    = isset( $args['date_from'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', (string) $args['date_from'] )
			? (string) $args['date_from']
			: wp_date( 'Y-m-d', time() - ( 30 * DAY_IN_SECONDS ), wp_timezone() );
		$date_to      = isset( $args['date_to'] ) && preg_match( '/^\d{4}-\d{2}-\d{2}$/', (string) $args['date_to'] )
			? (string) $args['date_to']
			: wp_date( 'Y-m-d', time() + ( 366 * DAY_IN_SECONDS ), wp_timezone() );

		try {
			$service = new Niwa_AI_Appointment_Service();
			if ( $contact_id > 0 ) {
				$profile      = method_exists( $service, 'get_contact_profile' ) ? $service->get_contact_profile( $contact_id ) : array();
				$appointments = method_exists( $service, 'get_contact_appointments' ) ? $service->get_contact_appointments( $contact_id, $limit ) : array();
				return array(
					'success'      => true,
					'timezone'     => wp_timezone_string(),
					'contact'      => is_array( $profile ) ? $profile : array(),
					'appointments' => is_array( $appointments ) ? array_slice( array_values( $appointments ), 0, $limit ) : array(),
				);
			}

			$filters = array();
			if ( '' !== $search_query ) {
				$filters['search'] = $search_query;
			}
			$result = $service->get_appointments_for_range( $date_from, $date_to, $filters, $limit, 0 );
			$items  = is_array( $result ) && isset( $result['items'] ) && is_array( $result['items'] )
				? $result['items']
				: ( is_array( $result ) && isset( $result['appointments'] ) && is_array( $result['appointments'] ) ? $result['appointments'] : $result );
			$items  = is_array( $items ) ? array_slice( array_values( $items ), 0, $limit ) : array();

			$contacts = array();
			if ( '' !== $search_query && class_exists( 'Niwa_AI_Contact_Service' ) ) {
				$contact_service = new Niwa_AI_Contact_Service();
				$contact_rows    = $contact_service->search_contacts( $search_query, min( $limit, 20 ), 0 );
				foreach ( isset( $contact_rows['items'] ) && is_array( $contact_rows['items'] ) ? $contact_rows['items'] : array() as $contact ) {
					$id       = isset( $contact['id'] ) ? intval( $contact['id'] ) : 0;
					$profile  = $id > 0 && method_exists( $service, 'get_contact_profile' ) ? $service->get_contact_profile( $id ) : $contact;
					$contacts[] = is_array( $profile ) ? $profile : $contact;
				}
			}

			return array(
				'success'      => true,
				'timezone'     => wp_timezone_string(),
				'date_from'    => $date_from,
				'date_to'      => $date_to,
				'query'        => $search_query,
				'count'        => count( $items ),
				'appointments' => $items,
				'contacts'     => $contacts,
			);
		} catch ( \Throwable $e ) {
			return array(
				'success' => false,
				'error'   => 'Scheduler context nije dostupan.',
			);
		}
	}

    private function find_plugin_in_snapshot( $snapshot, $query ) {
        $query = strtolower( trim( (string) $query ) );
        foreach ( (array) ( isset( $snapshot['plugins'] ) ? $snapshot['plugins'] : array() ) as $plugin ) {
            $slug = strtolower( isset( $plugin['slug'] ) ? (string) $plugin['slug'] : '' );
            $name = strtolower( isset( $plugin['name'] ) ? (string) $plugin['name'] : '' );
            if ( '' === $query || false !== strpos( $slug, $query ) || false !== strpos( $name, $query ) ) {
                return $plugin;
            }
        }

        return array();
    }

    private function search_codebase( $query, $scope, $limit ) {
        $query = trim( (string) $query );
        if ( '' === $query ) {
            return array();
        }

        $terms        = $this->normalize_search_terms( $query );
        $limit        = max( 1, min( 12, intval( $limit ) ) );
        $allowed_roots = $this->get_code_search_roots( $scope );
        $results      = array();
        $scanned      = 0;
        $max_files    = in_array( $scope, array( 'wordpress_env', 'all', 'site_code' ), true ) ? 7000 : 3000;

        foreach ( $allowed_roots as $root ) {
            if ( $scanned >= $max_files ) {
                break;
            }

            if ( ! is_dir( $root ) ) {
                continue;
            }

            try {
                $iterator = new RecursiveIteratorIterator(
                    new RecursiveCallbackFilterIterator(
                        new RecursiveDirectoryIterator( $root, FilesystemIterator::SKIP_DOTS ),
                        function ( $current ) {
                            $name = $current->getFilename();
                            if ( $current->isDir() ) {
                                return ! in_array( $name, array( '.git', 'node_modules', 'vendor', 'uploads', 'cache', 'upgrade', 'backups', 'ai1wm-backups', 'logs' ), true );
                            }

                            if ( preg_match( '/\.min\.(js|css)$/i', $name ) ) {
                                return false;
                            }

                            return preg_match( '/\.(php|js|ts|tsx|jsx|css|scss|json|html|txt|md|yml|yaml|xml)$/i', $name );
                        }
                    ),
                    RecursiveIteratorIterator::LEAVES_ONLY
                );
            } catch ( Throwable $e ) {
                continue;
            }

            foreach ( $iterator as $file_info ) {
                if ( $scanned >= $max_files || count( $results ) >= ( $limit * 4 ) ) {
                    break;
                }

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

                $scanned++;
                $path  = $file_info->getPathname();
                $match = $this->search_file_for_terms( $path, $terms );
                if ( empty( $match ) ) {
                    continue;
                }

                $results[] = $match;
            }
        }

        usort(
            $results,
            function ( $a, $b ) {
                return intval( isset( $b['score'] ) ? $b['score'] : 0 ) <=> intval( isset( $a['score'] ) ? $a['score'] : 0 );
            }
        );

        return array_slice( $results, 0, $limit );
    }

    private function search_file_for_terms( $path, $terms ) {
        $terms = array_values( array_filter( (array) $terms ) );
        if ( empty( $terms ) ) {
            return array();
        }

        $handle = @fopen( $path, 'r' );
        if ( ! $handle ) {
            return array();
        }

        $best = array();
        $line_number = 0;
        while ( false !== ( $line = fgets( $handle ) ) ) {
            $line_number++;
            $line_lc = strtolower( (string) $line );
            $score   = 0;
            foreach ( $terms as $term ) {
                if ( false !== strpos( $line_lc, $term ) ) {
                    $score++;
                }
            }

            if ( $score <= 0 ) {
                continue;
            }

            $relative_path = $this->to_relative_code_path( $path );
            $safe_line     = $this->redact_sensitive_file_line( (string) $line, $relative_path );
            $best = array(
                'path'    => $this->to_relative_code_path( $path ),
                'line'    => $line_number,
                'excerpt' => trim( $this->truncate_block( preg_replace( '/\s+/', ' ', $safe_line ), 220 ) ),
                'score'   => $score,
            );
            if ( $score >= count( $terms ) ) {
                break;
            }
        }

        fclose( $handle );
        if ( ! empty( $best ) ) {
            $best['snippet']  = $this->build_code_snippet( $path, intval( $best['line'] ), 2, 1 );
            $best['editable'] = $this->is_editable_code_path( $path );
            $best['edit_mode'] = $best['editable'] ? 'file_edit_patch' : 'read_only';
        }

        return $best;
    }

    private function read_code_file( $path, $start_line, $limit ) {
        $resolved = $this->resolve_code_path( $path );
        if ( empty( $resolved['ok'] ) ) {
            return $resolved;
        }

        $file_lines = @file( $resolved['path'], FILE_IGNORE_NEW_LINES );
        if ( false === $file_lines || ! is_array( $file_lines ) ) {
            return array(
                'ok'    => false,
                'error' => 'Unable to read file.',
            );
        }

        $start_line = max( 1, intval( $start_line ) );
        $limit      = max( 1, min( 200, intval( $limit ) ) );
        $slice         = array_slice( $file_lines, $start_line - 1, $limit );
        $relative_path = $this->to_relative_code_path( $resolved['path'] );
        $safe_slice    = $this->redact_sensitive_file_lines( $slice, $relative_path );

        return array(
            'ok'         => true,
            'path'       => $relative_path,
            'start_line' => $start_line,
            'lines'      => $this->format_code_lines( $safe_slice, $start_line ),
            'snippet'    => $this->build_numbered_snippet_from_lines( $safe_slice, $start_line ),
            'editable'   => $this->is_editable_code_path( $resolved['path'] ),
            'edit_mode'  => $this->is_editable_code_path( $resolved['path'] ) ? 'file_edit_patch' : 'read_only',
        );
    }

    /* ================================================================
     * file_read — Full file read with File Guard denylist + boundary
     * ============================================================= */

    private function execute_file_read( $path, $start_line, $limit, $tail_lines = 0, $find_pattern = '', $context_lines = 40 ) {
        $path = trim( (string) $path );
        if ( '' === $path ) {
            return array( 'ok' => false, 'error' => 'path je obavezan.' );
        }
        $find_pattern = trim( (string) $find_pattern );
        $context_lines = max( 0, min( 250, intval( $context_lines ) ) );

        if ( ! class_exists( 'Niwa_AI_Admin_Autonomy_File_Guard' ) ) {
            $guard_file = dirname( __FILE__ ) . '/class-admin-autonomy-file-guard.php';
            if ( file_exists( $guard_file ) ) {
                require_once $guard_file;
            }
        }

        $guard    = class_exists( 'Niwa_AI_Admin_Autonomy_File_Guard' ) ? new Niwa_AI_Admin_Autonomy_File_Guard() : null;
        $max_read = $guard ? Niwa_AI_Admin_Autonomy_File_Guard::MAX_READ_BYTES : 2097152;

        if ( $guard ) {
            $resolved = $guard->validate_read( $path );
            if ( empty( $resolved['success'] ) ) {
                return array( 'ok' => false, 'error' => isset( $resolved['error'] ) ? (string) $resolved['error'] : 'Path validation failed.' );
            }
            $abs_path      = $resolved['path'];
            $relative_path = $resolved['relative_path'];
            $risk_level    = $resolved['risk_level'];
        } else {
            $legacy = $this->resolve_code_path( $path );
            if ( empty( $legacy['ok'] ) ) {
                return $legacy;
            }
            $abs_path      = $legacy['path'];
            $relative_path = $this->to_relative_code_path( $abs_path );
            $risk_level    = 'unknown';
        }

        $file_size = @filesize( $abs_path );
        $tail_lines = max( 0, min( 500, intval( $tail_lines ) ) );
        if ( $tail_lines > 0 ) {
            $tail = $this->read_tail_lines_from_file( $abs_path, $tail_lines );
            if ( empty( $tail['ok'] ) ) {
                return $tail;
            }

            $slice      = $this->redact_sensitive_file_lines( (array) $tail['lines'], $relative_path );
            $start_line = isset( $tail['start_line'] ) ? intval( $tail['start_line'] ) : 1;

            return array(
                'ok'             => true,
                'path'           => $relative_path,
                'start_line'     => $start_line,
                'total_lines'    => isset( $tail['total_lines'] ) ? intval( $tail['total_lines'] ) : count( $slice ),
                'lines_returned' => count( $slice ),
                'file_size'      => false !== $file_size ? $file_size : 0,
                'snippet'        => $this->build_numbered_snippet_from_lines( $slice, $start_line ),
                'editable'       => $this->is_editable_code_path( $abs_path ),
                'edit_mode'      => $this->is_editable_code_path( $abs_path ) ? 'file_edit_patch' : 'read_only',
                'risk_level'     => $risk_level,
                'has_more'       => false,
                'tail'           => true,
            );
        }

        if ( false !== $file_size && $file_size > $max_read ) {
            return array( 'ok' => false, 'error' => 'Fajl je prevelik za citanje (' . round( $file_size / 1024 ) . ' KB, limit ' . round( $max_read / 1024 ) . ' KB).', 'file_size' => $file_size );
        }

        $file_lines = @file( $abs_path, FILE_IGNORE_NEW_LINES );
        if ( false === $file_lines || ! is_array( $file_lines ) ) {
            return array( 'ok' => false, 'error' => 'Ne mogu da procitam fajl.' );
        }

        $total_lines = count( $file_lines );
        $start_line  = max( 1, intval( $start_line ) );
        $limit       = max( 1, min( 500, intval( $limit ) ) );

        $matched_line = 0;
        if ( '' !== $find_pattern ) {
            foreach ( $file_lines as $line_index => $line ) {
                if ( false !== strpos( (string) $line, $find_pattern ) ) {
                    $matched_line = intval( $line_index ) + 1;
                    break;
                }
            }
            if ( $matched_line < 1 ) {
                return array(
                    'ok'           => false,
                    'error'        => 'find_pattern was not found in the file.',
                    'path'         => $relative_path,
                    'find_pattern' => $find_pattern,
                    'total_lines'  => $total_lines,
                    'file_size'    => false !== $file_size ? $file_size : 0,
                    'risk_level'   => $risk_level,
                );
            }

            $start_line = max( 1, $matched_line - $context_lines );
            $limit      = min( 500, max( 1, ( $context_lines * 2 ) + 1 ) );
        }

        $slice       = array_slice( $file_lines, $start_line - 1, $limit );
        $safe_slice  = $this->redact_sensitive_file_lines( $slice, $relative_path );

        $payload = array(
            'ok'             => true,
            'path'           => $relative_path,
            'start_line'     => $start_line,
            'total_lines'    => $total_lines,
            'lines_returned' => count( $safe_slice ),
            'file_size'      => false !== $file_size ? $file_size : 0,
            'snippet'        => $this->build_numbered_snippet_from_lines( $safe_slice, $start_line ),
            'editable'       => $this->is_editable_code_path( $abs_path ),
            'edit_mode'      => $this->is_editable_code_path( $abs_path ) ? 'file_edit_patch' : 'read_only',
            'risk_level'     => $risk_level,
            'has_more'       => ( $start_line + $limit - 1 ) < $total_lines,
        );
        if ( '' !== $find_pattern ) {
            $payload['find_pattern']  = $find_pattern;
            $payload['matched_line']  = $matched_line;
            $payload['context_lines'] = $context_lines;
        }

        return $payload;
    }

    /* ================================================================
     * file_list — Directory listing with boundary check
     * ============================================================= */

    private function execute_file_list( $path, $pattern ) {
        $path = trim( (string) $path );
        if ( '' === $path ) {
            return array( 'ok' => false, 'error' => 'path je obavezan.' );
        }

        if ( ! defined( 'ABSPATH' ) ) {
            return array( 'ok' => false, 'error' => 'ABSPATH nije definisan.' );
        }

        $root        = wp_normalize_path( untrailingslashit( ABSPATH ) );
        $root_prefix = trailingslashit( $root );
        $normalized  = wp_normalize_path( str_replace( '\\', '/', $path ) );
        $candidate   = $normalized;

        if ( ! preg_match( '#^[A-Za-z]:/#', $candidate ) && 0 !== strpos( $candidate, '/' ) ) {
            $candidate = wp_normalize_path( $root_prefix . ltrim( $candidate, '/' ) );
        }

        $real = realpath( $candidate );
        if ( false === $real || ! is_dir( $real ) ) {
            return array( 'ok' => false, 'error' => 'Direktorijum ne postoji ili nije validan.' );
        }

        $real = wp_normalize_path( $real );
        if ( $real !== $root && 0 !== strpos( $real, $root_prefix ) ) {
            return array( 'ok' => false, 'error' => 'Direktorijum mora biti unutar WordPress instalacije.' );
        }

        $entries    = array();
        $max_items  = 100;
        $pattern    = trim( (string) $pattern );
        $dir_handle = @opendir( $real );
        if ( false === $dir_handle ) {
            return array( 'ok' => false, 'error' => 'Ne mogu da otvorim direktorijum.' );
        }

        while ( false !== ( $entry = readdir( $dir_handle ) ) && count( $entries ) < $max_items ) {
            if ( '.' === $entry || '..' === $entry ) {
                continue;
            }
            if ( '' !== $pattern && ! fnmatch( $pattern, $entry, FNM_CASEFOLD ) ) {
                continue;
            }

            $full_path = trailingslashit( $real ) . $entry;
            $is_dir    = is_dir( $full_path );
            $size      = $is_dir ? 0 : @filesize( $full_path );

            $entries[] = array(
                'name' => $entry . ( $is_dir ? '/' : '' ),
                'type' => $is_dir ? 'directory' : 'file',
                'size' => false !== $size ? $size : 0,
            );
        }

        closedir( $dir_handle );

        usort(
            $entries,
            function ( $a, $b ) {
                if ( $a['type'] !== $b['type'] ) {
                    return 'directory' === $a['type'] ? -1 : 1;
                }
                return strcasecmp( $a['name'], $b['name'] );
            }
        );

        $relative_dir = ltrim( str_replace( $root_prefix, '', trailingslashit( $real ) ), '/' );

        return array(
            'ok'      => true,
            'path'    => $relative_dir,
            'count'   => count( $entries ),
            'entries' => $entries,
        );
    }

    private function execute_code_scan_run( $args, $context, $mode = 'main', $options = array() ) {
        if ( ! $this->is_proc_open_runtime_available() ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'proc_open runtime is not available on this server.',
            );
        }

        $argv = isset( $args['argv'] ) && is_array( $args['argv'] ) ? array_values( $args['argv'] ) : array();
        $argv = array_values(
            array_filter(
                array_map(
                    static function ( $value ) {
                        return is_scalar( $value ) ? (string) $value : '';
                    },
                    $argv
                ),
                static function ( $value ) {
                    return '' !== $value;
                }
            )
        );

        if ( empty( $argv ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'argv is required. Pass a read-only command as an argument array, for example ["rg","-n","pattern","wp-content/plugins"]. Use the current Niwa plugin path from runtime context instead of assuming wp-content/plugins/niwa-ai.',
            );
        }

        $cwd_result = $this->resolve_code_shell_cwd( isset( $args['cwd'] ) ? (string) $args['cwd'] : '' );
        if ( empty( $cwd_result['ok'] ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => isset( $cwd_result['error'] ) ? (string) $cwd_result['error'] : 'Invalid cwd.',
            );
        }

        $validation = $this->validate_code_scan_argv( $argv, $cwd_result['path'] );
        if ( empty( $validation['ok'] ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => isset( $validation['error'] ) ? (string) $validation['error'] : 'Command is not allowed.',
                'policy'  => isset( $validation['policy'] ) ? (string) $validation['policy'] : 'code_scan_read_only_shell',
            );
        }

        $timeout = isset( $args['timeout_seconds'] ) ? intval( $args['timeout_seconds'] ) : 15;
        $timeout = max( 1, min( 60, $timeout ) );
        $limit   = isset( $args['max_output_chars'] ) ? intval( $args['max_output_chars'] ) : 8000;
        $limit   = max( 1000, min( 20000, $limit ) );

        $run = $this->run_code_shell_process( $argv, $cwd_result['path'], $timeout, $limit );
        $run['command']          = $argv;
        $run['cwd']              = $cwd_result['relative_path'];
        $run['timeout_seconds']  = $timeout;
        $run['max_output_chars'] = $limit;
        $run['policy']           = 'code_scan_read_only_shell';

        return $run;
    }

    private function validate_code_scan_argv( $argv, $cwd ) {
        $argv = is_array( $argv ) ? array_values( $argv ) : array();
        $cmd  = isset( $argv[0] ) ? strtolower( basename( (string) $argv[0] ) ) : '';
        $cmd  = preg_replace( '/(?:\.exe|\.bat|\.cmd)$/i', '', $cmd );

        foreach ( $argv as $arg ) {
            $arg = (string) $arg;
            if ( false !== strpos( $arg, "\0" ) || strlen( $arg ) > 2000 ) {
                return array(
                    'ok'     => false,
                    'error'  => 'Command argument is invalid.',
                    'policy' => 'argument_validation',
                );
            }
        }

        if ( ! $this->code_shell_args_stay_in_wordpress_root( $argv, $cwd ) ) {
            return array(
                'ok'     => false,
                'error'  => 'Command paths must stay inside the WordPress installation and cannot use parent-directory traversal.',
                'policy' => 'path_boundary',
            );
        }

        if ( in_array( $cmd, array( 'rg', 'grep', 'ls', 'pwd', 'wc', 'head', 'tail', 'cat', 'stat', 'du' ), true ) ) {
            return array( 'ok' => true );
        }

		if ( 'sed' === $cmd ) {
			$tail_args = array_slice( $argv, 1 );
			foreach ( $tail_args as $arg ) {
				if ( '-i' === $arg || 0 === strpos( (string) $arg, '--in-place' ) ) {
					return array(
						'ok'     => false,
                        'error'  => 'sed in-place editing is blocked in code_scan_run.',
                        'policy' => 'code_scan_read_only_shell',
					);
				}
			}
			$has_quiet   = false;
			$sed_script  = '';
			foreach ( $tail_args as $arg ) {
				if ( '-n' === $arg ) {
					$has_quiet = true;
					continue;
				}
				if ( '' === $sed_script && '' !== $arg && 0 !== strpos( (string) $arg, '-' ) ) {
					$sed_script = (string) $arg;
				}
			}
			if ( $has_quiet && '' !== $sed_script && preg_match( '/^\d+(,\d+)?p$/', $sed_script ) ) {
				return array(
					'ok'     => false,
					'error'  => 'sed -n N,Mp pattern is blocked for file viewing. Use file_read(path, start_line, limit) instead.',
					'policy' => 'tool_redirect_to_file_read',
				);
			}
			return array( 'ok' => true );
		}

        if ( 'find' === $cmd ) {
            foreach ( array_slice( $argv, 1 ) as $arg ) {
                if ( in_array( (string) $arg, array( '-delete', '-exec', '-execdir', '-ok', '-okdir' ), true ) ) {
                    return array(
                        'ok'     => false,
                        'error'  => 'find mutation/execution flags are blocked in code_scan_run.',
                        'policy' => 'code_scan_read_only_shell',
                    );
                }
            }
            return array( 'ok' => true );
        }

        if ( 'git' === $cmd ) {
            return $this->validate_code_shell_git_command( $argv );
        }

        return array(
            'ok'     => false,
            'error'  => 'code_scan_run is limited to read-only inspection commands such as rg, grep, find, ls, wc, head, tail, cat, stat, du, sed without -i, and read-only git inspection.',
            'policy' => 'code_scan_read_only_shell',
        );
    }


    private function execute_main_coding_shell_run( $args, $context, $mode = 'main', $options = array() ) {
        $args = is_array( $args ) ? $args : array();
        $audit_id = $this->new_main_coding_audit_id( 'shell' );
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            $audit_path = $this->append_main_coding_audit_event( 'shell_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'not_main_runtime',
            ) );
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'main_coding_shell_run is available only in the main founder runtime or a write-enabled subagent runtime.',
                'audit_id' => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        if ( ! $this->is_proc_open_runtime_available() ) {
            $audit_path = $this->append_main_coding_audit_event( 'shell_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'proc_open_unavailable',
            ) );
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'proc_open runtime is not available on this server.',
                'audit_id' => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $command = isset( $args['command'] ) ? trim( (string) $args['command'] ) : '';
        $argv    = isset( $args['argv'] ) && is_array( $args['argv'] ) ? array_values( $args['argv'] ) : array();
        $argv    = array_values(
            array_filter(
                array_map(
                    static function ( $value ) {
                        return is_scalar( $value ) ? (string) $value : '';
                    },
                    $argv
                ),
                static function ( $value ) {
                    return '' !== $value;
                }
            )
        );

        if ( '' === $command && empty( $argv ) ) {
            $audit_path = $this->append_main_coding_audit_event( 'shell_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'missing_command',
                'session_id' => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            ) );
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'Pass command as a raw shell string or argv as an argument array.',
                'audit_id' => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

		$secret_boundary = $this->validate_main_coding_shell_secret_boundary( $command, $argv );
		if ( empty( $secret_boundary['ok'] ) ) {
			$audit_path = $this->append_main_coding_audit_event( 'shell_rejected', array(
				'audit_id'   => $audit_id,
				'mode'       => (string) $mode,
				'reason'     => 'secret_egress_or_obfuscation_blocked',
				'command'    => '' !== $command ? $command : $argv,
				'session_id' => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
			) );
			return array(
				'ok'             => false,
				'success'        => false,
				'error'          => isset( $secret_boundary['error'] ) ? (string) $secret_boundary['error'] : 'Outbound transfer or obfuscation of local secret sources is blocked.',
				'policy'         => 'main_coding_shell_secret_boundary',
				'audit_id'       => $audit_id,
				'audit_log_path' => $audit_path,
			);
        }

        $cwd_result = $this->resolve_main_coding_cwd( isset( $args['cwd'] ) ? (string) $args['cwd'] : '' );
        if ( empty( $cwd_result['ok'] ) ) {
            $audit_path = $this->append_main_coding_audit_event( 'shell_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'invalid_cwd',
                'cwd_arg'  => isset( $args['cwd'] ) ? (string) $args['cwd'] : '',
                'error'    => isset( $cwd_result['error'] ) ? (string) $cwd_result['error'] : 'Invalid cwd.',
                'session_id' => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            ) );
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => isset( $cwd_result['error'] ) ? (string) $cwd_result['error'] : 'Invalid cwd.',
                'audit_id' => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $write_guard = $this->validate_main_coding_shell_write_targets( $command, $argv, $cwd_result['path'] );
        if ( ! empty( $write_guard['heuristic_allow'] ) ) {
            $this->append_main_coding_audit_event( 'shell_warning_false_positive_likely', array(
                'audit_id'       => $audit_id,
                'mode'           => (string) $mode,
                'session_id'     => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'turn_id'        => isset( $options['turn_id'] ) ? (string) $options['turn_id'] : '',
                'reason'         => isset( $write_guard['reason'] ) ? (string) $write_guard['reason'] : 'likely_false_positive_write_parse',
                'command'        => '' !== $command ? $command : $argv,
                'cwd'            => $cwd_result['display_path'],
                'resolved_cwd'   => $cwd_result['path'],
                'write_targets'  => isset( $write_guard['write_targets'] ) ? $write_guard['write_targets'] : array(),
                'unsafe_targets' => isset( $write_guard['unsafe_targets'] ) ? $write_guard['unsafe_targets'] : array(),
                'warning'        => isset( $write_guard['diagnostic_warning'] ) ? (string) $write_guard['diagnostic_warning'] : '',
            ) );
        }
        if ( empty( $write_guard['ok'] ) ) {
            $audit_path = $this->append_main_coding_audit_event( 'shell_warning_direct_write_blocked', array(
                'audit_id'       => $audit_id,
                'mode'           => (string) $mode,
                'session_id'     => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'turn_id'        => isset( $options['turn_id'] ) ? (string) $options['turn_id'] : '',
                'reason'         => isset( $write_guard['reason'] ) ? (string) $write_guard['reason'] : 'unsafe_write_target',
                'command'        => '' !== $command ? $command : $argv,
                'cwd'            => $cwd_result['display_path'],
                'resolved_cwd'   => $cwd_result['path'],
                'write_targets'  => isset( $write_guard['write_targets'] ) ? $write_guard['write_targets'] : array(),
                'unsafe_targets' => isset( $write_guard['unsafe_targets'] ) ? $write_guard['unsafe_targets'] : array(),
            ) );
            return array(
                'ok'            => false,
                'success'       => false,
                'error'         => isset( $write_guard['error'] ) ? (string) $write_guard['error'] : 'Direct production file writes are blocked. Use main_coding_workspace_prepare, edit returned work_path files, then main_coding_workspace_apply.',
                'policy'        => 'main_coding_workspace_write_guard',
                'write_targets' => isset( $write_guard['write_targets'] ) ? $write_guard['write_targets'] : array(),
                'unsafe_targets' => isset( $write_guard['unsafe_targets'] ) ? $write_guard['unsafe_targets'] : array(),
                'audit_id'      => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $timeout = isset( $args['timeout_seconds'] ) ? max( 0, intval( $args['timeout_seconds'] ) ) : 0;
        $limit   = array_key_exists( 'max_output_chars', $args ) ? max( 0, intval( $args['max_output_chars'] ) ) : 40000;
        $audit_path = $this->append_main_coding_audit_event( 'shell_start', array(
            'audit_id'         => $audit_id,
            'mode'             => (string) $mode,
            'session_id'       => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'turn_id'          => isset( $options['turn_id'] ) ? (string) $options['turn_id'] : '',
            'command'          => '' !== $command ? $command : $argv,
            'cwd'              => $cwd_result['display_path'],
            'resolved_cwd'     => $cwd_result['path'],
            'timeout_seconds'  => $timeout,
            'max_output_chars' => $limit,
            'policy'           => 'main_coding_shell_workspace_guarded',
        ) );

        $run = array(
            'ok'          => false,
            'success'     => false,
            'exit_code'   => -1,
            'timed_out'   => false,
            'duration_ms' => 0,
            'stdout'      => '',
            'stderr'      => '',
            'error'       => 'Shell process did not complete.',
        );

        try {
            $run = $this->run_main_coding_shell_process( $command, $argv, $cwd_result['path'], $timeout, $limit );
        } catch ( \Throwable $e ) {
            $run = array(
                'ok'          => false,
                'success'     => false,
                'exit_code'   => -1,
                'timed_out'   => false,
                'duration_ms' => 0,
                'stdout'      => '',
                'stderr'      => $e->getMessage(),
                'error'       => 'main_coding_shell_run exception: ' . $e->getMessage(),
            );
        } finally {
            $run['command']          = '' !== $command ? $command : $argv;
            $run['cwd']              = $cwd_result['display_path'];
            $run['timeout_seconds']  = $timeout;
            $run['max_output_chars'] = $limit;
            $run['policy']           = 'main_coding_shell_workspace_guarded';
            $run['audit_id']         = $audit_id;
            $run['audit_log_path']   = $audit_path;
            $run['wordpress_root']    = defined( 'ABSPATH' ) ? wp_normalize_path( untrailingslashit( ABSPATH ) ) : '';
            $run['niwa_plugin_path']  = $this->get_current_niwa_plugin_relative_path();
            $run['path_contract']     = 'Resolve Niwa runtime files from niwa_plugin_path or exact workspace_prepare target_path/work_path values. Do not assume wp-content/plugins/niwa-ai when niwa_plugin_path differs.';
            if ( ! empty( $write_guard['diagnostic_warning'] ) ) {
                $run['write_guard_warning'] = (string) $write_guard['diagnostic_warning'];
            }

            $run['command'] = $this->sanitize_main_coding_audit_value( $run['command'] );
            foreach ( array( 'stdout', 'stderr', 'error', 'write_guard_warning' ) as $text_key ) {
                if ( isset( $run[ $text_key ] ) && is_string( $run[ $text_key ] ) ) {
                    $run[ $text_key ] = $this->redact_main_coding_audit_text( $run[ $text_key ] );
                }
            }

            $this->append_main_coding_audit_event( 'shell_end', array(
                'audit_id'       => $audit_id,
                'mode'           => (string) $mode,
                'session_id'     => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'turn_id'        => isset( $options['turn_id'] ) ? (string) $options['turn_id'] : '',
                'success'        => ! empty( $run['success'] ),
                'exit_code'      => isset( $run['exit_code'] ) ? intval( $run['exit_code'] ) : -1,
                'timed_out'      => ! empty( $run['timed_out'] ),
                'duration_ms'    => isset( $run['duration_ms'] ) ? $run['duration_ms'] : 0,
                'command'        => $run['command'],
                'cwd'            => $run['cwd'],
                'stdout'         => isset( $run['stdout'] ) ? (string) $run['stdout'] : '',
                'stderr'         => isset( $run['stderr'] ) ? (string) $run['stderr'] : '',
                'error'          => isset( $run['error'] ) ? (string) $run['error'] : '',
            ) );
        }

        return $run;
    }

    private function can_execute_main_coding_tools( $mode, $options = array() ) {
        $mode    = (string) $mode;
        $options = is_array( $options ) ? $options : array();

        return 'main' === $mode || ( 'subagent' === $mode && ! empty( $options['allow_writes'] ) );
    }

    private function validate_main_coding_shell_write_targets( $command, $argv, $cwd ) {
        $targets = $this->extract_main_coding_shell_write_targets( $command, $argv, $cwd );
        if ( empty( $targets ) ) {
            return array( 'ok' => true, 'write_targets' => array(), 'unsafe_targets' => array() );
        }

        $unsafe = array();
        foreach ( $targets as $target ) {
            $target = is_array( $target ) ? $target : array( 'raw' => (string) $target );
            if ( ! empty( $target['discard'] ) ) {
                continue;
            }
            if ( empty( $target['path'] ) || empty( $target['allowed'] ) ) {
                $unsafe[] = $target;
            }
        }

        if ( empty( $unsafe ) ) {
            return array( 'ok' => true, 'write_targets' => $targets, 'unsafe_targets' => array() );
        }

        $heuristic_tokens = $this->summarize_main_coding_write_target_tokens( $unsafe );
        if ( $this->is_likely_main_coding_write_guard_false_positive( $targets ) ) {
            return array(
                'ok'                 => true,
                'reason'             => 'likely_false_positive_write_parse',
                'diagnostic_warning' => 'Write guard saw likely false-positive sed/perl in-place tokens (' . $heuristic_tokens . ') and allowed the command. If this command was meant to write files, rerun it against returned work_path files under niwa-ai-workspace/main-coding.',
                'heuristic_allow'    => true,
                'write_targets'      => $targets,
                'unsafe_targets'     => $unsafe,
            );
        }

        return array(
            'ok'             => false,
            'reason'         => 'direct_file_write_outside_workspace',
            'error'          => 'Direct file writes outside the main-coding workspace are blocked. Use main_coding_workspace_prepare, edit only returned work_path files under niwa-ai-workspace/main-coding, then call main_coding_workspace_apply. This was a heuristic block on tokens: ' . $heuristic_tokens . '. If your command was read-only (sed -n, find -iname, grep, ls), retry without the suspicious tokens, or wrap with bash -c \'cmd\' to force atomic parsing of the exact read-only command.',
            'write_targets'  => $targets,
            'unsafe_targets' => $unsafe,
        );
    }

    private function summarize_main_coding_write_target_tokens( $targets ) {
        $tokens = array();
        foreach ( (array) $targets as $target ) {
            if ( ! is_array( $target ) ) {
                continue;
            }
            $raw = isset( $target['raw'] ) ? trim( (string) $target['raw'] ) : '';
            if ( '' === $raw ) {
                $raw = isset( $target['source'] ) ? trim( (string) $target['source'] ) : '';
            }
            if ( '' === $raw ) {
                continue;
            }
            $tokens[] = $raw;
        }

        $tokens = array_values( array_unique( $tokens ) );
        if ( empty( $tokens ) ) {
            return '[unparsed]';
        }

        $tokens = array_slice( $tokens, 0, 8 );
        return implode( ', ', $tokens );
    }

    private function is_likely_main_coding_write_guard_false_positive( $targets ) {
        $checked = 0;
        foreach ( (array) $targets as $target ) {
            if ( ! is_array( $target ) || ! empty( $target['discard'] ) ) {
                continue;
            }

            $source = isset( $target['source'] ) ? (string) $target['source'] : '';
            if ( ! in_array( $source, array( 'sed_in_place', 'perl_in_place' ), true ) ) {
                return false;
            }

            $checked++;
            foreach ( array( 'raw', 'path' ) as $key ) {
                $value = isset( $target[ $key ] ) ? (string) $target[ $key ] : '';
                if ( $this->main_coding_target_has_known_write_suffix( $value ) ) {
                    return false;
                }
            }
        }

        return $checked > 0;
    }

    private function main_coding_target_has_known_write_suffix( $value ) {
        $value = strtolower( trim( str_replace( '\\', '/', (string) $value ) ) );
        if ( '' === $value ) {
            return false;
        }

        $path = parse_url( $value, PHP_URL_PATH );
        if ( is_string( $path ) && '' !== $path ) {
            $value = $path;
        }

        $basename = basename( $value );
        return (bool) preg_match( '/\.(?:php|phtml|inc|js|mjs|cjs|jsx|ts|tsx|json|lock|txt|md|css|scss|less|html|htm|xml|yml|yaml|ini|conf|config|env|sql|csv|tsv|po|pot|mo|sh|bash|zsh|ps1|bat|cmd|py|rb|go|java|c|cc|cpp|h|hpp|twig|blade\.php)$/i', $basename );
    }

    private function extract_main_coding_shell_write_targets( $command, $argv, $cwd ) {
        $command = trim( (string) $command );
        $argv    = is_array( $argv ) ? array_values( array_map( 'strval', $argv ) ) : array();
        $cwd     = $this->normalize_main_coding_shell_path( (string) $cwd );

        if ( '' !== $command ) {
            return $this->extract_main_coding_write_targets_from_command_string( $command, $cwd );
        }

        if ( empty( $argv ) ) {
            return array();
        }

        return $this->extract_main_coding_write_targets_from_argv( $argv, $cwd );
    }

    private function extract_main_coding_write_targets_from_command_string( $command, $cwd ) {
        $targets = array();
        $shell_vars   = $this->extract_main_coding_shell_literal_variables( $command, $cwd );
        $scan_command = $this->strip_main_coding_literal_heredoc_bodies( $command, $cwd, $shell_vars );

        if ( preg_match_all( '/(?:^|[\s;|&])(?:\d*)?>{1,2}\s*(?!&)(?:"([^"]+)"|\'([^\']+)\'|([^\s;&|<]+))/u', (string) $scan_command, $matches, PREG_SET_ORDER ) ) {
            foreach ( $matches as $match ) {
                $raw = '';
                foreach ( array( 1, 2, 3 ) as $idx ) {
                    if ( isset( $match[ $idx ] ) && '' !== $match[ $idx ] ) {
                        $raw = (string) $match[ $idx ];
                        break;
                    }
                }
                $raw = $this->expand_main_coding_shell_target_variables( $raw, $shell_vars );
                $targets[] = $this->build_main_coding_shell_write_target( 'redirect', $raw, $cwd );
            }
        }

        foreach ( $this->split_main_coding_shell_segments( $this->tokenize_main_coding_shell_command( $scan_command ) ) as $segment ) {
            $targets = array_merge( $targets, $this->extract_main_coding_write_targets_from_argv( $segment, $cwd ) );
        }

        foreach ( $this->extract_main_coding_programmatic_write_targets( $command, $scan_command, $cwd ) as $target ) {
            $targets[] = $target;
        }

        return $this->dedupe_main_coding_write_targets( $targets );
    }

    private function extract_main_coding_shell_literal_variables( $command, $cwd ) {
        $lines      = preg_split( "/\r\n|\n|\r/", (string) $command );
        $command    = '';
        $vars       = array();
        $delimiter  = '';
        $strip_tabs = false;

        foreach ( $lines as $line ) {
            if ( '' !== $delimiter ) {
                $check = $strip_tabs ? ltrim( (string) $line, "\t" ) : (string) $line;
                if ( trim( $check ) === $delimiter ) {
                    $delimiter  = '';
                    $strip_tabs = false;
                }
                continue;
            }

            $command .= (string) $line . "\n";
            $marker   = $this->extract_main_coding_heredoc_marker_from_line( $line );
            if ( '' !== $marker ) {
                $delimiter  = $marker;
                $strip_tabs = (bool) preg_match( '/<<-\s*\S+/', (string) $line );
            }
        }

        if ( ! preg_match_all( '/(?:^|[\s;])([A-Za-z_][A-Za-z0-9_]*)=(?:"([^"\r\n]*)"|\'([^\'\r\n]*)\'|([^\s;&|<>]+))/u', $command, $matches, PREG_SET_ORDER ) ) {
            return $vars;
        }

        foreach ( $matches as $match ) {
            $name = isset( $match[1] ) ? (string) $match[1] : '';
            if ( '' === $name ) {
                continue;
            }

            $value = '';
            foreach ( array( 2, 3, 4 ) as $idx ) {
                if ( isset( $match[ $idx ] ) && '' !== $match[ $idx ] ) {
                    $value = (string) $match[ $idx ];
                    break;
                }
            }
            if ( '' === $value || false !== strpos( $value, "\0" ) ) {
                continue;
            }

            $value = $this->expand_main_coding_shell_target_variables( $value, $vars );
            if ( false !== strpos( $value, '`' ) || false !== strpos( $value, '$(' ) ) {
                continue;
            }

            $vars[ $name ] = $value;
        }

        return $vars;
    }

    private function expand_main_coding_shell_target_variables( $raw, $vars ) {
        $raw = (string) $raw;
        if ( empty( $vars ) || false === strpos( $raw, '$' ) ) {
            return $raw;
        }

        return preg_replace_callback(
            '/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?/',
            static function ( $match ) use ( $vars ) {
                $name = isset( $match[1] ) ? (string) $match[1] : '';
                return array_key_exists( $name, $vars ) ? (string) $vars[ $name ] : (string) $match[0];
            },
            $raw
        );
    }

    private function strip_main_coding_literal_heredoc_bodies( $command, $cwd, $shell_vars = array() ) {
        $lines      = preg_split( "/\r\n|\n|\r/", (string) $command );
        $out        = array();
        $delimiter  = '';
        $strip_tabs = false;

        foreach ( $lines as $line ) {
            if ( '' !== $delimiter ) {
                $check = $strip_tabs ? ltrim( (string) $line, "\t" ) : (string) $line;
                if ( trim( $check ) === $delimiter ) {
                    $delimiter  = '';
                    $strip_tabs = false;
                    $out[]      = ';';
                }
                continue;
            }

            $marker = $this->extract_main_coding_heredoc_marker_from_line( $line );
            $out[]  = $line;
            if ( '' !== $marker ) {
                $delimiter  = $marker;
                $strip_tabs = (bool) preg_match( '/<<-\s*\S+/', (string) $line );
            }
        }

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

    private function extract_main_coding_heredoc_marker_from_line( $line ) {
        if ( ! preg_match( '/<<-?\s*(\S+)/', (string) $line, $match ) ) {
            return '';
        }

        $token = isset( $match[1] ) ? (string) $match[1] : '';
        $token = rtrim( $token, ';' );
        $token = preg_replace( '/[^A-Za-z0-9_.-]/', '', $token );
        return is_string( $token ) ? $token : '';
    }

    private function extract_main_coding_write_targets_from_argv( $argv, $cwd ) {
        $argv = is_array( $argv ) ? array_values( array_map( 'strval', $argv ) ) : array();
        if ( empty( $argv ) ) {
            return array();
        }

        $cmd = strtolower( basename( (string) $argv[0] ) );
        $cmd = preg_replace( '/(?:\.exe|\.bat|\.cmd)$/i', '', $cmd );

        if ( '' === $cmd ) {
            return array();
        }

        if ( ! $this->is_main_coding_shell_write_command_name( $cmd ) ) {
            return array();
        }

        if ( 'tee' === $cmd ) {
            return $this->extract_main_coding_tee_targets( array_slice( $argv, 1 ), $cwd );
        }

        if ( 'sed' === $cmd ) {
            return $this->extract_main_coding_sed_targets( array_slice( $argv, 1 ), $cwd );
        }

        if ( 'perl' === $cmd ) {
            return $this->extract_main_coding_perl_targets( array_slice( $argv, 1 ), $cwd );
        }

        if ( in_array( $cmd, array( 'cp', 'mv' ), true ) ) {
            return $this->extract_main_coding_copy_move_targets( array_slice( $argv, 1 ), $cwd, $cmd );
        }

        if ( in_array( $cmd, array( 'touch', 'rm', 'mkdir', 'chmod', 'chown' ), true ) ) {
            return $this->extract_main_coding_generic_file_targets( array_slice( $argv, 1 ), $cwd, $cmd );
        }

        if ( in_array( $cmd, array( 'set-content', 'add-content', 'out-file', 'new-item', 'remove-item' ), true ) ) {
            return $this->extract_main_coding_powershell_file_targets( array_slice( $argv, 1 ), $cwd, $cmd );
        }

        if ( in_array( $cmd, array( 'copy-item', 'move-item' ), true ) ) {
            return $this->extract_main_coding_powershell_copy_move_targets( array_slice( $argv, 1 ), $cwd, $cmd );
        }

        return array();
    }

    private function is_main_coding_shell_write_command_name( $cmd ) {
        $cmd = strtolower( (string) $cmd );
        return in_array(
            $cmd,
            array(
                'tee',
                'sed',
                'perl',
                'cp',
                'mv',
                'touch',
                'rm',
                'mkdir',
                'chmod',
                'chown',
                'set-content',
                'add-content',
                'out-file',
                'new-item',
                'remove-item',
                'copy-item',
                'move-item',
            ),
            true
        );
    }

    private function extract_main_coding_tee_targets( $args, $cwd ) {
        $targets = array();
        foreach ( (array) $args as $arg ) {
            $arg = (string) $arg;
            if ( '' === $arg || '-' === $arg || 0 === strpos( $arg, '-' ) ) {
                continue;
            }
            $targets[] = $this->build_main_coding_shell_write_target( 'tee', $arg, $cwd );
        }
        return $targets;
    }

    private function extract_main_coding_sed_targets( $args, $cwd ) {
        $args      = array_values( array_map( 'strval', (array) $args ) );
        $in_place  = false;
        $script_seen = false;
        $skip_next_script = false;
        $targets   = array();

        foreach ( $args as $idx => $arg ) {
            if ( '' === $arg ) {
                continue;
            }
            if ( $skip_next_script ) {
                $script_seen      = true;
                $skip_next_script = false;
                continue;
            }
            if ( $this->is_main_coding_sed_in_place_arg( $arg ) ) {
                $in_place = true;
                continue;
            }
            if ( in_array( $arg, array( '-e', '-f' ), true ) ) {
                $skip_next_script = true;
                continue;
            }
            if ( 0 === strpos( $arg, '-' ) && ! preg_match( '/^[A-Za-z]:[\/\\\\]/', $arg ) ) {
                continue;
            }
            if ( ! $script_seen ) {
                $script_seen = true;
                continue;
            }
            $targets[] = $this->build_main_coding_shell_write_target( 'sed_in_place', $arg, $cwd );
        }

        return $in_place ? $targets : array();
    }

    private function is_main_coding_sed_in_place_arg( $arg ) {
        $arg = trim( (string) $arg );
        if ( in_array( strtolower( $arg ), array( '-iname', '-ipath', '-iregex', '-ilname', '-iwholename' ), true ) ) {
            return false;
        }

        if ( '-i' === $arg || '--in-place' === $arg ) {
            return true;
        }

        if ( preg_match( '/^-i\.[A-Za-z0-9_.-]+$/', $arg ) ) {
            return true;
        }

        return (bool) preg_match( '/^--in-place=[A-Za-z0-9_.-]+$/', $arg );
    }

    private function extract_main_coding_perl_targets( $args, $cwd ) {
        $args     = array_values( array_map( 'strval', (array) $args ) );
        $in_place = false;
        $targets  = array();
        $skip_next_script = false;

        foreach ( $args as $arg ) {
            if ( '' === $arg ) {
                continue;
            }
            if ( $skip_next_script ) {
                $skip_next_script = false;
                continue;
            }
            if ( '-pi' === strtolower( $arg ) || preg_match( '/^-.*i.*$/', $arg ) ) {
                $in_place = true;
                continue;
            }
            if ( in_array( $arg, array( '-e', '-E' ), true ) ) {
                $skip_next_script = true;
                continue;
            }
            if ( 0 === strpos( $arg, '-' ) && ! preg_match( '/^[A-Za-z]:[\/\\\\]/', $arg ) ) {
                continue;
            }
            $targets[] = $this->build_main_coding_shell_write_target( 'perl_in_place', $arg, $cwd );
        }

        return $in_place ? $targets : array();
    }

    private function extract_main_coding_copy_move_targets( $args, $cwd, $source ) {
        $paths = array();
        foreach ( (array) $args as $arg ) {
            $arg = (string) $arg;
            if ( '' === $arg || '-' === $arg ) {
                continue;
            }
            if ( 0 === strpos( $arg, '-' ) && ! preg_match( '/^[A-Za-z]:[\/\\\\]/', $arg ) ) {
                continue;
            }
            $paths[] = $arg;
        }
        if ( count( $paths ) < 2 ) {
            return array();
        }

        return array( $this->build_main_coding_shell_write_target( $source . '_destination', end( $paths ), $cwd ) );
    }

    private function extract_main_coding_generic_file_targets( $args, $cwd, $source ) {
        $targets   = array();
        $skip_next = in_array( $source, array( 'chmod', 'chown' ), true );
        foreach ( (array) $args as $arg ) {
            $arg = (string) $arg;
            if ( '' === $arg || '-' === $arg ) {
                continue;
            }
            if ( 0 === strpos( $arg, '-' ) && ! preg_match( '/^[A-Za-z]:[\/\\\\]/', $arg ) ) {
                continue;
            }
            if ( $skip_next ) {
                $skip_next = false;
                continue;
            }
            $targets[] = $this->build_main_coding_shell_write_target( $source, $arg, $cwd );
        }

        return $targets;
    }

    private function extract_main_coding_powershell_file_targets( $args, $cwd, $source ) {
        $args          = array_values( array_map( 'strval', (array) $args ) );
        $target_params = 'out-file' === $source ? array( 'filepath', 'literalpath' ) : array( 'path', 'literalpath' );
        $skip_params   = array( 'value', 'encoding', 'stream', 'itemtype', 'type', 'filter', 'include', 'exclude', 'credential' );
        $targets       = array();
        $positionals   = array();

        for ( $i = 0; $i < count( $args ); $i++ ) {
            $arg = trim( (string) $args[ $i ] );
            if ( '' === $arg || '-' === $arg ) {
                continue;
            }

            if ( 0 === strpos( $arg, '-' ) && ! preg_match( '/^[A-Za-z]:[\/\\\\]/', $arg ) ) {
                $param_text = ltrim( $arg, '-' );
                $param      = strtolower( $param_text );
                $value      = '';
                if ( false !== strpos( $param_text, ':' ) ) {
                    list( $param, $value ) = array_pad( explode( ':', $param_text, 2 ), 2, '' );
                    $param = strtolower( $param );
                } elseif ( false !== strpos( $param_text, '=' ) ) {
                    list( $param, $value ) = array_pad( explode( '=', $param_text, 2 ), 2, '' );
                    $param = strtolower( $param );
                }

                if ( in_array( $param, $target_params, true ) ) {
                    if ( '' === $value && isset( $args[ $i + 1 ] ) ) {
                        $value = (string) $args[ ++$i ];
                    }
                    if ( '' !== trim( $value ) ) {
                        $targets[] = $this->build_main_coding_shell_write_target( $source, $value, $cwd );
                    }
                    continue;
                }

                if ( in_array( $param, $skip_params, true ) && isset( $args[ $i + 1 ] ) ) {
                    $i++;
                }
                continue;
            }

            $positionals[] = $arg;
        }

        if ( empty( $targets ) && ! empty( $positionals ) ) {
            $targets[] = $this->build_main_coding_shell_write_target( $source, $positionals[0], $cwd );
        }

        if ( empty( $targets ) ) {
            $targets[] = array(
                'source'  => $source . '_unknown_target',
                'raw'     => '[unparsed]',
                'path'    => '',
                'allowed' => false,
                'reason'  => 'unparsed_powershell_write_target',
            );
        }

        return $targets;
    }

    private function extract_main_coding_powershell_copy_move_targets( $args, $cwd, $source ) {
        $args        = array_values( array_map( 'strval', (array) $args ) );
        $skip_params = array( 'filter', 'include', 'exclude', 'credential' );
        $positionals = array();
        $destination = '';

        for ( $i = 0; $i < count( $args ); $i++ ) {
            $arg = trim( (string) $args[ $i ] );
            if ( '' === $arg || '-' === $arg ) {
                continue;
            }

            if ( 0 === strpos( $arg, '-' ) && ! preg_match( '/^[A-Za-z]:[\/\\\\]/', $arg ) ) {
                $param_text = ltrim( $arg, '-' );
                $param      = strtolower( $param_text );
                $value      = '';
                if ( false !== strpos( $param_text, ':' ) ) {
                    list( $param, $value ) = array_pad( explode( ':', $param_text, 2 ), 2, '' );
                    $param = strtolower( $param );
                } elseif ( false !== strpos( $param_text, '=' ) ) {
                    list( $param, $value ) = array_pad( explode( '=', $param_text, 2 ), 2, '' );
                    $param = strtolower( $param );
                }

                if ( 'destination' === $param ) {
                    if ( '' === $value && isset( $args[ $i + 1 ] ) ) {
                        $value = (string) $args[ ++$i ];
                    }
                    if ( '' !== trim( $value ) ) {
                        $destination = $value;
                    }
                    continue;
                }

                if ( in_array( $param, array( 'path', 'literalpath' ), true ) && isset( $args[ $i + 1 ] ) ) {
                    $i++;
                    continue;
                }

                if ( in_array( $param, $skip_params, true ) && isset( $args[ $i + 1 ] ) ) {
                    $i++;
                }
                continue;
            }

            $positionals[] = $arg;
        }

        if ( '' === trim( $destination ) && count( $positionals ) >= 2 ) {
            $destination = end( $positionals );
        }

        if ( '' !== trim( $destination ) ) {
            return array( $this->build_main_coding_shell_write_target( $source . '_destination', $destination, $cwd ) );
        }

        return array(
            array(
                'source'  => $source . '_unknown_destination',
                'raw'     => '[unparsed]',
                'path'    => '',
                'allowed' => false,
                'reason'  => 'unparsed_powershell_write_target',
            ),
        );
    }

    private function extract_main_coding_programmatic_write_targets( $command, $scan_command, $cwd ) {
        $targets = array();
        foreach ( $this->extract_main_coding_python_write_targets( $command, $cwd ) as $target ) {
            $targets[] = $target;
        }

        $scan_command = (string) $scan_command;
        $patterns = array(
            'file_put_contents' => '/file_put_contents\s*\(\s*[\'"]([^\'"]+)[\'"]/i',
            'fopen_write'       => '/fopen\s*\(\s*[\'"]([^\'"]+)[\'"]\s*,\s*[\'"][^\'"]*[wa+][^\'"]*[\'"]/i',
            'python_open_write' => '/open\s*\(\s*[\'"]([^\'"]+)[\'"]\s*,\s*[\'"][^\'"]*[wa+][^\'"]*[\'"]/i',
            'rename_target'     => '/rename\s*\(\s*[\'"][^\'"]+[\'"]\s*,\s*[\'"]([^\'"]+)[\'"]/i',
            'copy_target'       => '/copy\s*\(\s*[\'"][^\'"]+[\'"]\s*,\s*[\'"]([^\'"]+)[\'"]/i',
            'unlink_target'     => '/unlink\s*\(\s*[\'"]([^\'"]+)[\'"]/i',
        );

        foreach ( $patterns as $source => $pattern ) {
            if ( preg_match_all( $pattern, $scan_command, $matches ) ) {
                foreach ( $matches[1] as $raw ) {
                    $targets[] = $this->build_main_coding_shell_write_target( $source, (string) $raw, $cwd );
                }
            }
        }

        if ( empty( $targets ) && preg_match( '/file_put_contents\s*\(|fopen\s*\(|rename\s*\(|copy\s*\(|unlink\s*\(/i', $scan_command ) ) {
            $targets[] = array(
                'source'  => 'programmatic_write_unknown_target',
                'raw'     => '[unparsed]',
                'path'    => '',
                'allowed' => false,
                'reason'  => 'unparsed_programmatic_write_target',
            );
        }

        return $targets;
    }

    private function extract_main_coding_python_write_targets( $command, $cwd ) {
        $targets = array();
        $paths   = array();
        $lines   = preg_split( "/\r\n|\n|\r/", (string) $command );
        foreach ( (array) $lines as $line ) {
            $line = trim( (string) $line );
            if ( '' === $line || '#' === substr( $line, 0, 1 ) ) {
                continue;
            }

            if ( preg_match( '/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*Path\s*\(\s*([\'"])(.*?)\2\s*\)\s*$/', $line, $match ) ) {
                $paths[ $match[1] ] = (string) $match[3];
                continue;
            }

            if ( preg_match( '/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([A-Za-z_][A-Za-z0-9_]*)\s*\/\s*([\'"])(.*?)\3\s*$/', $line, $match ) ) {
                $base = isset( $paths[ $match[2] ] ) ? (string) $paths[ $match[2] ] : '';
                if ( '' !== $base ) {
                    $paths[ $match[1] ] = rtrim( str_replace( '\\', '/', $base ), '/' ) . '/' . (string) $match[4];
                }
                continue;
            }

            if ( preg_match_all( '/\b([A-Za-z_][A-Za-z0-9_]*)\.(?:write_text|write_bytes|touch|unlink)\s*\(/', $line, $matches ) ) {
                foreach ( $matches[1] as $var_name ) {
                    if ( isset( $paths[ $var_name ] ) ) {
                        $targets[] = $this->build_main_coding_shell_write_target( 'python_pathlib_write', (string) $paths[ $var_name ], $cwd );
                    }
                }
            }

            if ( preg_match_all( '/Path\s*\(\s*([\'"])(.*?)\1\s*\)\.(?:write_text|write_bytes|touch|unlink)\s*\(/', $line, $matches ) ) {
                foreach ( $matches[2] as $raw ) {
                    $targets[] = $this->build_main_coding_shell_write_target( 'python_pathlib_write', (string) $raw, $cwd );
                }
            }

            if ( preg_match_all( '/\bopen\s*\(\s*([A-Za-z_][A-Za-z0-9_]*|[\'"][^\'"]+[\'"])\s*,\s*[\'"][^\'"]*[wax+][^\'"]*[\'"]/', $line, $matches ) ) {
                foreach ( $matches[1] as $raw ) {
                    $raw = trim( (string) $raw, "\"' \t\r\n" );
                    if ( isset( $paths[ $raw ] ) ) {
                        $raw = (string) $paths[ $raw ];
                    }
                    $targets[] = $this->build_main_coding_shell_write_target( 'python_open_write', $raw, $cwd );
                }
            }
        }

        return $targets;
    }

    private function tokenize_main_coding_shell_command( $command ) {
        $command = (string) $command;
        $length  = strlen( $command );
        $tokens  = array();
        $current = '';
        $quote   = '';
        $escaped = false;

        for ( $i = 0; $i < $length; $i++ ) {
            $char = $command[ $i ];
            if ( $escaped ) {
                $current .= $char;
                $escaped = false;
                continue;
            }
            if ( '\\' === $char ) {
                $escaped = true;
                continue;
            }
            if ( '' !== $quote ) {
                if ( $char === $quote ) {
                    $quote = '';
                    continue;
                }
                $current .= $char;
                continue;
            }
            if ( '"' === $char || "'" === $char ) {
                $quote = $char;
                continue;
            }
            if ( "\r" === $char || "\n" === $char ) {
                if ( '' !== $current ) {
                    $tokens[] = $current;
                    $current  = '';
                }
                $tokens[] = ';';
                if ( "\r" === $char && $i + 1 < $length && "\n" === $command[ $i + 1 ] ) {
                    $i++;
                }
                continue;
            }
            if ( preg_match( '/\s/', $char ) ) {
                if ( '' !== $current ) {
                    $tokens[] = $current;
                    $current  = '';
                }
                continue;
            }
            if ( in_array( $char, array( ';', '|' ), true ) ) {
                if ( '' !== $current ) {
                    $tokens[] = $current;
                    $current  = '';
                }
                $tokens[] = $char;
                continue;
            }
            if ( '&' === $char && $i + 1 < $length && '&' === $command[ $i + 1 ] ) {
                if ( '' !== $current ) {
                    $tokens[] = $current;
                    $current  = '';
                }
                $tokens[] = '&&';
                $i++;
                continue;
            }
            $current .= $char;
        }

        if ( '' !== $current ) {
            $tokens[] = $current;
        }

        return $tokens;
    }

    private function split_main_coding_shell_segments( $tokens ) {
        $segments = array();
        $current  = array();
        foreach ( (array) $tokens as $token ) {
            if ( in_array( (string) $token, array( ';', '|', '&&', '||' ), true ) ) {
                if ( ! empty( $current ) ) {
                    $segments[] = $current;
                    $current    = array();
                }
                continue;
            }
            $current[] = (string) $token;
        }
        if ( ! empty( $current ) ) {
            $segments[] = $current;
        }

        return $segments;
    }

    private function build_main_coding_shell_write_target( $source, $raw, $cwd ) {
        $raw = $this->clean_main_coding_shell_target_token( $raw );
        if ( $this->is_main_coding_discard_write_target( $raw ) ) {
            return array(
                'source'  => (string) $source,
                'raw'     => $raw,
                'path'    => $raw,
                'allowed' => true,
                'discard' => true,
            );
        }

        $path = $this->resolve_main_coding_shell_target_path( $raw, $cwd );
        $allowed = '' !== $path && $this->is_main_coding_workspace_write_path( $path );

        return array(
            'source'  => (string) $source,
            'raw'     => $raw,
            'path'    => $path,
            'allowed' => $allowed,
            'reason'  => $allowed ? 'workspace_target' : ( '' === $path ? 'unresolved_write_target' : 'outside_main_coding_workspace' ),
        );
    }

    private function clean_main_coding_shell_target_token( $raw ) {
        $raw = trim( (string) $raw );
        $raw = trim( $raw, "\"' \t\r\n" );
        $raw = rtrim( $raw, ';' );
        return $raw;
    }

    private function is_main_coding_discard_write_target( $raw ) {
        $raw = strtolower( trim( str_replace( '\\', '/', (string) $raw ) ) );
        return in_array( $raw, array( '/dev/null', 'nul', 'null' ), true );
    }

    private function resolve_main_coding_shell_target_path( $raw, $cwd ) {
        $raw = $this->clean_main_coding_shell_target_token( $raw );
        if ( '' === $raw || false !== strpos( $raw, "\0" ) ) {
            return '';
        }
        if ( preg_match( '/[$`*?\[\]{}]|^\(|^~|^[a-z][a-z0-9+.-]*:/i', $raw ) && ! preg_match( '/^[A-Za-z]:[\/\\\\]/', $raw ) ) {
            return '';
        }

        $path = str_replace( '\\', '/', $raw );
        if ( ! preg_match( '#^[A-Za-z]:/#', $path ) && 0 !== strpos( $path, '/' ) ) {
            $path = rtrim( str_replace( '\\', '/', (string) $cwd ), '/' ) . '/' . $path;
        }

        $real = realpath( $path );
        if ( false !== $real ) {
            return $this->normalize_main_coding_shell_path( $real );
        }

        $parent = realpath( dirname( $path ) );
        if ( false === $parent ) {
            return '';
        }

        return $this->normalize_main_coding_shell_path( rtrim( str_replace( '\\', '/', $parent ), '/' ) . '/' . basename( $path ) );
    }

    private function is_main_coding_workspace_write_path( $path ) {
        $path = $this->normalize_main_coding_shell_path( $path );
        if ( '' === $path ) {
            return false;
        }

        $workspace_base = $this->normalize_main_coding_shell_path( $this->get_main_coding_workspace_base_dir() );
        if ( '' !== $workspace_base && 0 === strpos( $path . '/', rtrim( $workspace_base, '/' ) . '/' ) ) {
            return true;
        }

        if ( preg_match( '#/(?:uploads/)?niwa-ai-workspace/main-coding/#i', $path ) ) {
            return true;
        }

        return (bool) preg_match( '/\.work-[^\/]+$/i', $path );
    }

    private function normalize_main_coding_shell_path( $path ) {
        $path = str_replace( '\\', '/', (string) $path );
        if ( function_exists( 'wp_normalize_path' ) ) {
            return wp_normalize_path( $path );
        }
        return preg_replace( '#/+#', '/', $path );
    }

    private function remap_legacy_main_coding_workspace_paths( $value ) {
        if ( is_array( $value ) ) {
            foreach ( $value as $key => $item ) {
                $value[ $key ] = $this->remap_legacy_main_coding_workspace_paths( $item );
            }
            return $value;
        }
        if ( ! is_string( $value ) || '' === $value ) {
            return $value;
        }

        $uploads = function_exists( 'wp_upload_dir' ) ? wp_upload_dir( null, false ) : array();
        $legacy  = ! empty( $uploads['basedir'] )
            ? $this->normalize_main_coding_shell_path( trailingslashit( (string) $uploads['basedir'] ) . 'niwa-ai-workspace/main-coding' )
            : '';
        $current = $this->normalize_main_coding_shell_path( $this->get_main_coding_workspace_base_dir() );
        $path    = $this->normalize_main_coding_shell_path( $value );
        if ( '' !== $legacy && '' !== $current && ( $path === $legacy || 0 === strpos( $path, rtrim( $legacy, '/' ) . '/' ) ) ) {
            return $current . substr( $path, strlen( $legacy ) );
        }

        return $value;
    }

    private function dedupe_main_coding_write_targets( $targets ) {
        $out  = array();
        $seen = array();
        foreach ( (array) $targets as $target ) {
            $target = is_array( $target ) ? $target : array( 'raw' => (string) $target );
            $key    = ( isset( $target['source'] ) ? (string) $target['source'] : '' ) . '|' . ( isset( $target['raw'] ) ? (string) $target['raw'] : '' ) . '|' . ( isset( $target['path'] ) ? (string) $target['path'] : '' );
            if ( isset( $seen[ $key ] ) ) {
                continue;
            }
            $seen[ $key ] = true;
            $out[]        = $target;
        }

        return $out;
    }

    private function expand_main_coding_prepare_target_paths( $target_paths ) {
        $target_paths = is_array( $target_paths ) ? array_values( $target_paths ) : array();
        $expanded     = array();
        $metadata     = array();
        $seen         = array();

        foreach ( $target_paths as $target ) {
            $target = trim( (string) $target );
            if ( '' === $target ) {
                continue;
            }

            if ( ! $this->is_main_coding_glob_pattern( $target ) ) {
                $key = wp_normalize_path( $target );
                if ( ! isset( $seen[ $key ] ) ) {
                    $seen[ $key ] = true;
                    $expanded[]   = $target;
                }
                continue;
            }

            $pattern = $this->normalize_main_coding_glob_pattern( $target );
            if ( '' === $pattern ) {
                return array(
                    'success'        => false,
                    'error'          => 'Glob target path could not be normalized.',
                    'failure_reason' => 'glob_invalid_pattern',
                    'target_path'    => $target,
                    'expanded_paths' => $metadata,
                );
            }

            $matches = glob( $pattern, GLOB_NOSORT );
            $matches = is_array( $matches ) ? array_values( $matches ) : array();
            $matches = array_values(
                array_filter(
                    array_map( 'wp_normalize_path', $matches ),
                    static function ( $match ) {
                        return is_string( $match ) && is_file( $match );
                    }
                )
            );
            sort( $matches, SORT_STRING );

            if ( empty( $matches ) ) {
                return array(
                    'success'        => false,
                    'error'          => 'Glob target path matched no files: ' . $target,
                    'failure_reason' => 'glob_no_matches',
                    'target_path'    => $target,
                    'expanded_paths' => $metadata,
                );
            }

            foreach ( $matches as $match ) {
                $display = $this->display_main_coding_path( $match );
                $key     = wp_normalize_path( $display );
                if ( isset( $seen[ $key ] ) ) {
                    continue;
                }
                $seen[ $key ] = true;
                $expanded[]   = $display;
                $metadata[]   = array(
                    'pattern' => $target,
                    'path'    => $display,
                );
            }

            if ( count( $expanded ) > 20 ) {
                return array(
                    'success'        => false,
                    'error'          => 'target_paths expands to more than 20 files. Narrow the glob or split the prepare operation.',
                    'failure_reason' => 'glob_too_many_matches',
                    'target_path'    => $target,
                    'expanded_paths' => $metadata,
                );
            }
        }

        if ( count( $expanded ) > 20 ) {
            return array(
                'success'        => false,
                'error'          => 'target_paths includes more than 20 files. Split the prepare operation.',
                'failure_reason' => 'too_many_target_paths',
                'expanded_paths' => $metadata,
            );
        }

        return array(
            'success'        => true,
            'target_paths'   => $expanded,
            'expanded_paths' => $metadata,
        );
    }

    private function is_main_coding_glob_pattern( $path ) {
        return is_string( $path ) && preg_match( '/[*?\\[]/', $path );
    }

    private function normalize_main_coding_glob_pattern( $pattern ) {
        $pattern = trim( str_replace( '\\', '/', (string) $pattern ) );
        if ( '' === $pattern ) {
            return '';
        }

        if ( defined( 'ABSPATH' ) ) {
            $base = wp_normalize_path( untrailingslashit( ABSPATH ) );
            if ( ! preg_match( '#^[A-Za-z]:/#', $pattern ) && 0 !== strpos( $pattern, '/' ) ) {
                $pattern = trailingslashit( $base ) . ltrim( $pattern, '/' );
            }
            $normalized = wp_normalize_path( $pattern );
            if ( 0 !== strpos( $normalized, trailingslashit( $base ) ) ) {
                return '';
            }
        }

        return wp_normalize_path( $pattern );
    }

    private function execute_main_coding_workspace_prepare( $args, $context, $prompt = '', $mode = 'main', $options = array() ) {
        $args = is_array( $args ) ? $args : array();
        $audit_id = $this->new_main_coding_audit_id( 'prepare' );
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            $failure = $this->build_main_coding_workspace_prepare_failure_intel( 'not_main_runtime' );
            $audit_path = $this->append_main_coding_audit_event( 'workspace_prepare_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'not_main_runtime',
            ) );
            return array(
                'success'        => false,
                'error'          => 'main_coding_workspace_prepare is available only in the main founder runtime or a write-enabled subagent runtime.',
                'failure_reason' => $failure['failure_reason'],
                'suggested_fix'  => $failure['suggested_fix'],
                'audit_id'       => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $target_paths = isset( $args['target_paths'] ) && is_array( $args['target_paths'] ) ? array_values( $args['target_paths'] ) : array();
        $target_paths = array_values(
            array_filter(
                array_map(
                    static function ( $value ) {
                        return is_scalar( $value ) ? trim( (string) $value ) : '';
                    },
                    $target_paths
                ),
                static function ( $value ) {
                    return '' !== $value;
                }
            )
        );

        if ( empty( $target_paths ) ) {
            $failure = $this->build_main_coding_workspace_prepare_failure_intel( 'missing_target_paths' );
            $audit_path = $this->append_main_coding_audit_event( 'workspace_prepare_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'missing_target_paths',
                'session_id' => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            ) );
            return array(
                'success'        => false,
                'error'          => 'target_paths is required.',
                'failure_reason' => $failure['failure_reason'],
                'suggested_fix'  => $failure['suggested_fix'],
                'audit_id'       => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $requested_target_paths = $target_paths;
        $expanded_targets       = $this->expand_main_coding_prepare_target_paths( $target_paths );
        if ( empty( $expanded_targets['success'] ) ) {
            $reason     = isset( $expanded_targets['failure_reason'] ) ? (string) $expanded_targets['failure_reason'] : 'target_path_unresolved';
            $failure    = $this->build_main_coding_workspace_prepare_failure_intel( $reason, array( 'target_path' => isset( $expanded_targets['target_path'] ) ? (string) $expanded_targets['target_path'] : '' ) );
            $audit_path = $this->append_main_coding_audit_event( 'workspace_prepare_rejected', array(
                'audit_id'       => $audit_id,
                'mode'           => (string) $mode,
                'reason'         => $reason,
                'target_paths'   => $requested_target_paths,
                'expanded_paths' => isset( $expanded_targets['expanded_paths'] ) ? $expanded_targets['expanded_paths'] : array(),
                'session_id'     => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            ) );
            return array(
                'success'          => false,
                'error'            => isset( $expanded_targets['error'] ) ? (string) $expanded_targets['error'] : 'Unable to expand target_paths.',
                'failure_reason'   => $failure['failure_reason'],
                'suggested_fix'    => $failure['suggested_fix'],
                'requested_paths'   => $requested_target_paths,
                'expanded_paths'    => isset( $expanded_targets['expanded_paths'] ) ? $expanded_targets['expanded_paths'] : array(),
                'max_target_paths'  => 20,
                'audit_id'         => $audit_id,
                'audit_log_path'   => $audit_path,
            );
        }
        $target_paths   = isset( $expanded_targets['target_paths'] ) ? $expanded_targets['target_paths'] : $target_paths;
        $expanded_paths = isset( $expanded_targets['expanded_paths'] ) ? $expanded_targets['expanded_paths'] : array();

        $base_dir = $this->get_main_coding_workspace_base_dir();
        if ( '' === $base_dir ) {
            $failure = $this->build_main_coding_workspace_prepare_failure_intel( 'workspace_base_unavailable' );
            $audit_path = $this->append_main_coding_audit_event( 'workspace_prepare_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'workspace_base_unavailable',
                'session_id' => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            ) );
            return array(
                'success'        => false,
                'error'          => 'Main coding workspace base directory is not available.',
                'failure_reason' => $failure['failure_reason'],
                'suggested_fix'  => $failure['suggested_fix'],
                'audit_id'       => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $operation_id = isset( $args['operation_id'] ) ? $this->sanitize_main_coding_operation_id( (string) $args['operation_id'] ) : '';
        if ( '' === $operation_id ) {
            $operation_id = 'mc-' . gmdate( 'Ymd-His' ) . '-' . substr( wp_generate_password( 12, false, false ), 0, 8 );
        }
        $audit_path = $this->append_main_coding_audit_event( 'workspace_prepare_start', array(
            'audit_id'       => $audit_id,
            'mode'           => (string) $mode,
            'session_id'     => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'requested_paths' => $requested_target_paths,
            'operation_id'   => $operation_id,
            'target_paths'   => $target_paths,
            'expanded_paths' => $expanded_paths,
            'allow_missing'  => ! empty( $args['allow_missing'] ),
            'note'           => isset( $args['note'] ) ? trim( (string) $args['note'] ) : trim( (string) $prompt ),
        ) );

        $op_dir     = trailingslashit( $base_dir ) . $operation_id;
        $backup_dir = trailingslashit( $op_dir ) . 'backups';
        $work_dir   = trailingslashit( $op_dir ) . 'work';
        $broken_dir = trailingslashit( $op_dir ) . 'broken';
        foreach ( array( $op_dir, $backup_dir, $work_dir, $broken_dir ) as $dir ) {
            if ( ! is_dir( $dir ) ) {
                Niwa_AI_Admin_Autonomy_Workspace::ensure_private_directory( $dir );
            }
        }
        $this->write_main_coding_workspace_deny_files( $op_dir );

        $files         = array();
        $errors        = array();
        $allow_missing = ! empty( $args['allow_missing'] );
        foreach ( $target_paths as $idx => $target ) {
            $resolved = $this->resolve_main_coding_target_file_path( $target, $allow_missing );
            if ( empty( $resolved['ok'] ) ) {
                $reason  = $this->classify_main_coding_workspace_prepare_resolve_failure( isset( $resolved['error'] ) ? (string) $resolved['error'] : '' );
                $failure = $this->build_main_coding_workspace_prepare_failure_intel( $reason, array( 'target_path' => $target ) );
                $errors[] = array(
                    'target_path'    => $target,
                    'error'          => isset( $resolved['error'] ) ? (string) $resolved['error'] : 'Unable to resolve target file.',
                    'failure_reason' => $failure['failure_reason'],
                    'suggested_fix'  => $failure['suggested_fix'],
                );
                continue;
            }

            $filename    = $this->build_main_coding_workspace_filename( count( $files ) + 1, $resolved['display_path'] );
            $backup_path = trailingslashit( $backup_dir ) . $filename . '.orig';
            $work_path   = trailingslashit( $work_dir ) . $filename;
            $exists      = ! empty( $resolved['exists'] );

            if ( $exists ) {
                if ( ! Niwa_AI_Admin_Autonomy_Workspace::copy_private_file( $resolved['path'], $backup_path ) ) {
                    $failure = $this->build_main_coding_workspace_prepare_failure_intel( 'backup_copy_failed', array( 'target_path' => $target ) );
                    $errors[] = array(
                        'target_path'    => $target,
                        'error'          => 'Unable to create original backup copy.',
                        'failure_reason' => $failure['failure_reason'],
                        'suggested_fix'  => $failure['suggested_fix'],
                    );
                    continue;
                }
                if ( ! Niwa_AI_Admin_Autonomy_Workspace::copy_private_file( $resolved['path'], $work_path ) ) {
                    $failure = $this->build_main_coding_workspace_prepare_failure_intel( 'work_copy_failed', array( 'target_path' => $target ) );
                    $errors[] = array(
                        'target_path'    => $target,
                        'error'          => 'Unable to create editable work copy.',
                        'failure_reason' => $failure['failure_reason'],
                        'suggested_fix'  => $failure['suggested_fix'],
                    );
                    continue;
                }
            } elseif ( false === Niwa_AI_Admin_Autonomy_Workspace::write_private_file( $work_path, '' ) ) {
                $failure = $this->build_main_coding_workspace_prepare_failure_intel( 'missing_work_copy_create_failed', array( 'target_path' => $target ) );
                $errors[] = array(
                    'target_path'    => $target,
                    'error'          => 'Unable to create empty work copy for missing target file.',
                    'failure_reason' => $failure['failure_reason'],
                    'suggested_fix'  => $failure['suggested_fix'],
                );
                continue;
            }

            $files[] = array(
                'target_path'      => $resolved['path'],
                'display_path'     => $resolved['display_path'],
                'target_existed'   => $exists,
                'backup_path'      => $exists ? $backup_path : '',
                'work_path'        => $work_path,
                'broken_path'      => '',
                'original_sha256'  => $exists ? hash_file( 'sha256', $resolved['path'] ) : '',
                'work_sha256'      => is_file( $work_path ) ? hash_file( 'sha256', $work_path ) : '',
                'original_line_endings' => $exists ? $this->get_main_coding_file_line_ending_profile( $resolved['path'] ) : array(),
                'work_line_endings'     => is_file( $work_path ) ? $this->get_main_coding_file_line_ending_profile( $work_path ) : array(),
                'status'           => 'prepared',
                'prepared_at'      => time(),
            );
        }

        $manifest = array(
            'operation_id' => $operation_id,
            'status'       => empty( $errors ) ? 'prepared' : 'prepared_with_errors',
            'created_at'   => time(),
            'updated_at'   => time(),
            'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'note'         => isset( $args['note'] ) ? trim( (string) $args['note'] ) : trim( (string) $prompt ),
            'requested_paths' => $requested_target_paths,
            'expanded_paths'  => $expanded_paths,
            'base_dir'     => $op_dir,
            'backup_dir'   => $backup_dir,
            'work_dir'     => $work_dir,
            'broken_dir'   => $broken_dir,
            'files'        => $files,
            'errors'       => $errors,
        );
        if ( ! $this->save_main_coding_manifest( $manifest ) ) {
            $failure = $this->build_main_coding_workspace_prepare_failure_intel( 'manifest_write_failed' );
            $this->append_main_coding_audit_event( 'workspace_prepare_end', array(
                'audit_id'     => $audit_id,
                'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id' => $operation_id,
                'success'      => false,
                'status'       => 'manifest_write_failed',
                'file_count'   => count( $files ),
                'errors'       => $errors,
            ) );
            return array(
                'success'      => false,
                'operation_id' => $operation_id,
                'status'       => 'manifest_write_failed',
                'failure_reason' => $failure['failure_reason'],
                'suggested_fix'  => $failure['suggested_fix'],
                'files'        => $files,
                'errors'       => array_merge(
                    $errors,
                    array(
                        array(
                            'error'          => 'Unable to write main coding manifest.',
                            'failure_reason' => $failure['failure_reason'],
                            'suggested_fix'  => $failure['suggested_fix'],
                        ),
                    )
                ),
                'audit_id'     => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $success = ! empty( $files ) && empty( $errors );
        $this->append_main_coding_audit_event( 'workspace_prepare_end', array(
            'audit_id'     => $audit_id,
            'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'operation_id' => $operation_id,
            'success'      => $success,
            'status'       => $manifest['status'],
            'file_count'   => count( $files ),
            'files'        => $files,
            'errors'       => $errors,
        ) );

        return array(
            'success'      => ! empty( $files ) && empty( $errors ),
            'operation_id' => $operation_id,
            'status'       => $manifest['status'],
            'requested_paths' => $requested_target_paths,
            'expanded_paths' => $expanded_paths,
            'workspace'    => array(
                'base_dir'   => $op_dir,
                'backup_dir' => $backup_dir,
                'work_dir'   => $work_dir,
                'broken_dir' => $broken_dir,
            ),
            'files'        => $files,
            'errors'       => $errors,
            'path_contract' => 'Use the returned files[].work_path values exactly for edits and files[].target_path/display_path exactly for verification. Do not inspect or edit assumed plugin aliases after prepare.',
            'next_step'    => 'Edit only the returned work_path files with main_coding_file_edit when possible (main_coding_shell_run only for bulk/shell-native edits), verify with main_coding_git_diff/main_coding_lint_check, then call main_coding_workspace_apply.',
            'audit_id'     => $audit_id,
            'audit_log_path' => $audit_path,
        );
    }

    private function classify_main_coding_workspace_prepare_resolve_failure( $error ) {
        $error_lc = strtolower( (string) $error );
        if ( false !== strpos( $error_lc, 'not a regular file' ) ) {
            return 'target_path_is_directory';
        }
        if ( false !== strpos( $error_lc, 'does not exist' ) || false !== strpos( $error_lc, 'missing target path' ) || false !== strpos( $error_lc, 'parent directory' ) ) {
            return 'file_missing';
        }

        return 'target_path_unresolved';
    }

    private function build_main_coding_workspace_prepare_failure_intel( $reason, $details = array() ) {
        $reason  = sanitize_key( (string) $reason );
        $details = is_array( $details ) ? $details : array();
        $target  = isset( $details['target_path'] ) ? (string) $details['target_path'] : '';

        $fixes = array(
            'not_main_runtime' => array(
                'action'      => 'retry_in_write_enabled_runtime',
                'explanation' => 'main_coding_workspace_prepare can run only in the founder main runtime or a write-enabled subagent.',
            ),
            'missing_target_paths' => array(
                'action'      => 'provide_target_paths',
                'explanation' => 'Pass one or more WordPress-root-relative or absolute target_paths. Existing-file globs are allowed, for example wp-content/plugins/example/includes/*.php.',
            ),
            'glob_invalid_pattern' => array(
                'action'      => 'fix_glob_pattern',
                'explanation' => 'The glob pattern could not be normalized. Use a WordPress-root-relative pattern under an allowed wp-content path.',
            ),
            'glob_no_matches' => array(
                'action'      => 'list_then_retry_exact_paths',
                'explanation' => 'The glob matched no existing files. Use file_list or batch_read to inspect the directory, then retry with a narrower pattern or explicit paths.',
            ),
            'glob_too_many_matches' => array(
                'action'      => 'narrow_glob_or_split_batches',
                'explanation' => 'A single prepare operation can expand at most 20 files. Narrow the pattern or split the work into multiple verified batches.',
            ),
            'too_many_target_paths' => array(
                'action'      => 'split_prepare_batches',
                'explanation' => 'A single prepare operation accepts at most 20 target files. Split larger work into multiple verified batches.',
            ),
            'workspace_base_unavailable' => array(
                'action'      => 'check_uploads_workspace_directory',
                'explanation' => 'Verify wp-content/uploads is writable and the niwa-ai workspace directory can be created.',
            ),
            'file_missing' => array(
                'action'      => 'retry_with_allow_missing_or_correct_path',
                'args_to_add' => array( 'allow_missing' => true ),
                'explanation' => 'If creating a new file under an allowed wp-content path, retry with allow_missing=true. Missing parent directories are staged and created only during workspace_apply. Otherwise re-check the path with file_list or file_read.',
            ),
            'target_path_unresolved' => array(
                'action'      => 'resolve_exact_target_path',
                'explanation' => 'Use file_list/file_read or a read-only shell pwd/ls check, then retry with the exact target path.',
            ),
            'target_path_is_directory' => array(
                'action'      => 'choose_file_inside_directory',
                'explanation' => 'workspace_prepare requires file paths, not directories.',
            ),
            'backup_copy_failed' => array(
                'action'      => 'check_workspace_permissions',
                'explanation' => 'The original file was resolved but the immutable backup copy could not be written.',
            ),
            'work_copy_failed' => array(
                'action'      => 'check_workspace_permissions',
                'explanation' => 'The original file was resolved but the editable work copy could not be written.',
            ),
            'missing_work_copy_create_failed' => array(
                'action'      => 'check_workspace_permissions',
                'explanation' => 'allow_missing was accepted but the empty work copy for the new target could not be created.',
            ),
            'manifest_write_failed' => array(
                'action'      => 'check_workspace_manifest_permissions',
                'explanation' => 'Prepared files may exist, but the manifest could not be saved, so the operation cannot safely continue.',
            ),
        );

        $suggested = isset( $fixes[ $reason ] ) ? $fixes[ $reason ] : $fixes['target_path_unresolved'];
        if ( '' !== $target ) {
            $suggested['target_path'] = $target;
        }

        return array(
            'failure_reason' => $reason,
            'suggested_fix'  => $suggested,
        );
    }

    private function execute_main_coding_workspace_apply( $args, $context, $mode = 'main', $options = array() ) {
        $args = is_array( $args ) ? $args : array();
        $audit_id = $this->new_main_coding_audit_id( 'apply' );
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            $audit_path = $this->append_main_coding_audit_event( 'workspace_apply_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'not_main_runtime',
            ) );
            return array(
                'success' => false,
                'error'   => 'main_coding_workspace_apply is available only in the main founder runtime or a write-enabled subagent runtime.',
                'audit_id' => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $operation_id = isset( $args['operation_id'] ) ? (string) $args['operation_id'] : '';
        $audit_path = $this->append_main_coding_audit_event( 'workspace_apply_start', array(
            'audit_id'     => $audit_id,
            'mode'         => (string) $mode,
            'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'operation_id' => $operation_id,
            'target_paths' => isset( $args['target_paths'] ) && is_array( $args['target_paths'] ) ? $args['target_paths'] : array(),
        ) );
        $loaded = $this->load_main_coding_manifest( isset( $args['operation_id'] ) ? (string) $args['operation_id'] : '' );
        if ( empty( $loaded['success'] ) ) {
            $loaded['audit_id']       = $audit_id;
            $loaded['audit_log_path'] = $audit_path;
            $this->append_main_coding_audit_event( 'workspace_apply_end', array(
                'audit_id'     => $audit_id,
                'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id' => $operation_id,
                'success'      => false,
                'status'       => 'manifest_load_failed',
                'error'        => isset( $loaded['error'] ) ? (string) $loaded['error'] : 'Unable to load main coding manifest.',
            ) );
            return $loaded;
        }

        $manifest = isset( $loaded['manifest'] ) && is_array( $loaded['manifest'] ) ? $loaded['manifest'] : array();
        $selected = $this->select_main_coding_manifest_files( $manifest, isset( $args['target_paths'] ) && is_array( $args['target_paths'] ) ? $args['target_paths'] : array() );
        if ( empty( $selected ) ) {
            $this->append_main_coding_audit_event( 'workspace_apply_end', array(
                'audit_id'     => $audit_id,
                'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : $operation_id,
                'success'      => false,
                'status'       => 'no_matching_files',
            ) );
            return array(
                'success' => false,
                'error'   => 'No prepared files matched the apply request.',
                'audit_id' => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $preflight = $this->preflight_main_coding_workspace_apply( $manifest, $selected );
        if ( ! empty( $preflight['manifest'] ) && is_array( $preflight['manifest'] ) ) {
            $manifest = $preflight['manifest'];
        }
        $this->save_main_coding_manifest( $manifest );
        if ( empty( $preflight['success'] ) ) {
            $this->append_main_coding_audit_event( 'workspace_apply_end', array(
                'audit_id'     => $audit_id,
                'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
                'success'      => false,
                'status'       => 'apply_preflight_blocked',
                'preflight'    => isset( $preflight['reports'] ) ? $preflight['reports'] : array(),
                'errors'       => isset( $preflight['errors'] ) ? $preflight['errors'] : array(),
            ) );

            return array(
                'success'        => false,
                'operation_id'   => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
                'status'         => 'apply_preflight_blocked',
                'error'          => 'Workspace apply preflight blocked unsafe file replacement.',
                'preflight'      => isset( $preflight['reports'] ) ? $preflight['reports'] : array(),
                'errors'         => isset( $preflight['errors'] ) ? $preflight['errors'] : array(),
                'next_step'      => 'Inspect the reported work_path files, preserve original line endings, make a semantic edit, then run main_coding_git_diff/main_coding_lint_check before applying again.',
                'audit_id'       => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $applied = array();
        $errors  = array();
        foreach ( $selected as $index => $file ) {
            $work_path   = isset( $file['work_path'] ) ? (string) $file['work_path'] : '';
            $target_path = isset( $file['target_path'] ) ? (string) $file['target_path'] : '';
            if ( '' === $work_path || ! is_file( $work_path ) ) {
                $errors[] = array(
                    'display_path' => isset( $file['display_path'] ) ? (string) $file['display_path'] : $target_path,
                    'error'        => 'Work copy is missing.',
                );
                continue;
            }
            if ( '' === $target_path ) {
                $errors[] = array(
                    'display_path' => isset( $file['display_path'] ) ? (string) $file['display_path'] : '',
                    'error'        => 'Target path is missing from manifest.',
                );
                continue;
            }

            $parent = dirname( $target_path );
            if ( ! is_dir( $parent ) ) {
                wp_mkdir_p( $parent );
            }
            $replace = $this->atomic_replace_main_coding_target( $work_path, $target_path );
            if ( empty( $replace['success'] ) ) {
                $errors[] = array(
                    'display_path' => isset( $file['display_path'] ) ? (string) $file['display_path'] : $target_path,
                    'error'        => isset( $replace['error'] ) ? (string) $replace['error'] : 'Unable to atomically replace target with work copy.',
                );
                continue;
            }
            $target_sha = isset( $replace['sha256'] ) ? (string) $replace['sha256'] : hash_file( 'sha256', $target_path );

            $applied[] = array(
                'index'        => $index,
                'target_path'  => $target_path,
                'display_path' => isset( $file['display_path'] ) ? (string) $file['display_path'] : $target_path,
                'work_path'    => $work_path,
                'sha256'       => $target_sha,
            );
            $manifest['files'][ $index ]['status']         = 'applied';
            $manifest['files'][ $index ]['applied_at']     = time();
            $manifest['files'][ $index ]['applied_sha256'] = $target_sha;
            $manifest['files'][ $index ]['work_sha256']    = hash_file( 'sha256', $work_path );
            $manifest['files'][ $index ]['target_sha256']  = $target_sha;
            $manifest['files'][ $index ]['work_line_endings'] = $this->get_main_coding_file_line_ending_profile( $work_path );
            $manifest['files'][ $index ]['target_line_endings'] = $this->get_main_coding_file_line_ending_profile( $target_path );
        }

        if ( ! empty( $errors ) ) {
            $rollback = $this->rollback_main_coding_manifest_files( $manifest, $selected, 'apply_copy_failed' );
            $manifest['status']     = 'rolled_back_after_apply_error';
            $manifest['updated_at'] = time();
            $manifest['errors']     = array_merge( isset( $manifest['errors'] ) && is_array( $manifest['errors'] ) ? $manifest['errors'] : array(), $errors );
            $this->save_main_coding_manifest( $manifest );
            $this->append_main_coding_audit_event( 'workspace_apply_end', array(
                'audit_id'     => $audit_id,
                'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
                'success'      => false,
                'status'       => 'rolled_back_after_apply_error',
                'applied'      => $applied,
                'errors'       => $errors,
                'rollback'     => $rollback,
            ) );

            return array(
                'success'            => false,
                'operation_id'       => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
                'applied'            => $applied,
                'errors'             => $errors,
                'rollback_performed' => ! empty( $rollback['success'] ),
                'rollback'           => $rollback,
                'audit_id'           => $audit_id,
                'audit_log_path'     => $audit_path,
            );
        }

        if ( ! empty( $args['skip_health_check'] ) ) {
            $health = array(
                'success' => true,
                'skipped' => true,
                'reason'  => 'Health check explicitly skipped by skip_health_check=true. Agent confirmed safety via lint_check/git_diff/test_run instead.',
            );
        } else {
            $health = $this->run_main_coding_health_check(
                isset( $args['health_check_url'] ) ? (string) $args['health_check_url'] : '',
                isset( $args['health_check_timeout_seconds'] ) ? intval( $args['health_check_timeout_seconds'] ) : 8
            );
        }

        if ( empty( $health['success'] ) ) {
            $rollback = $this->rollback_main_coding_manifest_files( $manifest, $selected, 'watchdog_health_check_failed' );
            $manifest['status']      = 'rolled_back_by_watchdog';
            $manifest['updated_at']  = time();
            $manifest['watchdog']    = $health;
            $manifest['last_result'] = array(
                'applied'  => $applied,
                'rollback' => $rollback,
            );
            $this->save_main_coding_manifest( $manifest );
            $this->append_main_coding_audit_event( 'workspace_apply_end', array(
                'audit_id'     => $audit_id,
                'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
                'success'      => false,
                'status'       => 'rolled_back_by_watchdog',
                'applied'      => $applied,
                'watchdog'     => $health,
                'rollback'     => $rollback,
            ) );

            return array(
                'success'            => false,
                'operation_id'       => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
                'applied'            => $applied,
                'watchdog'           => $health,
                'rollback_performed' => ! empty( $rollback['success'] ),
                'rollback'           => $rollback,
                'next_step'          => 'Inspect debug.log and the broken_path files, fix the work copies, then apply again.',
                'audit_id'           => $audit_id,
                'audit_log_path'     => $audit_path,
            );
        }

        $manifest['status']      = 'applied';
        $manifest['updated_at']  = time();
        $manifest['watchdog']    = $health;
        $manifest['last_result'] = array(
            'applied' => $applied,
        );
        $this->save_main_coding_manifest( $manifest );
        $this->append_main_coding_audit_event( 'workspace_apply_end', array(
            'audit_id'     => $audit_id,
            'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
            'success'      => true,
            'status'       => 'applied',
            'applied'      => $applied,
            'watchdog'     => $health,
        ) );

        return array(
            'success'      => true,
            'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
            'applied'      => $applied,
            'watchdog'     => $health,
            'audit_id'     => $audit_id,
            'audit_log_path' => $audit_path,
        );
    }

    private function atomic_replace_main_coding_target( $source_path, $target_path ) {
        $source_path = (string) $source_path;
        $target_path = (string) $target_path;
        if ( '' === $source_path || ! is_file( $source_path ) ) {
            return array(
                'success' => false,
                'error'   => 'Source file for atomic replace is missing.',
            );
        }
        if ( '' === $target_path ) {
            return array(
                'success' => false,
                'error'   => 'Target path for atomic replace is empty.',
            );
        }

        $parent = dirname( $target_path );
        if ( ! is_dir( $parent ) ) {
            wp_mkdir_p( $parent );
        }
        if ( ! is_dir( $parent ) || ! is_writable( $parent ) ) {
            return array(
                'success' => false,
                'error'   => 'Target directory is not writable for atomic replace.',
            );
        }

        $tmp = @tempnam( $parent, '.niwa-apply-' );
        if ( false === $tmp || '' === $tmp ) {
            return array(
                'success' => false,
                'error'   => 'Unable to allocate temporary file for atomic replace.',
            );
        }

        $tmp = (string) $tmp;
        if ( ! @copy( $source_path, $tmp ) ) {
            @unlink( $tmp );
            return array(
                'success' => false,
                'error'   => 'Unable to copy source into temporary file for atomic replace.',
            );
        }

        $permissions = is_file( $target_path ) ? ( @fileperms( $target_path ) & 0777 ) : 0644;
        if ( ! is_int( $permissions ) || $permissions <= 0 ) {
            $permissions = 0644;
        }
        @chmod( $tmp, $permissions );

        if ( ! @rename( $tmp, $target_path ) ) {
            @unlink( $tmp );
            return array(
                'success' => false,
                'error'   => 'Atomic rename failed; target was left untouched.',
            );
        }

        clearstatcache( true, $target_path );
        return array(
            'success' => true,
            'sha256'  => is_file( $target_path ) ? hash_file( 'sha256', $target_path ) : '',
        );
    }

    private function execute_main_coding_workspace_rollback( $args, $context, $mode = 'main', $options = array() ) {
        $args = is_array( $args ) ? $args : array();
        $audit_id = $this->new_main_coding_audit_id( 'rollback' );
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            $audit_path = $this->append_main_coding_audit_event( 'workspace_rollback_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'not_main_runtime',
            ) );
            return array(
                'success' => false,
                'error'   => 'main_coding_workspace_rollback is available only in the main founder runtime or a write-enabled subagent runtime.',
                'audit_id' => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $operation_id = isset( $args['operation_id'] ) ? (string) $args['operation_id'] : '';
        $audit_path = $this->append_main_coding_audit_event( 'workspace_rollback_start', array(
            'audit_id'     => $audit_id,
            'mode'         => (string) $mode,
            'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'operation_id' => $operation_id,
            'target_paths' => isset( $args['target_paths'] ) && is_array( $args['target_paths'] ) ? $args['target_paths'] : array(),
            'reason'       => isset( $args['reason'] ) ? trim( (string) $args['reason'] ) : 'manual_rollback',
        ) );
        $loaded = $this->load_main_coding_manifest( isset( $args['operation_id'] ) ? (string) $args['operation_id'] : '' );
        if ( empty( $loaded['success'] ) ) {
            $loaded['audit_id']       = $audit_id;
            $loaded['audit_log_path'] = $audit_path;
            $this->append_main_coding_audit_event( 'workspace_rollback_end', array(
                'audit_id'     => $audit_id,
                'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id' => $operation_id,
                'success'      => false,
                'status'       => 'manifest_load_failed',
                'error'        => isset( $loaded['error'] ) ? (string) $loaded['error'] : 'Unable to load main coding manifest.',
            ) );
            return $loaded;
        }

        $manifest = isset( $loaded['manifest'] ) && is_array( $loaded['manifest'] ) ? $loaded['manifest'] : array();
        $selected = $this->select_main_coding_manifest_files( $manifest, isset( $args['target_paths'] ) && is_array( $args['target_paths'] ) ? $args['target_paths'] : array() );
        if ( empty( $selected ) ) {
            $this->append_main_coding_audit_event( 'workspace_rollback_end', array(
                'audit_id'     => $audit_id,
                'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : $operation_id,
                'success'      => false,
                'status'       => 'no_matching_files',
            ) );
            return array(
                'success' => false,
                'error'   => 'No prepared files matched the rollback request.',
                'audit_id' => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $reason   = isset( $args['reason'] ) ? trim( (string) $args['reason'] ) : 'manual_rollback';
        $rollback = $this->rollback_main_coding_manifest_files( $manifest, $selected, $reason );
        $manifest['status']      = ! empty( $rollback['success'] ) ? 'rolled_back_manual' : 'rollback_failed';
        $manifest['updated_at']  = time();
        $manifest['last_result'] = array(
            'rollback' => $rollback,
        );
        $this->save_main_coding_manifest( $manifest );
        $this->append_main_coding_audit_event( 'workspace_rollback_end', array(
            'audit_id'     => $audit_id,
            'session_id'   => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
            'success'      => ! empty( $rollback['success'] ),
            'status'       => $manifest['status'],
            'rollback'     => $rollback,
        ) );

        return array_merge(
            array(
                'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
                'audit_id'     => $audit_id,
                'audit_log_path' => $audit_path,
            ),
            $rollback
        );
    }

    /**
     * main_coding_git_diff — Show diff between work copies and original targets.
     *
     * Without applying. Useful for the agent to verify edits before main_coding_workspace_apply.
     */
    private function execute_main_coding_git_diff( $args, $context, $mode = 'main', $options = array() ) {
        $args = is_array( $args ) ? $args : array();
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'main_coding_git_diff is available only in the main founder runtime or a write-enabled subagent runtime.',
            );
        }

        $loaded = $this->load_main_coding_manifest( isset( $args['operation_id'] ) ? (string) $args['operation_id'] : '' );
        if ( empty( $loaded['success'] ) ) {
            return $loaded;
        }

        $manifest = isset( $loaded['manifest'] ) && is_array( $loaded['manifest'] ) ? $loaded['manifest'] : array();
        $files    = isset( $manifest['files'] ) && is_array( $manifest['files'] ) ? $manifest['files'] : array();
        $selected = $this->select_main_coding_manifest_files( $manifest, isset( $args['target_paths'] ) && is_array( $args['target_paths'] ) ? $args['target_paths'] : array() );
        $context_lines = isset( $args['context_lines'] ) ? max( 0, min( 10, intval( $args['context_lines'] ) ) ) : 3;
        $max_diff_lines_per_file = isset( $args['max_lines_per_file'] ) ? max( 20, min( 5000, intval( $args['max_lines_per_file'] ) ) ) : 400;

        $results = array();
        foreach ( $selected as $file ) {
            $work_path   = isset( $file['work_path'] ) ? (string) $file['work_path'] : '';
            $orig_path   = ! empty( $file['target_existed'] ) && ! empty( $file['backup_path'] ) ? (string) $file['backup_path'] : ( isset( $file['target_path'] ) ? (string) $file['target_path'] : '' );
            $display     = isset( $file['display_path'] ) ? (string) $file['display_path'] : '';

            if ( '' === $work_path || ! is_file( $work_path ) ) {
                $results[] = array(
                    'display_path' => $display,
                    'error'        => 'Work copy missing.',
                );
                continue;
            }

            if ( '' === $orig_path || ! is_file( $orig_path ) ) {
                $results[] = array(
                    'display_path' => $display,
                    'status'       => 'new_file',
                    'work_size'    => @filesize( $work_path ),
                );
                continue;
            }

            if ( $this->is_proc_open_runtime_available() ) {
                $diff_cmd = sprintf( 'diff -u --label original --label work -U%d %s %s', $context_lines, escapeshellarg( $orig_path ), escapeshellarg( $work_path ) );
                $run      = $this->run_main_coding_shell_process( $diff_cmd, array(), dirname( $work_path ), 30, $max_diff_lines_per_file * 200 );
                $diff_out = isset( $run['stdout'] ) ? (string) $run['stdout'] : '';
                $exit_code = isset( $run['exit_code'] ) ? intval( $run['exit_code'] ) : -1;
                $diff_engine = 'process_diff';
            } else {
                $native_diff = $this->build_main_coding_native_diff_summary( $orig_path, $work_path, $context_lines, $max_diff_lines_per_file );
                $diff_out    = isset( $native_diff['diff'] ) ? (string) $native_diff['diff'] : '';
                $exit_code   = isset( $native_diff['exit_code'] ) ? intval( $native_diff['exit_code'] ) : -1;
                $diff_engine = 'php_native_summary';
            }
            $line_count = '' !== $diff_out ? substr_count( $diff_out, "\n" ) + 1 : 0;
            $safety = $this->build_main_coding_apply_file_report( $file );
            $results[] = array(
                'display_path' => $display,
                'status'       => '' === $diff_out ? 'unchanged' : 'modified',
                'diff'         => $diff_out,
                'diff_lines'   => $line_count,
                'exit_code'    => $exit_code,
                'diff_engine'  => $diff_engine,
                'safety'       => isset( $safety['summary'] ) ? $safety['summary'] : $safety,
            );
        }

        return array(
            'ok'           => true,
            'success'      => true,
            'operation_id' => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
            'file_count'   => count( $results ),
            'files'        => $results,
            'hint'         => 'Verify the diff before calling main_coding_workspace_apply. Run main_coding_lint_check to validate PHP syntax of work copies.',
        );
    }

    private function build_main_coding_native_diff_summary( $original_path, $work_path, $context_lines, $max_lines ) {
        $original = is_file( $original_path ) ? file( $original_path, FILE_IGNORE_NEW_LINES ) : array();
        $work     = is_file( $work_path ) ? file( $work_path, FILE_IGNORE_NEW_LINES ) : array();
        if ( false === $original ) {
            $original = array();
        }
        if ( false === $work ) {
            $work = array();
        }

        if ( $original === $work ) {
            return array(
                'diff'      => '',
                'exit_code' => 0,
            );
        }

        $original_count = count( $original );
        $work_count     = count( $work );
        $first          = 0;
        $min_count      = min( $original_count, $work_count );
        while ( $first < $min_count && $original[ $first ] === $work[ $first ] ) {
            $first++;
        }

        $last_original = $original_count - 1;
        $last_work     = $work_count - 1;
        while ( $last_original >= $first && $last_work >= $first && $original[ $last_original ] === $work[ $last_work ] ) {
            $last_original--;
            $last_work--;
        }

        $context_lines = max( 0, min( 10, intval( $context_lines ) ) );
        $max_lines     = max( 20, min( 5000, intval( $max_lines ) ) );
        $start         = max( 0, $first - $context_lines );
        $end_original  = min( $original_count - 1, $last_original + $context_lines );
        $end_work      = min( $work_count - 1, $last_work + $context_lines );
        $lines         = array(
            '--- original',
            '+++ work',
            '@@ php-native-summary original:' . ( $start + 1 ) . ' work:' . ( $start + 1 ) . ' @@',
        );

        for ( $i = $start; $i <= $end_original && count( $lines ) < $max_lines; $i++ ) {
            if ( $i < $first || $i > $last_original ) {
                $lines[] = ' ' . ( isset( $original[ $i ] ) ? (string) $original[ $i ] : '' );
            } else {
                $lines[] = '-' . ( isset( $original[ $i ] ) ? (string) $original[ $i ] : '' );
            }
        }

        for ( $i = $start; $i <= $end_work && count( $lines ) < $max_lines; $i++ ) {
            if ( $i >= $first && $i <= $last_work ) {
                $lines[] = '+' . ( isset( $work[ $i ] ) ? (string) $work[ $i ] : '' );
            }
        }

        if ( count( $lines ) >= $max_lines ) {
            $lines[] = '[diff truncated by php-native-summary]';
        }

        return array(
            'diff'      => implode( "\n", $lines ),
            'exit_code' => 1,
        );
    }

    private function run_main_coding_php_syntax_check( $path, $php_binary = 'php' ) {
        if ( $this->is_proc_open_runtime_available() ) {
            $run = $this->run_main_coding_shell_process( '', array( $php_binary, '-l', $path ), dirname( $path ), 20, 8000 );
            $run['engine'] = 'php_lint_process';
            return $run;
        }

        if ( function_exists( 'opcache_compile_file' ) && filter_var( ini_get( 'opcache.enable' ), FILTER_VALIDATE_BOOLEAN ) ) {
            $errors = array();
            $handler = function ( $errno, $errstr ) use ( &$errors ) {
                $errors[] = trim( (string) $errstr );
                return true;
            };
            set_error_handler( $handler );
            $ok = false;
            try {
                $ok = @opcache_compile_file( $path );
            } catch ( \Throwable $e ) {
                $errors[] = $e->getMessage();
                $ok = false;
            }
            restore_error_handler();

            return array(
                'ok'        => ! empty( $ok ),
                'success'   => ! empty( $ok ),
                'exit_code' => ! empty( $ok ) ? 0 : 1,
                'stdout'    => ! empty( $ok ) ? 'PHP syntax check passed via opcache_compile_file.' : '',
                'stderr'    => implode( "\n", array_filter( $errors ) ),
                'engine'    => 'opcache_compile_file',
            );
        }

        $source = @file_get_contents( $path );
        if ( false === $source ) {
            return array(
                'ok'        => false,
                'success'   => false,
                'exit_code' => 1,
                'stdout'    => '',
                'stderr'    => 'Unable to read PHP work copy for syntax fallback.',
                'engine'    => 'php_tokenizer_fallback',
            );
        }

        try {
            token_get_all( $source );
        } catch ( \Throwable $e ) {
            return array(
                'ok'        => false,
                'success'   => false,
                'exit_code' => 1,
                'stdout'    => '',
                'stderr'    => $e->getMessage(),
                'engine'    => 'php_tokenizer_fallback',
            );
        }

        return array(
            'ok'        => true,
            'success'   => true,
            'exit_code' => 0,
            'stdout'    => 'Tokenizer fallback completed. Full php -l is unavailable because proc_open is disabled.',
            'stderr'    => '',
            'engine'    => 'php_tokenizer_fallback',
        );
    }

    /**
     * main_coding_lint_check — Run php -l on prepared work copies.
     *
     * Catches PHP syntax errors BEFORE apply, preventing watchdog rollback cycles.
     */
    private function execute_main_coding_lint_check( $args, $context, $mode = 'main', $options = array() ) {
        $args = is_array( $args ) ? $args : array();
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'main_coding_lint_check is available only in the main founder runtime or a write-enabled subagent runtime.',
            );
        }

        $loaded = $this->load_main_coding_manifest( isset( $args['operation_id'] ) ? (string) $args['operation_id'] : '' );
        if ( empty( $loaded['success'] ) ) {
            return $loaded;
        }

        $manifest = isset( $loaded['manifest'] ) && is_array( $loaded['manifest'] ) ? $loaded['manifest'] : array();
        $selected = $this->select_main_coding_manifest_files( $manifest, isset( $args['target_paths'] ) && is_array( $args['target_paths'] ) ? $args['target_paths'] : array() );

        $results       = array();
        $error_count   = 0;
        $php_binary    = isset( $args['php_binary'] ) && '' !== trim( (string) $args['php_binary'] ) ? trim( (string) $args['php_binary'] ) : 'php';
        foreach ( $selected as $file ) {
            $work_path = isset( $file['work_path'] ) ? (string) $file['work_path'] : '';
            $display   = isset( $file['display_path'] ) ? (string) $file['display_path'] : '';

            if ( '' === $work_path || ! is_file( $work_path ) ) {
                $results[] = array( 'display_path' => $display, 'status' => 'missing', 'error' => 'Work copy missing.' );
                $error_count++;
                continue;
            }

            if ( ! preg_match( '/\.php$/i', $work_path ) ) {
                $results[] = array( 'display_path' => $display, 'status' => 'skipped', 'reason' => 'Not a PHP file.' );
                continue;
            }

            $run = $this->run_main_coding_php_syntax_check( $work_path, $php_binary );
            $ok  = ! empty( $run['success'] );
            if ( ! $ok ) {
                $error_count++;
            }
            $results[] = array(
                'display_path' => $display,
                'status'       => $ok ? 'ok' : 'error',
                'stdout'       => isset( $run['stdout'] ) ? trim( (string) $run['stdout'] ) : '',
                'stderr'       => isset( $run['stderr'] ) ? trim( (string) $run['stderr'] ) : '',
                'exit_code'    => isset( $run['exit_code'] ) ? intval( $run['exit_code'] ) : -1,
                'lint_engine'  => isset( $run['engine'] ) ? (string) $run['engine'] : '',
            );
        }

        return array(
            'ok'             => 0 === $error_count,
            'success'        => 0 === $error_count,
            'operation_id'   => isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '',
            'file_count'     => count( $results ),
            'error_count'    => $error_count,
            'files'          => $results,
            'hint'           => $error_count > 0 ? 'Fix syntax errors in work copies via main_coding_file_edit when possible, or main_coding_shell_run for bulk/shell-native edits, before calling main_coding_workspace_apply.' : 'All PHP work copies pass syntax check. Safe to call main_coding_workspace_apply.',
        );
    }

    /**
     * main_coding_test_run — Run PHPUnit or Jest tests for verification.
     *
     * Accepts test_path (specific file/dir) or test_filter (PHPUnit --filter).
     * Returns full output for the agent to diagnose failures.
     */
    private function execute_main_coding_test_run( $args, $context, $mode = 'main', $options = array() ) {
        $args = is_array( $args ) ? $args : array();
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'main_coding_test_run is available only in the main founder runtime or a write-enabled subagent runtime.',
            );
        }

        $framework = isset( $args['framework'] ) ? sanitize_key( (string) $args['framework'] ) : 'phpunit';
        if ( ! in_array( $framework, array( 'phpunit', 'jest' ), true ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'Unsupported framework. Use phpunit or jest.',
            );
        }

        $cwd_result = $this->resolve_main_coding_cwd( isset( $args['cwd'] ) ? (string) $args['cwd'] : '' );
        if ( empty( $cwd_result['ok'] ) ) {
            return array( 'ok' => false, 'success' => false, 'error' => isset( $cwd_result['error'] ) ? (string) $cwd_result['error'] : 'Invalid cwd.' );
        }

        $test_path   = isset( $args['test_path'] ) ? trim( (string) $args['test_path'] ) : '';
        $test_filter = isset( $args['test_filter'] ) ? trim( (string) $args['test_filter'] ) : '';
        $timeout     = isset( $args['timeout_seconds'] ) ? max( 5, min( 600, intval( $args['timeout_seconds'] ) ) ) : 120;
        $max_output  = isset( $args['max_output_chars'] ) ? max( 1000, min( 200000, intval( $args['max_output_chars'] ) ) ) : 60000;

        if ( 'phpunit' === $framework ) {
            $binary = isset( $args['binary'] ) && '' !== trim( (string) $args['binary'] ) ? trim( (string) $args['binary'] ) : 'vendor/bin/phpunit';
            $cmd_parts = array( $binary );
            if ( '' !== $test_filter ) {
                $cmd_parts[] = '--filter';
                $cmd_parts[] = escapeshellarg( $test_filter );
            }
            if ( '' !== $test_path ) {
                $cmd_parts[] = escapeshellarg( $test_path );
            }
            $cmd_parts[] = '--colors=never';
            $cmd = implode( ' ', $cmd_parts );
        } else {
            $binary = isset( $args['binary'] ) && '' !== trim( (string) $args['binary'] ) ? trim( (string) $args['binary'] ) : 'npx jest';
            $cmd_parts = array( $binary );
            if ( '' !== $test_filter ) {
                $cmd_parts[] = '-t';
                $cmd_parts[] = escapeshellarg( $test_filter );
            }
            if ( '' !== $test_path ) {
                $cmd_parts[] = escapeshellarg( $test_path );
            }
            $cmd_parts[] = '--colors=false';
            $cmd = implode( ' ', $cmd_parts );
        }

        $run = $this->run_main_coding_shell_process( $cmd, array(), $cwd_result['path'], $timeout, $max_output );

        return array(
            'ok'          => ! empty( $run['success'] ),
            'success'     => ! empty( $run['success'] ),
            'framework'   => $framework,
            'command'     => $cmd,
            'cwd'         => $cwd_result['display_path'],
            'exit_code'   => isset( $run['exit_code'] ) ? intval( $run['exit_code'] ) : -1,
            'timed_out'   => ! empty( $run['timed_out'] ),
            'duration_ms' => isset( $run['duration_ms'] ) ? $run['duration_ms'] : 0,
            'stdout'      => isset( $run['stdout'] ) ? (string) $run['stdout'] : '',
            'stderr'      => isset( $run['stderr'] ) ? (string) $run['stderr'] : '',
        );
    }

    private function resolve_main_coding_cwd( $cwd ) {
        $base = defined( 'ABSPATH' ) ? wp_normalize_path( untrailingslashit( ABSPATH ) ) : ( defined( 'NIWA_AI_DIR' ) ? wp_normalize_path( untrailingslashit( NIWA_AI_DIR ) ) : '' );
        if ( '' === $base ) {
            return array(
                'ok'    => false,
                'error' => 'No base path is available.',
            );
        }

        $cwd       = trim( (string) $cwd );
        $candidate = '' === $cwd ? $base : wp_normalize_path( str_replace( '\\', '/', $cwd ) );
        if ( ! preg_match( '#^[A-Za-z]:/#', $candidate ) && 0 !== strpos( $candidate, '/' ) ) {
            $candidate = wp_normalize_path( trailingslashit( $base ) . $candidate );
        }

        $real = realpath( $candidate );
        if ( false === $real || ! is_dir( $real ) ) {
            return array(
                'ok'    => false,
                'error' => 'cwd does not exist or is not a directory.',
            );
        }

        return array(
            'ok'           => true,
            'path'         => wp_normalize_path( $real ),
            'display_path' => $this->display_main_coding_path( wp_normalize_path( $real ) ),
        );
    }

    private function is_proc_open_runtime_available(): bool {
        if ( class_exists( 'Niwa_AI_Admin_Autonomy_Process_Runtime' ) ) {
            return Niwa_AI_Admin_Autonomy_Process_Runtime::is_proc_open_available();
        }

        if ( defined( 'NIWA_AI_FORCE_PROC_OPEN_UNAVAILABLE' ) && NIWA_AI_FORCE_PROC_OPEN_UNAVAILABLE ) {
            return false;
        }

        if ( function_exists( 'get_option' ) ) {
            $forced_unavailable = get_option( 'niwa_ai_force_proc_open_unavailable', false );
            if ( ! empty( $forced_unavailable ) && '0' !== (string) $forced_unavailable ) {
                return false;
            }
        }

        return function_exists( 'proc_open' ) && function_exists( 'proc_get_status' ) && function_exists( 'proc_close' );
    }

    private function proc_open_unavailable_result(): array {
        return array(
            'ok'        => false,
            'success'   => false,
            'exit_code' => -1,
            'timed_out' => false,
            'stdout'    => '',
            'stderr'    => 'proc_open runtime is not available on this server.',
            'error'     => 'proc_open_unavailable',
        );
    }

    private function run_main_coding_shell_process( $command, $argv, $cwd, $timeout, $limit ) {
        if ( ! $this->is_proc_open_runtime_available() ) {
            return $this->proc_open_unavailable_result();
        }

        $descriptors = array(
            0 => array( 'pipe', 'r' ),
            1 => array( 'pipe', 'w' ),
            2 => array( 'pipe', 'w' ),
        );

        $use_raw        = '' !== trim( (string) $command );
        $timeout_binary = $this->resolve_main_coding_timeout_binary();
        if ( $timeout > 0 && '' !== $timeout_binary ) {
            $timeout_arg = max( 1, intval( $timeout ) ) . 's';
            if ( $use_raw ) {
                $argv    = array( $timeout_binary, '--kill-after=5s', $timeout_arg, 'sh', '-lc', (string) $command );
                $command = '';
                $use_raw = false;
            } elseif ( ! empty( $argv ) ) {
                $argv = array_merge( array( $timeout_binary, '--kill-after=5s', $timeout_arg ), $argv );
            }
        }

        $old_umask = umask( 0077 );
        try {
            $process = $use_raw
                ? @proc_open( (string) $command, $descriptors, $pipes, $cwd )
                : @proc_open( $argv, $descriptors, $pipes, $cwd, null, array( 'bypass_shell' => true ) );
        } finally {
            umask( $old_umask );
        }
        if ( ! is_resource( $process ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'Unable to start process.',
            );
        }

        fclose( $pipes[0] );
        stream_set_blocking( $pipes[1], false );
        stream_set_blocking( $pipes[2], false );

        $stdout    = '';
        $stderr    = '';
        $start     = microtime( true );
        $timed_out = false;
        $exit_code = null;
        $limit     = max( 0, intval( $limit ) );
        $timeout   = max( 0, intval( $timeout ) );

        while ( true ) {
            $stdout .= (string) stream_get_contents( $pipes[1] );
            $stderr .= (string) stream_get_contents( $pipes[2] );

            if ( $limit > 0 && strlen( $stdout ) + strlen( $stderr ) > $limit * 2 ) {
                $stdout = substr( $stdout, 0, $limit );
                $stderr = substr( $stderr, 0, $limit );
            }

            $status = proc_get_status( $process );
            if ( empty( $status['running'] ) ) {
                $exit_code = isset( $status['exitcode'] ) ? intval( $status['exitcode'] ) : null;
                break;
            }

            if ( $timeout > 0 && ( microtime( true ) - $start ) >= $timeout ) {
                $timed_out = true;
                proc_terminate( $process );
                usleep( 250000 );
                $status = proc_get_status( $process );
                if ( ! empty( $status['running'] ) ) {
                    @proc_terminate( $process, 9 );
                }
                break;
            }

            usleep( 50000 );
        }

        $stdout .= (string) stream_get_contents( $pipes[1] );
        $stderr .= (string) stream_get_contents( $pipes[2] );
        fclose( $pipes[1] );
        fclose( $pipes[2] );
        $close_code = proc_close( $process );
        if ( null === $exit_code && is_int( $close_code ) ) {
            $exit_code = $close_code;
        }

        return array(
            'ok'          => ! $timed_out && 0 === intval( $exit_code ),
            'success'     => ! $timed_out && 0 === intval( $exit_code ),
            'exit_code'   => null === $exit_code ? -1 : intval( $exit_code ),
            'timed_out'   => $timed_out,
            'duration_ms' => round( ( microtime( true ) - $start ) * 1000, 2 ),
            'stdout'      => $this->truncate_main_coding_shell_output( $stdout, $limit ),
            'stderr'      => $this->truncate_main_coding_shell_output( $stderr, $limit ),
        );
    }

    private function resolve_main_coding_timeout_binary() {
        if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ) {
            return '';
        }

        foreach ( array( '/usr/bin/timeout', '/bin/timeout' ) as $candidate ) {
            if ( is_executable( $candidate ) ) {
                return $candidate;
            }
        }

        return '';
    }

    private function new_main_coding_audit_id( $prefix = 'event' ) {
        $prefix = sanitize_key( (string) $prefix );
        if ( '' === $prefix ) {
            $prefix = 'event';
        }

        if ( function_exists( 'wp_generate_uuid4' ) ) {
            return $prefix . '-' . wp_generate_uuid4();
        }

        return $prefix . '-' . uniqid( '', true );
    }

    private function get_main_coding_audit_dir() {
        $base = $this->get_main_coding_workspace_base_dir();
        if ( '' === $base ) {
            return '';
        }

        $dir = wp_normalize_path( trailingslashit( $base ) . 'audit' );
        if ( ! is_dir( $dir ) ) {
            Niwa_AI_Admin_Autonomy_Workspace::ensure_private_directory( $dir );
        }
        $this->write_main_coding_workspace_deny_files( $dir );

        return is_dir( $dir ) ? $dir : '';
    }

    private function get_main_coding_audit_path() {
        $dir = $this->get_main_coding_audit_dir();
        if ( '' === $dir ) {
            return '';
        }

        return wp_normalize_path( trailingslashit( $dir ) . 'main-coding-audit-' . gmdate( 'Y-m-d' ) . '.jsonl' );
    }

    private function append_main_coding_audit_event( $event, $context = array() ) {
        $path = $this->get_main_coding_audit_path();
        if ( '' === $path ) {
            return '';
        }

        $context = is_array( $context ) ? $context : array( 'value' => $context );
        $entry   = array(
            'logged_at_utc' => gmdate( 'c' ),
            'event'         => sanitize_key( (string) $event ),
            'plugin_version' => defined( 'NIWA_AI_VERSION' ) ? NIWA_AI_VERSION : '',
            'context'       => $this->sanitize_main_coding_audit_value( $context ),
        );
        $line = wp_json_encode( $entry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
        if ( ! is_string( $line ) || '' === $line ) {
            return $path;
        }

        Niwa_AI_Admin_Autonomy_Workspace::write_private_file( $path, $line . "\n", FILE_APPEND | LOCK_EX );
        @chmod( $path, 0600 );

        return $path;
    }

    private function sanitize_main_coding_audit_value( $value ) {
        if ( is_array( $value ) ) {
            $out = array();
            foreach ( $value as $key => $item ) {
                $key_string = is_string( $key ) ? $key : (string) $key;
                if ( $this->is_main_coding_audit_sensitive_key( $key_string ) ) {
                    $out[ $key ] = '[redacted]';
                    continue;
                }
                $out[ $key ] = $this->sanitize_main_coding_audit_value( $item );
            }
            return $out;
        }

        if ( is_object( $value ) ) {
            return '[object:' . get_class( $value ) . ']';
        }

        if ( is_resource( $value ) ) {
            return '[resource]';
        }

        if ( is_string( $value ) ) {
            $value = $this->redact_main_coding_audit_text( $value );
            if ( strlen( $value ) > 6000 ) {
                return substr( $value, 0, 5900 ) . "\n...[audit truncated]";
            }
        }

        return $value;
    }

    private function is_main_coding_audit_sensitive_key( $key ) {
        $key = strtolower( (string) $key );
        foreach ( array( 'authorization', 'api_key', 'access_token', 'refresh_token', 'codex_token', 'secret', 'password', 'passwd', 'token', 'bearer' ) as $needle ) {
            if ( false !== strpos( $key, $needle ) ) {
                return true;
            }
        }

        return false;
    }

	private function redact_main_coding_audit_text( $text ) {
		$text = $this->redact_main_coding_runtime_secrets( (string) $text );
		$patterns = array(
			"/(define\\(\\s*['\"](?:DB_NAME|DB_USER|DB_PASSWORD|DB_HOST|AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|NONCE_KEY|AUTH_SALT|SECURE_AUTH_SALT|LOGGED_IN_SALT|NONCE_SALT|WP_CACHE_KEY_SALT|OPENAI_API_KEY|ANTHROPIC_API_KEY)['\"]\\s*,\\s*)['\"][^'\"]*['\"](\\s*\\))/i",
			'/((?:["\']?)[A-Za-z0-9_.-]*(?:password|passwd|secret|token|private[_-]?key|api[_-]?key|access[_-]?key|database[_-]?url)[A-Za-z0-9_.-]*(?:["\']?)\\s*[=:]\\s*)(?:"[^"\\r\\n]*"|\'[^\'\\r\\n]*\'|[^\\s,;&\\r\\n]+)/i',
			'/(Authorization:\s*Bearer\s+)[A-Za-z0-9._~+\-\/]+=*/i',
            '/(sk-[A-Za-z0-9_\-]{12,})/i',
            '/(password\s*[=:]\s*)[^\s&]+/i',
            '/(api[_-]?key\s*[=:]\s*)[^\s&]+/i',
            '/(access[_-]?token\s*[=:]\s*)[^\s&]+/i',
            '/((?:mysql|mariadb)(?:admin)?\b[^\r\n]*?\s-p)([^\s"\']+)/i',
            '/(--password(?:=|\s+))([^\s"\']+)/i',
            '#(\b[a-z][a-z0-9+.-]*://[^\s:/@]+:)[^\s/@]+(@)#i',
        );
		$replacements = array(
			'$1\'[redacted]\'$2',
			'$1[redacted]',
			'$1[redacted]',
            '[redacted-api-key]',
            '$1[redacted]',
            '$1[redacted]',
            '$1[redacted]',
            '$1[redacted]',
            '$1[redacted]',
            '$1[redacted]$2',
        );

		return preg_replace( $patterns, $replacements, $text );
	}

	private function redact_main_coding_runtime_secrets( $text, $extra_secrets = array() ) {
		$secrets = is_array( $extra_secrets ) ? $extra_secrets : array();
		foreach ( array( 'DB_NAME', 'DB_USER', 'DB_PASSWORD', 'DB_HOST', 'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY', 'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT', 'WP_CACHE_KEY_SALT', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY' ) as $constant ) {
			if ( defined( $constant ) && is_scalar( constant( $constant ) ) ) {
				$secrets[] = (string) constant( $constant );
			}
		}

		$environment = getenv();
		if ( is_array( $environment ) ) {
			foreach ( $environment as $key => $value ) {
				if (
					is_scalar( $value )
					&& preg_match( '/password|passwd|secret|token|private[_-]?key|api[_-]?key|access[_-]?key/i', (string) $key )
				) {
					$secrets[] = (string) $value;
				}
			}
		}

		$variants = array();
		foreach ( array_unique( array_map( 'strval', $secrets ) ) as $secret ) {
			if ( strlen( $secret ) < 4 || '[redacted]' === $secret ) {
				continue;
			}
			foreach ( array( $secret, rawurlencode( $secret ), urlencode( $secret ), base64_encode( $secret ) ) as $variant ) {
				if ( strlen( $variant ) >= 4 ) {
					$variants[] = $variant;
				}
			}
		}
		$variants = array_values( array_unique( $variants ) );
		usort(
			$variants,
			static function ( $left, $right ) {
				return strlen( $right ) <=> strlen( $left );
			}
		);

		return empty( $variants ) ? (string) $text : str_replace( $variants, '[redacted]', (string) $text );
	}

	private function validate_main_coding_shell_secret_boundary( $command, $argv ) {
		$text = trim( (string) $command . ' ' . implode( ' ', array_map( 'strval', (array) $argv ) ) );
		$sensitive_source = (bool) preg_match(
			'#(?:wp-config(?:-sample)?\.php|(?:^|[\s\'"/])\.env(?:$|[\s\'"])|\bwp\s+config\s+get\b|(?:^|[;&|]\s*|\s)(?:printenv|env)(?:\s|$)|/proc/(?:self|[0-9]+)/environ)#i',
			$text
		);
		$obfuscation = (bool) preg_match( '/\b(?:base64|xxd|uuencode|openssl|gzip|bzip2|xz|zip|tar)\b/i', $text );
		$external_transfer = (bool) preg_match(
			'#(?:https?://|\b(?:curl|wget|ssh|scp|sftp|ftp|nc|ncat|socat)\b|\brsync\b[^\r\n]*(?:[^\s]+@)?[^\s:]+:)#i',
			$text
		);
		$local_upload = (bool) preg_match(
			'#\b(?:scp|sftp|ftp|nc|ncat|socat)\b|\bcurl\b[^\r\n]*(?:--upload-file|-T\s|--data-binary\s+@|--data\s+@|-d\s+@|--form\s+[^\s=]+=@|-F\s+[^\s=]+=@)|\bwget\b[^\r\n]*--post-file|\brsync\b[^\r\n]*(?:[^\s]+@)?[^\s:]+:#i',
			$text
		);

		if ( $local_upload || ( $sensitive_source && ( $obfuscation || $external_transfer ) ) ) {
			return array(
				'ok'    => false,
				'error' => 'Local configuration reads are allowed, but encoding or transmitting local/process data outside the site is blocked. Use native WordPress diagnostics or a download-only request.',
			);
		}

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

    private function get_main_coding_workspace_base_dir() {
        $workspace_root = class_exists( 'Niwa_AI_Admin_Autonomy_Workspace' )
            ? Niwa_AI_Admin_Autonomy_Workspace::get_private_workspace_root()
            : '';
        if ( '' === $workspace_root ) {
            return '';
        }

        $dir = wp_normalize_path( trailingslashit( $workspace_root ) . 'main-coding' );
        if ( ! is_dir( $dir ) ) {
            Niwa_AI_Admin_Autonomy_Workspace::ensure_private_directory( $dir );
        }
        $this->write_main_coding_workspace_deny_files( $dir );

        return $dir;
    }

    private function write_main_coding_workspace_deny_files( $dir ) {
        $dir = wp_normalize_path( (string) $dir );
        if ( '' === $dir || ! is_dir( $dir ) ) {
            return;
        }
        Niwa_AI_Admin_Autonomy_Workspace::write_private_file( trailingslashit( $dir ) . '.htaccess', "Deny from all\n" );
        Niwa_AI_Admin_Autonomy_Workspace::write_private_file( trailingslashit( $dir ) . 'index.php', '<?php // Silence is golden.' );
    }

    private function sanitize_main_coding_operation_id( $operation_id ) {
        $operation_id = trim( (string) $operation_id );
        $operation_id = preg_replace( '/[^A-Za-z0-9_.-]+/', '-', $operation_id );
        return trim( (string) $operation_id, '.-' );
    }

    private function resolve_main_coding_target_file_path( $path, $allow_missing = false ) {
        $path = trim( (string) $path );
        if ( '' === $path ) {
            return array(
                'ok'    => false,
                'error' => 'Missing target path.',
            );
        }

        $base      = defined( 'ABSPATH' ) ? wp_normalize_path( untrailingslashit( ABSPATH ) ) : '';
        $candidate = wp_normalize_path( str_replace( '\\', '/', $path ) );
        if ( '' !== $base && ! preg_match( '#^[A-Za-z]:/#', $candidate ) && 0 !== strpos( $candidate, '/' ) ) {
            $candidate = wp_normalize_path( trailingslashit( $base ) . ltrim( $candidate, '/' ) );
        }

        $guard = class_exists( 'Niwa_AI_Admin_Autonomy_File_Guard' ) ? new Niwa_AI_Admin_Autonomy_File_Guard() : null;
        $real  = realpath( $candidate );
        if ( false !== $real ) {
            $real = wp_normalize_path( $real );
            if ( $guard ) {
                $validated = $guard->validate_write( $real );
                if ( empty( $validated['success'] ) ) {
                    return array(
                        'ok'    => false,
                        'error' => isset( $validated['error'] ) ? (string) $validated['error'] : 'Target path is not allowed for staged workspace writes.',
                    );
                }
                $real = isset( $validated['path'] ) ? wp_normalize_path( (string) $validated['path'] ) : $real;
            }
            if ( ! is_file( $real ) ) {
                return array(
                    'ok'    => false,
                    'error' => 'Target exists but is not a regular file.',
                );
            }

            return array(
                'ok'           => true,
                'path'         => $real,
                'display_path' => $this->display_main_coding_path( $real ),
                'exists'       => true,
            );
        }

        if ( ! $allow_missing ) {
            return array(
                'ok'    => false,
                'error' => 'Target file does not exist. Set allow_missing=true for new files.',
            );
        }

        if ( $guard ) {
            $validated = $guard->validate_create( $candidate );
            if ( empty( $validated['success'] ) ) {
                return array(
                    'ok'    => false,
                    'error' => isset( $validated['error'] ) ? (string) $validated['error'] : 'Missing target path is not allowed for staged workspace creation.',
                );
            }

            $resolved = isset( $validated['path'] ) ? wp_normalize_path( (string) $validated['path'] ) : wp_normalize_path( $candidate );
            return array(
                'ok'           => true,
                'path'         => $resolved,
                'display_path' => $this->display_main_coding_path( $resolved ),
                'exists'       => false,
            );
        }

        $parent_dir           = wp_normalize_path( dirname( $candidate ) );
        $nearest_existing_dir = $parent_dir;
        while ( false === realpath( $nearest_existing_dir ) && dirname( $nearest_existing_dir ) !== $nearest_existing_dir ) {
            $nearest_existing_dir = dirname( $nearest_existing_dir );
        }
        $parent = realpath( $nearest_existing_dir );
        if ( false === $parent || ! is_dir( $parent ) ) {
            return array(
                'ok'    => false,
                'error' => 'No existing parent directory for missing target path.',
            );
        }

        $resolved = wp_normalize_path( trailingslashit( wp_normalize_path( $parent ) ) . ltrim( str_replace( trailingslashit( wp_normalize_path( $parent ) ), '', wp_normalize_path( $candidate ) ), '/' ) );
        return array(
            'ok'           => true,
            'path'         => $resolved,
            'display_path' => $this->display_main_coding_path( $resolved ),
            'exists'       => false,
        );
    }

    private function display_main_coding_path( $path ) {
        $path = wp_normalize_path( (string) $path );
        if ( defined( 'ABSPATH' ) ) {
            $root        = wp_normalize_path( untrailingslashit( ABSPATH ) );
            $root_prefix = trailingslashit( $root );
            if ( $path === $root ) {
                return '.';
            }
            if ( 0 === strpos( $path, $root_prefix ) ) {
                return ltrim( str_replace( $root_prefix, '', $path ), '/' );
            }
        }

        return $path;
    }

    private function build_main_coding_workspace_filename( $index, $display_path ) {
        $base = basename( wp_normalize_path( (string) $display_path ) );
        $base = preg_replace( '/[^A-Za-z0-9._-]+/', '-', $base );
        if ( '' === $base || '.' === $base || '..' === $base ) {
            $base = 'file';
        }

        return sprintf( '%03d-%s-%s', max( 1, intval( $index ) ), substr( sha1( (string) $display_path ), 0, 12 ), $base );
    }

    private function get_main_coding_manifest_path( $operation_id ) {
        $operation_id = $this->sanitize_main_coding_operation_id( $operation_id );
        if ( '' === $operation_id ) {
            return '';
        }

        $base_dir = $this->get_main_coding_workspace_base_dir();
        if ( '' === $base_dir ) {
            return '';
        }

        return wp_normalize_path( trailingslashit( $base_dir ) . $operation_id . '/manifest.json' );
    }

    private function save_main_coding_manifest( $manifest ) {
        $manifest = is_array( $manifest ) ? $manifest : array();
        $path     = $this->get_main_coding_manifest_path( isset( $manifest['operation_id'] ) ? (string) $manifest['operation_id'] : '' );
        if ( '' === $path ) {
            return false;
        }
        if ( ! is_dir( dirname( $path ) ) ) {
            Niwa_AI_Admin_Autonomy_Workspace::ensure_private_directory( dirname( $path ) );
        }

        $manifest['updated_at'] = time();
        return false !== Niwa_AI_Admin_Autonomy_Workspace::write_private_file( $path, wp_json_encode( $manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) );
    }

    private function load_main_coding_manifest( $operation_id ) {
        $operation_id = $this->sanitize_main_coding_operation_id( $operation_id );
        if ( '' === $operation_id ) {
            return array(
                'success' => false,
                'error'   => 'operation_id is required.',
            );
        }

        $path = $this->get_main_coding_manifest_path( $operation_id );
        if ( '' === $path || ! is_file( $path ) ) {
            return array(
                'success'      => false,
                'operation_id' => $operation_id,
                'error'        => 'Main coding manifest was not found.',
            );
        }

        $json     = (string) @file_get_contents( $path );
        $manifest = json_decode( $json, true );
        if ( ! is_array( $manifest ) ) {
            return array(
                'success'      => false,
                'operation_id' => $operation_id,
                'error'        => 'Main coding manifest is not valid JSON.',
            );
        }

        $migrated_manifest = $this->remap_legacy_main_coding_workspace_paths( $manifest );
        if ( $migrated_manifest !== $manifest ) {
            $manifest = $migrated_manifest;
            $this->save_main_coding_manifest( $manifest );
        }

        return array(
            'success'       => true,
            'operation_id'  => $operation_id,
            'manifest_path' => $path,
            'manifest'      => $manifest,
        );
    }

    private function select_main_coding_manifest_files( $manifest, $target_paths = array() ) {
        $manifest = is_array( $manifest ) ? $manifest : array();
        $files    = isset( $manifest['files'] ) && is_array( $manifest['files'] ) ? $manifest['files'] : array();
        $targets  = is_array( $target_paths ) ? array_values( array_filter( array_map( 'strval', $target_paths ) ) ) : array();
        if ( empty( $targets ) ) {
            return $files;
        }

        $wanted = array();
        foreach ( $targets as $target ) {
            $resolved = $this->resolve_main_coding_target_file_path( $target, true );
            $wanted[] = wp_normalize_path( (string) $target );
            if ( ! empty( $resolved['path'] ) ) {
                $wanted[] = wp_normalize_path( (string) $resolved['path'] );
            }
            if ( ! empty( $resolved['display_path'] ) ) {
                $wanted[] = wp_normalize_path( (string) $resolved['display_path'] );
            }
        }
        $wanted = array_fill_keys( array_values( array_unique( $wanted ) ), true );

        $selected = array();
        foreach ( $files as $index => $file ) {
            $keys = array(
                isset( $file['target_path'] ) ? wp_normalize_path( (string) $file['target_path'] ) : '',
                isset( $file['display_path'] ) ? wp_normalize_path( (string) $file['display_path'] ) : '',
                isset( $file['work_path'] ) ? wp_normalize_path( (string) $file['work_path'] ) : '',
            );
            foreach ( $keys as $key ) {
                if ( '' !== $key && isset( $wanted[ $key ] ) ) {
                    $selected[ $index ] = $file;
                    break;
                }
            }
        }

        return $selected;
    }

    private function rollback_main_coding_manifest_files( &$manifest, $selected, $reason ) {
        $selected   = is_array( $selected ) ? $selected : array();
        $broken_dir = isset( $manifest['broken_dir'] ) ? (string) $manifest['broken_dir'] : '';
        if ( '' === $broken_dir ) {
            $base_dir   = isset( $manifest['base_dir'] ) ? (string) $manifest['base_dir'] : '';
            $broken_dir = '' !== $base_dir ? trailingslashit( $base_dir ) . 'broken' : '';
        }
        if ( '' !== $broken_dir && ! is_dir( $broken_dir ) ) {
            Niwa_AI_Admin_Autonomy_Workspace::ensure_private_directory( $broken_dir );
        }

        $restored = array();
        $errors   = array();
        foreach ( $selected as $index => $file ) {
            $target_path = isset( $file['target_path'] ) ? (string) $file['target_path'] : '';
            $display     = isset( $file['display_path'] ) ? (string) $file['display_path'] : $target_path;
            $backup_path = isset( $file['backup_path'] ) ? (string) $file['backup_path'] : '';
            $existed     = ! empty( $file['target_existed'] );
            $broken_path = '';

            if ( '' === $target_path ) {
                $errors[] = array(
                    'display_path' => $display,
                    'error'        => 'Target path is missing from manifest.',
                );
                continue;
            }

            if ( is_file( $target_path ) && '' !== $broken_dir ) {
                $broken_path = trailingslashit( $broken_dir ) . $this->build_main_coding_workspace_filename( intval( $index ) + 1, $display ) . '.broken-' . gmdate( 'Ymd-His' );
                Niwa_AI_Admin_Autonomy_Workspace::copy_private_file( $target_path, $broken_path );
            }

            if ( $existed ) {
                if ( '' === $backup_path || ! is_file( $backup_path ) ) {
                    $errors[] = array(
                        'display_path' => $display,
                        'error'        => 'Backup copy is missing.',
                    );
                    continue;
                }
                $restore = $this->atomic_replace_main_coding_target( $backup_path, $target_path );
                if ( empty( $restore['success'] ) ) {
                    $errors[] = array(
                        'display_path' => $display,
                        'error'        => isset( $restore['error'] ) ? (string) $restore['error'] : 'Unable to atomically restore backup copy.',
                    );
                    continue;
                }
            } elseif ( file_exists( $target_path ) && ! @unlink( $target_path ) ) {
                $errors[] = array(
                    'display_path' => $display,
                    'error'        => 'Unable to remove newly-created target during rollback.',
                );
                continue;
            }

            $restored[] = array(
                'target_path'  => $target_path,
                'display_path' => $display,
                'backup_path'  => $backup_path,
                'broken_path'  => $broken_path,
                'reason'       => (string) $reason,
            );
            if ( isset( $manifest['files'][ $index ] ) ) {
                $manifest['files'][ $index ]['status']      = 'rolled_back';
                $manifest['files'][ $index ]['rolled_at']   = time();
                $manifest['files'][ $index ]['broken_path'] = $broken_path;
                $manifest['files'][ $index ]['rollback_reason'] = (string) $reason;
            }
        }

        return array(
            'success'  => empty( $errors ),
            'restored' => $restored,
            'errors'   => $errors,
            'reason'   => (string) $reason,
        );
    }

    private function preflight_main_coding_workspace_apply( $manifest, $selected ) {
        $manifest = is_array( $manifest ) ? $manifest : array();
        $selected = is_array( $selected ) ? $selected : array();
        $reports  = array();
        $errors   = array();

        foreach ( $selected as $index => $file ) {
            $report = $this->build_main_coding_apply_file_report( $file );
            $reports[] = $report;

            if ( isset( $manifest['files'][ $index ] ) && is_array( $manifest['files'][ $index ] ) ) {
                foreach ( array(
                    'current_work_sha256',
                    'current_target_sha256',
                    'current_backup_sha256',
                    'work_line_endings',
                    'target_line_endings',
                    'backup_line_endings',
                    'apply_preflight',
                ) as $key ) {
                    if ( array_key_exists( $key, $report ) ) {
                        $manifest['files'][ $index ][ $key ] = $report[ $key ];
                    }
                }
                if ( ! empty( $report['current_work_sha256'] ) ) {
                    $manifest['files'][ $index ]['work_sha256'] = (string) $report['current_work_sha256'];
                }
            }

            if ( empty( $report['ok'] ) ) {
                $errors[] = array(
                    'display_path' => isset( $report['display_path'] ) ? (string) $report['display_path'] : '',
                    'reason'       => isset( $report['reason'] ) ? (string) $report['reason'] : 'apply_preflight_failed',
                    'error'        => isset( $report['error'] ) ? (string) $report['error'] : 'Apply preflight failed.',
                    'summary'      => isset( $report['summary'] ) ? $report['summary'] : array(),
                );
            }
        }

        $manifest['apply_preflight'] = array(
            'checked_at' => time(),
            'success'    => empty( $errors ),
            'reports'    => $reports,
            'errors'     => $errors,
        );
        if ( ! empty( $errors ) ) {
            $manifest['status'] = 'apply_preflight_blocked';
            $manifest['errors'] = array_merge(
                isset( $manifest['errors'] ) && is_array( $manifest['errors'] ) ? $manifest['errors'] : array(),
                $errors
            );
        }

        return array(
            'success'  => empty( $errors ),
            'manifest' => $manifest,
            'reports'  => $reports,
            'errors'   => $errors,
        );
    }

    private function build_main_coding_apply_file_report( $file ) {
        $file        = is_array( $file ) ? $file : array();
        $work_path   = isset( $file['work_path'] ) ? (string) $file['work_path'] : '';
        $target_path = isset( $file['target_path'] ) ? (string) $file['target_path'] : '';
        $backup_path = ! empty( $file['target_existed'] ) && ! empty( $file['backup_path'] ) ? (string) $file['backup_path'] : '';
        $display     = isset( $file['display_path'] ) ? (string) $file['display_path'] : $target_path;

        $report = array(
            'ok'           => false,
            'display_path' => $display,
            'work_path'    => $work_path,
            'target_path'  => $target_path,
        );

        if ( '' === $work_path || ! is_file( $work_path ) ) {
            $report['reason'] = 'work_copy_missing';
            $report['error']  = 'Work copy is missing.';
            return $report;
        }

        if ( '' === $target_path ) {
            $report['reason'] = 'target_path_missing';
            $report['error']  = 'Target path is missing from manifest.';
            return $report;
        }

        $work_content = @file_get_contents( $work_path );
        if ( false === $work_content ) {
            $report['reason'] = 'work_copy_unreadable';
            $report['error']  = 'Unable to read work copy.';
            return $report;
        }

        $base_path    = '';
        $base_content = null;
        if ( '' !== $backup_path && is_file( $backup_path ) ) {
            $base_path    = $backup_path;
            $base_content = @file_get_contents( $backup_path );
        } elseif ( is_file( $target_path ) ) {
            $base_path    = $target_path;
            $base_content = @file_get_contents( $target_path );
        }

        $current_target_sha = is_file( $target_path ) ? hash_file( 'sha256', $target_path ) : '';
        $current_backup_sha = '' !== $backup_path && is_file( $backup_path ) ? hash_file( 'sha256', $backup_path ) : '';
        $current_work_sha   = hash( 'sha256', $work_content );

        $report['current_work_sha256']   = $current_work_sha;
        $report['current_target_sha256'] = $current_target_sha;
        $report['current_backup_sha256'] = $current_backup_sha;
        $report['work_line_endings']     = $this->get_main_coding_content_line_ending_profile( $work_content );
        $report['target_line_endings']   = is_file( $target_path ) ? $this->get_main_coding_file_line_ending_profile( $target_path ) : array();
        $report['backup_line_endings']   = '' !== $backup_path && is_file( $backup_path ) ? $this->get_main_coding_file_line_ending_profile( $backup_path ) : array();

        $original_sha = isset( $file['original_sha256'] ) ? (string) $file['original_sha256'] : '';
        if ( ! empty( $file['target_existed'] ) && '' !== $original_sha && $current_target_sha !== $original_sha ) {
            $report['reason'] = 'target_changed_since_prepare';
            $report['error']  = 'Target file changed on disk after workspace_prepare; refusing to overwrite external changes.';
            $report['summary'] = array(
                'original_sha256' => $original_sha,
                'target_sha256'   => $current_target_sha,
                'work_sha256'     => $current_work_sha,
            );
            return $report;
        }

        if ( null === $base_content || false === $base_content ) {
            $report['ok']              = true;
            $report['reason']          = 'new_file_or_no_baseline';
            $report['apply_preflight'] = array( 'ok' => true, 'baseline_available' => false );
            $report['summary']         = array(
                'baseline_available' => false,
                'work_bytes'         => strlen( $work_content ),
                'work_sha256'        => $current_work_sha,
            );
            return $report;
        }

        $line_stats     = $this->get_main_coding_line_diff_stats( $base_content, $work_content );
        $line_endings_only = $base_content !== $work_content
            && $this->normalize_main_coding_line_endings( $base_content ) === $this->normalize_main_coding_line_endings( $work_content );
        $whitespace_only = $base_content !== $work_content
            && $this->strip_main_coding_all_whitespace( $base_content ) === $this->strip_main_coding_all_whitespace( $work_content );
        $huge_whitespace_only = $whitespace_only && $this->is_main_coding_huge_whitespace_only_diff( $line_stats );

        $summary = array(
            'baseline_path'          => $base_path,
            'baseline_sha256'        => hash( 'sha256', $base_content ),
            'work_sha256'            => $current_work_sha,
            'line_endings_only'      => $line_endings_only,
            'whitespace_only'        => $whitespace_only,
            'huge_whitespace_only'   => $huge_whitespace_only,
            'changed_lines'          => isset( $line_stats['changed_lines'] ) ? intval( $line_stats['changed_lines'] ) : 0,
            'total_lines'            => isset( $line_stats['total_lines'] ) ? intval( $line_stats['total_lines'] ) : 0,
            'changed_line_ratio'     => isset( $line_stats['changed_ratio'] ) ? $line_stats['changed_ratio'] : 0,
            'baseline_line_endings'  => $this->get_main_coding_content_line_ending_profile( $base_content ),
            'work_line_endings'      => $report['work_line_endings'],
        );

        $report['summary'] = $summary;

        if ( $line_endings_only ) {
            $report['reason'] = 'line_ending_only_change';
            $report['error']  = 'Work copy changes only line endings; refusing to replace the target because this is likely CRLF/LF conversion noise.';
            $report['apply_preflight'] = array( 'ok' => false, 'reason' => $report['reason'], 'summary' => $summary );
            return $report;
        }

        if ( $huge_whitespace_only ) {
            $report['reason'] = 'huge_whitespace_only_diff';
            $report['error']  = 'Work copy has a huge whitespace-only diff; refusing to apply likely formatter or CRLF/LF fallout.';
            $report['apply_preflight'] = array( 'ok' => false, 'reason' => $report['reason'], 'summary' => $summary );
            return $report;
        }

        $report['ok']              = true;
        $report['reason']          = 'safe_to_apply';
        $report['apply_preflight'] = array( 'ok' => true, 'summary' => $summary );
        return $report;
    }

    private function normalize_main_coding_line_endings( $content ) {
        return preg_replace( "/\r\n?/", "\n", (string) $content );
    }

    private function strip_main_coding_all_whitespace( $content ) {
        return preg_replace( '/\s+/u', '', (string) $content );
    }

    private function get_main_coding_line_diff_stats( $before, $after ) {
        $before_lines = explode( "\n", $this->normalize_main_coding_line_endings( (string) $before ) );
        $after_lines  = explode( "\n", $this->normalize_main_coding_line_endings( (string) $after ) );
        $total        = max( count( $before_lines ), count( $after_lines ) );
        $changed      = 0;

        for ( $i = 0; $i < $total; $i++ ) {
            $left  = array_key_exists( $i, $before_lines ) ? $before_lines[ $i ] : null;
            $right = array_key_exists( $i, $after_lines ) ? $after_lines[ $i ] : null;
            if ( $left !== $right ) {
                $changed++;
            }
        }

        return array(
            'total_lines'   => $total,
            'changed_lines' => $changed,
            'changed_ratio' => $total > 0 ? round( $changed / $total, 4 ) : 0,
        );
    }

    private function is_main_coding_huge_whitespace_only_diff( $stats ) {
        $stats   = is_array( $stats ) ? $stats : array();
        $total   = max( 0, intval( isset( $stats['total_lines'] ) ? $stats['total_lines'] : 0 ) );
        $changed = max( 0, intval( isset( $stats['changed_lines'] ) ? $stats['changed_lines'] : 0 ) );
        $ratio   = isset( $stats['changed_ratio'] ) ? floatval( $stats['changed_ratio'] ) : ( $total > 0 ? $changed / $total : 0 );

        return $changed >= 80 || ( $total >= 40 && $ratio >= 0.5 );
    }

    private function get_main_coding_file_line_ending_profile( $path ) {
        if ( ! is_file( $path ) ) {
            return array();
        }

        $content = @file_get_contents( $path );
        if ( false === $content ) {
            return array();
        }

        return $this->get_main_coding_content_line_ending_profile( $content );
    }

    private function get_main_coding_content_line_ending_profile( $content ) {
        $content = (string) $content;
        preg_match_all( "/\r\n/", $content, $crlf_matches );
        preg_match_all( "/(?<!\r)\n/", $content, $lf_matches );
        preg_match_all( "/\r(?!\n)/", $content, $cr_matches );

        return array(
            'crlf'       => isset( $crlf_matches[0] ) ? count( $crlf_matches[0] ) : 0,
            'lf'         => isset( $lf_matches[0] ) ? count( $lf_matches[0] ) : 0,
            'cr'         => isset( $cr_matches[0] ) ? count( $cr_matches[0] ) : 0,
            'final_eol'  => '' !== $content && 1 === preg_match( "/(?:\r\n|\n|\r)$/", $content ),
            'byte_count' => strlen( $content ),
        );
    }

    private function is_allowed_main_coding_health_url( $url ) {
        $origin = $this->main_coding_health_origin( $url );
        $home   = function_exists( 'home_url' ) ? $this->main_coding_health_origin( home_url( '/' ) ) : '';
        if ( '' === $origin || ( '' !== $home && hash_equals( $home, $origin ) ) ) {
            return '' !== $origin;
        }

        $allowed = apply_filters( 'niwa_ai_main_coding_health_allowed_origins', array() );
        foreach ( is_array( $allowed ) ? $allowed : array() as $allowed_url ) {
            $allowed_origin = $this->main_coding_health_origin( $allowed_url );
            if ( '' !== $allowed_origin && hash_equals( $allowed_origin, $origin ) ) {
                return true;
            }
        }
        return false;
    }

    private function main_coding_health_origin( $url ) {
        $parts = function_exists( 'wp_parse_url' ) ? wp_parse_url( trim( (string) $url ) ) : parse_url( trim( (string) $url ) );
        if ( ! is_array( $parts ) || empty( $parts['scheme'] ) || empty( $parts['host'] ) ) {
            return '';
        }
        $scheme = strtolower( (string) $parts['scheme'] );
        if ( ! in_array( $scheme, array( 'http', 'https' ), true ) ) {
            return '';
        }
        $port = isset( $parts['port'] ) ? intval( $parts['port'] ) : ( 'https' === $scheme ? 443 : 80 );
        return $scheme . '://' . strtolower( (string) $parts['host'] ) . ':' . $port;
    }

    private function run_main_coding_health_check( $url = '', $timeout = 8 ) {
        $url = trim( (string) $url );
        if ( '' === $url && function_exists( 'home_url' ) ) {
            $url = home_url( '/' );
        }
        if ( '' === $url || ! class_exists( 'Niwa_AI_Safe_HTTP' ) ) {
            return array(
                'success' => true,
                'skipped' => true,
                'reason'  => 'No health check URL/runtime is available.',
            );
        }
        if ( ! $this->is_allowed_main_coding_health_url( $url ) ) {
            return array(
                'success' => false,
                'url'     => $url,
                'error'   => 'Health check URL must use the site origin or an explicitly allowed origin.',
            );
        }

        $timeout       = max( 1, intval( $timeout ) );
        $max_attempts  = 3;
        $attempts      = array();
        $last_response = null;
        $last_error    = '';

        for ( $i = 1; $i <= $max_attempts; $i++ ) {
            $response = Niwa_AI_Safe_HTTP::get(
                $url,
                array(
                    'timeout'     => $timeout,
                    'redirection' => 3,
                ),
                128 * 1024
            );

            if ( is_wp_error( $response ) ) {
                $last_error = $response->get_error_message();
                $attempts[] = array(
                    'attempt' => $i,
                    'success' => false,
                    'error'   => $last_error,
                );
                if ( $i < $max_attempts ) {
                    usleep( 300000 * $i );
                    continue;
                }
                return array(
                    'success'  => false,
                    'url'      => $url,
                    'error'    => $last_error,
                    'attempts' => $attempts,
                );
            }

            $code = intval( wp_remote_retrieve_response_code( $response ) );
            $body = strtolower( substr( (string) wp_remote_retrieve_body( $response ), 0, 12000 ) );
            $fatal_patterns = array(
                'there has been a critical error',
                'technical difficulties',
                'fatal error',
                'parse error',
                'uncaught error',
                'wordpress &rsaquo; error',
            );
            $fatal_match = '';
            foreach ( $fatal_patterns as $pattern ) {
                if ( false !== strpos( $body, $pattern ) ) {
                    $fatal_match = $pattern;
                    break;
                }
            }

            $is_ok      = $code >= 200 && $code < 500 && '' === $fatal_match;
            $attempts[] = array(
                'attempt'     => $i,
                'success'     => $is_ok,
                'status_code' => $code,
                'fatal_match' => $fatal_match,
            );
            $last_response = array(
                'success'     => $is_ok,
                'url'         => $url,
                'status_code' => $code,
                'fatal_match' => $fatal_match,
                'attempts'    => $attempts,
            );

            if ( $is_ok ) {
                return $last_response;
            }

            if ( $i < $max_attempts ) {
                usleep( 300000 * $i );
            }
        }

        return $last_response ?: array(
            'success'  => false,
            'url'      => $url,
            'error'    => $last_error,
            'attempts' => $attempts,
        );
    }

    private function truncate_main_coding_shell_output( $text, $limit ) {
        $text  = (string) $text;
        $limit = max( 0, intval( $limit ) );
        if ( 0 === $limit || strlen( $text ) <= $limit ) {
            return $text;
        }

        return substr( $text, 0, max( 0, $limit - 15 ) ) . "\n...[truncated]";
    }

    private function get_current_niwa_plugin_relative_path() {
        if ( defined( 'ABSPATH' ) && defined( 'NIWA_AI_DIR' ) ) {
            $root       = wp_normalize_path( trailingslashit( ABSPATH ) );
            $plugin_dir = wp_normalize_path( trailingslashit( NIWA_AI_DIR ) );
            if ( 0 === strpos( $plugin_dir, $root ) ) {
                return trim( substr( $plugin_dir, strlen( $root ) ), '/' );
            }
        }

        return 'wp-content/plugins/niwa-ai';
    }

    private function resolve_code_shell_cwd( $cwd ) {
        if ( ! defined( 'ABSPATH' ) ) {
            return array(
                'ok'    => false,
                'error' => 'ABSPATH is not defined.',
            );
        }

        $root        = wp_normalize_path( untrailingslashit( ABSPATH ) );
        $root_prefix = trailingslashit( $root );
        $plugin_dir  = defined( 'NIWA_AI_DIR' ) ? wp_normalize_path( untrailingslashit( NIWA_AI_DIR ) ) : $root;
        $cwd         = trim( (string) $cwd );
        $candidate   = '' === $cwd ? $plugin_dir : wp_normalize_path( str_replace( '\\', '/', $cwd ) );

        if ( ! preg_match( '#^[A-Za-z]:/#', $candidate ) && 0 !== strpos( $candidate, '/' ) ) {
            $candidate = wp_normalize_path( $root_prefix . ltrim( $candidate, '/' ) );
        }

        $real = realpath( $candidate );
        if ( false === $real || ! is_dir( $real ) ) {
            return array(
                'ok'    => false,
                'error' => 'cwd does not exist or is not a directory.',
            );
        }

        $real = wp_normalize_path( $real );
        if ( $real !== $root && 0 !== strpos( $real, $root_prefix ) ) {
            return array(
                'ok'    => false,
                'error' => 'cwd must be inside the WordPress installation.',
            );
        }

        return array(
            'ok'            => true,
            'path'          => $real,
            'relative_path' => $real === $root ? '.' : ltrim( str_replace( $root_prefix, '', $real ), '/' ),
        );
    }



    private function validate_code_shell_git_command( $argv ) {
        $sub = isset( $argv[1] ) ? sanitize_key( (string) $argv[1] ) : '';
        if ( in_array( $sub, array( 'status', 'diff', 'show', 'log', 'ls-files', 'grep', 'branch', 'rev-parse' ), true ) ) {
            return array( 'ok' => true );
        }

        return array(
            'ok'     => false,
            'error'  => 'Git shell access is read-only and limited to status/diff/show/log/ls-files/grep/branch/rev-parse.',
            'policy' => 'git_read_only',
        );
    }








    private function code_shell_args_stay_in_wordpress_root( $argv, $cwd ) {
        foreach ( array_slice( (array) $argv, 1 ) as $arg ) {
            $arg = (string) $arg;
            if ( false !== strpos( str_replace( '\\', '/', $arg ), '../' ) || '..' === $arg ) {
                return false;
            }

            if ( 0 === strpos( $arg, '--path=' ) ) {
                $path_value = substr( $arg, 7 );
                $resolved   = $this->resolve_code_shell_arg_path( $path_value, $cwd, false );
                if ( empty( $resolved['ok'] ) ) {
                    return false;
                }
                continue;
            }

            if ( 0 === strpos( $arg, '--require=' ) ) {
                return false;
            }

            if ( preg_match( '#^[A-Za-z]:[\\\\/]#', $arg ) || 0 === strpos( $arg, '/' ) ) {
                $resolved = $this->resolve_code_shell_arg_path( $arg, $cwd, false );
                if ( empty( $resolved['ok'] ) ) {
                    return false;
                }
            }
        }

        return true;
    }

    private function resolve_code_shell_arg_path( $path, $cwd, $must_exist = true ) {
        if ( ! defined( 'ABSPATH' ) ) {
            return array( 'ok' => false );
        }

        $root        = wp_normalize_path( untrailingslashit( ABSPATH ) );
        $root_prefix = trailingslashit( $root );
        $candidate   = wp_normalize_path( str_replace( '\\', '/', (string) $path ) );
        if ( ! preg_match( '#^[A-Za-z]:/#', $candidate ) && 0 !== strpos( $candidate, '/' ) ) {
            $candidate = wp_normalize_path( trailingslashit( $cwd ) . ltrim( $candidate, '/' ) );
        }

        $real = realpath( $candidate );
        if ( false === $real ) {
            if ( $must_exist ) {
                return array( 'ok' => false );
            }
            $parent = realpath( dirname( $candidate ) );
            if ( false === $parent ) {
                return array( 'ok' => false );
            }
            $real = wp_normalize_path( trailingslashit( $parent ) . basename( $candidate ) );
        }

        $real = wp_normalize_path( $real );
        if ( $real !== $root && 0 !== strpos( $real, $root_prefix ) ) {
            return array( 'ok' => false );
        }

        return array(
            'ok'      => true,
            'path'    => $real,
            'is_file' => is_file( $real ),
            'is_dir'  => is_dir( $real ),
        );
    }


    private function run_code_shell_process( $argv, $cwd, $timeout, $limit ) {
        if ( ! $this->is_proc_open_runtime_available() ) {
            return $this->proc_open_unavailable_result();
        }

        $descriptors = array(
            0 => array( 'pipe', 'r' ),
            1 => array( 'pipe', 'w' ),
            2 => array( 'pipe', 'w' ),
        );

        $process = @proc_open( $argv, $descriptors, $pipes, $cwd, null, array( 'bypass_shell' => true ) );
        if ( ! is_resource( $process ) ) {
            return array(
                'ok'      => false,
                'success' => false,
                'error'   => 'Unable to start process.',
            );
        }

        fclose( $pipes[0] );
        stream_set_blocking( $pipes[1], false );
        stream_set_blocking( $pipes[2], false );

        $stdout     = '';
        $stderr     = '';
        $start      = microtime( true );
        $timed_out  = false;
        $exit_code  = null;
        $total_limit = max( 1000, intval( $limit ) );

        while ( true ) {
            $stdout .= (string) stream_get_contents( $pipes[1] );
            $stderr .= (string) stream_get_contents( $pipes[2] );

            if ( strlen( $stdout ) + strlen( $stderr ) > $total_limit * 2 ) {
                $stdout = substr( $stdout, 0, $total_limit );
                $stderr = substr( $stderr, 0, $total_limit );
            }

            $status = proc_get_status( $process );
            if ( empty( $status['running'] ) ) {
                $exit_code = isset( $status['exitcode'] ) ? intval( $status['exitcode'] ) : null;
                break;
            }

            if ( ( microtime( true ) - $start ) >= $timeout ) {
                $timed_out = true;
                proc_terminate( $process );
                break;
            }

            usleep( 50000 );
        }

        $stdout .= (string) stream_get_contents( $pipes[1] );
        $stderr .= (string) stream_get_contents( $pipes[2] );
        fclose( $pipes[1] );
        fclose( $pipes[2] );
        $close_code = proc_close( $process );
        if ( null === $exit_code && is_int( $close_code ) ) {
            $exit_code = $close_code;
        }

        $stdout = $this->redact_code_shell_output( $stdout );
        $stderr = $this->redact_code_shell_output( $stderr );

        return array(
            'ok'          => ! $timed_out && 0 === intval( $exit_code ),
            'success'     => ! $timed_out && 0 === intval( $exit_code ),
            'exit_code'   => null === $exit_code ? -1 : intval( $exit_code ),
            'timed_out'   => $timed_out,
            'duration_ms' => round( ( microtime( true ) - $start ) * 1000, 2 ),
            'stdout'      => $this->truncate_code_shell_output( $stdout, $limit ),
            'stderr'      => $this->truncate_code_shell_output( $stderr, $limit ),
        );
    }

    private function redact_code_shell_output( $text ) {
        $lines = preg_split( '/\r\n|\r|\n/', (string) $text );
        return implode( "\n", $this->redact_sensitive_file_lines( is_array( $lines ) ? $lines : array() ) );
    }

    private function truncate_code_shell_output( $text, $limit ) {
        $text  = (string) $text;
        $limit = max( 1000, intval( $limit ) );
        if ( strlen( $text ) <= $limit ) {
            return $text;
        }

        return substr( $text, 0, max( 0, $limit - 15 ) ) . "\n...[truncated]";
    }

    private function resolve_code_path( $path ) {
        $path = trim( (string) $path );
        if ( '' === $path ) {
            return array(
                'ok'    => false,
                'error' => 'Missing path.',
            );
        }

        $candidates = array();
        if ( preg_match( '/^[A-Za-z]:\\\\|^\//', $path ) ) {
            $candidates[] = $path;
        } else {
            $roots = $this->get_code_search_roots( 'all' );
            foreach ( $roots as $root ) {
                $candidates[] = trailingslashit( $root ) . ltrim( str_replace( array( '\\', '/' ), DIRECTORY_SEPARATOR, $path ), DIRECTORY_SEPARATOR );
            }
        }

        foreach ( $candidates as $candidate ) {
            $real = realpath( $candidate );
            if ( false === $real || ! is_file( $real ) ) {
                continue;
            }
            foreach ( $this->get_code_search_roots( 'all' ) as $root ) {
                if ( 0 === strpos( $real, $root ) ) {
                    return array(
                        'ok'   => true,
                        'path' => $real,
                    );
                }
            }
        }

        return array(
            'ok'    => false,
            'error' => 'Path is outside allowed WordPress code roots or file does not exist.',
        );
    }

    private function get_code_search_roots( $scope ) {
        $roots = array();
        $scope = trim( (string) $scope );

        $wp_root    = defined( 'ABSPATH' ) ? realpath( ABSPATH ) : false;
        $wp_content = defined( 'WP_CONTENT_DIR' ) ? realpath( WP_CONTENT_DIR ) : false;
        $plugin_dir = defined( 'NIWA_AI_DIR' ) ? realpath( NIWA_AI_DIR ) : false;
        $wp_admin   = $wp_root ? realpath( trailingslashit( $wp_root ) . 'wp-admin' ) : false;
        $wp_includes = $wp_root ? realpath( trailingslashit( $wp_root ) . 'wp-includes' ) : false;

        if ( in_array( $scope, array( 'wordpress_env', 'site_code', 'all', '' ), true ) && $wp_root ) {
            $roots[] = $wp_root;
        }
        if ( in_array( $scope, array( 'wp_content' ), true ) && $wp_content ) {
            $roots[] = $wp_content;
        }
        if ( in_array( $scope, array( 'wp_core' ), true ) ) {
            if ( $wp_admin ) {
                $roots[] = $wp_admin;
            }
            if ( $wp_includes ) {
                $roots[] = $wp_includes;
            }
            if ( $wp_root ) {
                $roots[] = $wp_root;
            }
        }
        if ( in_array( $scope, array( 'niwa_plugin', 'all' ), true ) && $plugin_dir ) {
            $roots[] = $plugin_dir;
        }

        $roots = array_values( array_unique( array_filter( $roots ) ) );
        return $this->collapse_nested_roots( $roots );
    }

    private function normalize_search_terms( $query ) {
        $query = remove_accents( strtolower( (string) $query ) );
        $raw   = preg_split( '/\s+/', preg_replace( '/[^\p{L}\p{N}\s_-]+/u', ' ', $query ) );
        $terms = array();
        foreach ( (array) $raw as $term ) {
            $term = trim( (string) $term );
            if ( strlen( $term ) >= 2 ) {
                $terms[] = $term;
            }
        }

        if ( ! empty( $terms ) ) {
            $terms[] = trim( strtolower( (string) $query ) );
        }

        return array_values( array_unique( array_filter( $terms ) ) );
    }

    private function build_code_snippet( $path, $center_line, $before = 2, $after = 1 ) {
        $lines = @file( $path, FILE_IGNORE_NEW_LINES );
        if ( false === $lines || ! is_array( $lines ) ) {
            return '';
        }

        $start = max( 1, intval( $center_line ) - max( 0, intval( $before ) ) );
        $end   = min( count( $lines ), intval( $center_line ) + max( 0, intval( $after ) ) );
        $slice = array_slice( $lines, $start - 1, ( $end - $start ) + 1 );

        return $this->build_numbered_snippet_from_lines( $this->redact_sensitive_file_lines( $slice, $this->to_relative_code_path( $path ) ), $start );
    }

    private function build_numbered_snippet_from_lines( $lines, $start_line ) {
        $formatted = array();
        $line_no   = intval( $start_line );
        foreach ( (array) $lines as $line ) {
            $formatted[] = $line_no . ': ' . rtrim( (string) $line );
            $line_no++;
        }

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

    private function format_code_lines( $lines, $start_line ) {
        $formatted = array();
        $line_no   = intval( $start_line );
        foreach ( (array) $lines as $line ) {
            $formatted[] = array(
                'line_number' => $line_no,
                'text'        => (string) $line,
            );
            $line_no++;
        }

        return $formatted;
    }

    private function read_tail_lines_from_file( $path, $tail_lines ) {
        $tail_lines = max( 1, min( 500, intval( $tail_lines ) ) );
        $handle     = @fopen( $path, 'r' );
        if ( ! $handle ) {
            return array(
                'ok'    => false,
                'error' => 'Ne mogu da otvorim fajl za tail read.',
            );
        }

        $lines = array();
        $total = 0;
        while ( false !== ( $line = fgets( $handle ) ) ) {
            $total++;
            $lines[] = rtrim( (string) $line, "\r\n" );
            if ( count( $lines ) > $tail_lines ) {
                array_shift( $lines );
            }
        }
        fclose( $handle );

        return array(
            'ok'          => true,
            'lines'       => $lines,
            'total_lines' => $total,
            'start_line'  => max( 1, $total - count( $lines ) + 1 ),
        );
    }

    private function redact_sensitive_file_lines( $lines, $relative_path = '' ) {
        $safe = array();
        foreach ( (array) $lines as $line ) {
            $safe[] = $this->redact_sensitive_file_line( (string) $line, $relative_path );
        }

        return $safe;
    }

    private function redact_sensitive_file_line( $line, $relative_path = '' ) {
        $line          = (string) $line;
        $relative_path = strtolower( (string) $relative_path );

        $line = preg_replace(
            "/(define\\(\\s*['\\\"](?:DB_NAME|DB_USER|DB_PASSWORD|DB_HOST|AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|NONCE_KEY|AUTH_SALT|SECURE_AUTH_SALT|LOGGED_IN_SALT|NONCE_SALT|WP_CACHE_KEY_SALT|OPENAI_API_KEY|ANTHROPIC_API_KEY)['\\\"]\\s*,\\s*)['\\\"][^'\\\"]*['\\\"](\\s*\\))/i",
            '$1\'[redacted]\'$2',
            $line
        );

        $line = preg_replace(
            "/((?:password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret|consumer[_-]?secret|authorization)\\s*['\\\"]?\\s*(?:=>|=|:)\\s*['\\\"]?)[^'\\\"\\s,;]+/i",
            '$1[redacted]',
            $line
        );

        $line = preg_replace( '/(Bearer\\s+)[A-Za-z0-9._~+\\/=:-]+/i', '$1[redacted]', $line );
        $line = preg_replace( '/sk-[A-Za-z0-9_-]{16,}/', 'sk-[redacted]', $line );

        if ( false !== strpos( $relative_path, '.env' ) ) {
            $line = preg_replace(
                '/^([A-Z0-9_]*(?:KEY|SECRET|TOKEN|PASSWORD|PASS|PWD)[A-Z0-9_]*\\s*=\\s*).+$/i',
                '$1[redacted]',
                $line
            );
        }

        return (string) $line;
    }

    private function is_editable_code_path( $path ) {
        $path = wp_normalize_path( (string) $path );
        $root = defined( 'ABSPATH' ) ? wp_normalize_path( untrailingslashit( ABSPATH ) ) : '';

        if ( '' === $root || '' === $path ) {
            return false;
        }

        return $path === $root || 0 === strpos( $path, trailingslashit( $root ) );
    }

    private function collapse_nested_roots( $roots ) {
        $roots = array_values( array_unique( array_map( 'wp_normalize_path', array_filter( (array) $roots ) ) ) );
        usort(
            $roots,
            function ( $a, $b ) {
                return strlen( (string) $a ) <=> strlen( (string) $b );
            }
        );

        $collapsed = array();
        foreach ( $roots as $root ) {
            $nested = false;
            foreach ( $collapsed as $existing ) {
                if ( 0 === strpos( (string) $root, trailingslashit( (string) $existing ) ) ) {
                    $nested = true;
                    break;
                }
            }
            if ( ! $nested ) {
                $collapsed[] = $root;
            }
        }

        return $collapsed;
    }

    private function to_relative_code_path( $path ) {
        $path    = str_replace( '\\', '/', (string) $path );
        $abspath = str_replace( '\\', '/', (string) ABSPATH );
        if ( 0 === strpos( $path, $abspath ) ) {
            return ltrim( substr( $path, strlen( $abspath ) ), '/' );
        }

        return $path;
    }

    private function truncate_block( $text, $max_chars ) {
        $text = trim( (string) $text );
        if ( '' === $text ) {
            return '';
        }

        $max_chars = max( 4, intval( $max_chars ) );

        if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) {
            if ( mb_strlen( $text, 'UTF-8' ) <= $max_chars ) {
                return $text;
            }

            return mb_substr( $text, 0, $max_chars - 3, 'UTF-8' ) . '...';
        }

        if ( strlen( $text ) <= $max_chars ) {
            return $text;
        }

        return substr( $text, 0, $max_chars - 3 ) . '...';
    }

    /**
     * Replace a specific block of text in a prepared workspace file.
     *
     * @param array  $args    Tool arguments.
     * @param array  $context Context array.
     * @param string $mode    Running mode.
     * @param array  $options Turn options.
     * @return array
     */
    private function execute_main_coding_file_edit( $args, $context, $mode = 'main', $options = array() ) {
        $args     = is_array( $args ) ? $args : array();
        $audit_id = $this->new_main_coding_audit_id( 'edit' );
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            $audit_path = $this->append_main_coding_audit_event( 'file_edit_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'not_main_runtime',
            ) );
            return array(
                'success'        => false,
                'error'          => 'main_coding_file_edit is available only in the main founder runtime or a write-enabled subagent runtime.',
                'audit_id'       => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $op_id               = isset( $args['operation_id'] ) ? (string) $args['operation_id'] : '';
        $work_path           = isset( $args['work_path'] ) ? (string) $args['work_path'] : '';
        $work_path           = $this->remap_legacy_main_coding_workspace_paths( $work_path );
        $dry_run             = ! empty( $args['dry_run'] );

        if ( '' === $op_id || '' === $work_path ) {
            return array(
                'success'  => false,
                'error'    => 'operation_id and work_path are required.',
                'audit_id' => $audit_id,
            );
        }

        // Strict path validation: must be a workspace write path AND contain /{op_id}/work/ segment
        // (substring match on op_id alone was too loose — could collide with stale folders).
        $work_path_normalized = $this->normalize_main_coding_shell_path( $work_path );
        $expected_segment     = '/' . trim( $op_id, '/' ) . '/work/';
        if ( ! $this->is_main_coding_workspace_write_path( $work_path ) || false === strpos( $work_path_normalized, $expected_segment ) ) {
            return array(
                'success'          => false,
                'error'            => 'Path must be inside the active workspace and contain the /{operation_id}/work/ path segment.',
                'expected_segment' => $expected_segment,
                'audit_id'         => $audit_id,
            );
        }

        if ( ! is_file( $work_path ) ) {
            return array(
                'success'  => false,
                'error'    => 'Target file copy does not exist in workspace. Run prepare first.',
                'audit_id' => $audit_id,
            );
        }

        $content = @file_get_contents( $work_path );
        if ( false === $content ) {
            return array(
                'success'  => false,
                'error'    => 'Failed to read work file.',
                'audit_id' => $audit_id,
            );
        }

        $edit = $this->apply_main_coding_file_edit_to_content( $content, $args, $work_path, $audit_id );
        if ( empty( $edit['success'] ) ) {
            return $edit;
        }

        $new_content     = isset( $edit['content'] ) ? (string) $edit['content'] : $content;
        $max_write_bytes = $this->get_main_coding_file_edit_max_write_bytes();
        $new_size        = strlen( $new_content );
        if ( $new_size > $max_write_bytes ) {
            return array(
                'success'         => false,
                'error'           => 'Resulting file is too large: ' . $new_size . ' bytes (max allowed: ' . $max_write_bytes . ' bytes). Split the change into smaller edits.',
                'audit_id'        => $audit_id,
                'resulting_bytes' => $new_size,
                'max_write_bytes' => $max_write_bytes,
            );
        }

        if ( $dry_run ) {
            $audit_path = $this->append_main_coding_audit_event( 'file_edit_dry_run', array(
                'audit_id'         => $audit_id,
                'mode'             => (string) $mode,
                'session_id'       => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
                'operation_id'     => $op_id,
                'work_path'        => $work_path,
                'match_mode'       => isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : 'exact',
                'occurrence_count' => isset( $edit['occurrence_count'] ) ? intval( $edit['occurrence_count'] ) : 0,
                'replaced_count'   => isset( $edit['replaced_count'] ) ? intval( $edit['replaced_count'] ) : 0,
            ) );

            return array(
                'success'          => true,
                'dry_run'          => true,
                'would_match'      => true,
                'would_write'      => false,
                'work_path'        => $work_path,
                'match_mode'       => isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : 'exact',
                'occurrence_count' => isset( $edit['occurrence_count'] ) ? intval( $edit['occurrence_count'] ) : 0,
                'replaced_count'   => isset( $edit['replaced_count'] ) ? intval( $edit['replaced_count'] ) : 0,
                'diff'             => $this->build_main_coding_file_edit_preview_diff( $content, $new_content ),
                'audit_id'         => $audit_id,
                'audit_log_path'   => $audit_path,
            ) + $this->build_main_coding_file_edit_transition_notice( $edit, $args );
        }

        if ( false === Niwa_AI_Admin_Autonomy_Workspace::write_private_file( $work_path, $new_content, LOCK_EX ) ) {
            return array(
                'success'  => false,
                'error'    => 'Failed to write changes to work copy.',
                'audit_id' => $audit_id,
            );
        }

        // Update the workspace manifest so apply/rollback/audit trails see the edit.
        $manifest_update = $this->update_main_coding_manifest_after_file_edit( $op_id, $work_path, $audit_id );

        $audit_path = $this->append_main_coding_audit_event( 'file_edit_success', array(
            'audit_id'         => $audit_id,
            'mode'             => (string) $mode,
            'session_id'       => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'operation_id'     => $op_id,
            'work_path'        => $work_path,
            'match_mode'       => isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : 'exact',
            'occurrence_count' => isset( $edit['occurrence_count'] ) ? intval( $edit['occurrence_count'] ) : 0,
            'replaced_count'   => isset( $edit['replaced_count'] ) ? intval( $edit['replaced_count'] ) : 0,
            'bytes_written'    => $new_size,
            'manifest_updated' => ! empty( $manifest_update['success'] ),
        ) );

        return array(
            'success'          => true,
            'message'          => 'File successfully updated in workspace.',
            'work_path'        => $work_path,
            'match_mode'       => isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : 'exact',
            'occurrence_count' => isset( $edit['occurrence_count'] ) ? intval( $edit['occurrence_count'] ) : 0,
            'replaced_count'   => isset( $edit['replaced_count'] ) ? intval( $edit['replaced_count'] ) : 0,
            'bytes_written'    => $new_size,
            'manifest_updated' => ! empty( $manifest_update['success'] ),
            'next_step'        => 'Verify with main_coding_git_diff and main_coding_lint_check, then call main_coding_workspace_apply.',
            'audit_id'         => $audit_id,
            'audit_log_path'   => $audit_path,
        ) + $this->build_main_coding_file_edit_transition_notice( $edit, $args );
    }

    /**
     * Read-only pre-prepare edit simulation against an allowed source file.
     */
    private function execute_main_coding_edit_dry_run( $args, $context, $mode = 'main', $options = array() ) {
        $args     = is_array( $args ) ? $args : array();
        $audit_id = $this->new_main_coding_audit_id( 'edit-dry-run' );
        if ( ! $this->can_execute_main_coding_tools( $mode, $options ) ) {
            $audit_path = $this->append_main_coding_audit_event( 'edit_dry_run_rejected', array(
                'audit_id' => $audit_id,
                'mode'     => (string) $mode,
                'reason'   => 'not_main_runtime',
            ) );
            if ( method_exists( $this, 'append_capability_audit_event' ) ) {
                $this->append_capability_audit_event( 'main_coding_edit_dry_run', 'rejected', $context, array(
                    'reason' => 'not_main_runtime',
                ) );
            }
            return array(
                'success'        => false,
                'error'          => 'main_coding_edit_dry_run is available only in the main founder runtime or a write-enabled subagent runtime.',
                'audit_id'       => $audit_id,
                'audit_log_path' => $audit_path,
            );
        }

        $path = isset( $args['path'] ) ? trim( (string) $args['path'] ) : ( isset( $args['target_path'] ) ? trim( (string) $args['target_path'] ) : '' );
        if ( '' === $path ) {
            if ( method_exists( $this, 'append_capability_audit_event' ) ) {
                $this->append_capability_audit_event( 'main_coding_edit_dry_run', 'rejected', $context, array(
                    'reason' => 'missing_path',
                ) );
            }
            return array(
                'success'  => false,
                'error'    => 'path is required.',
                'audit_id' => $audit_id,
            );
        }

        $resolved = $this->resolve_main_coding_file_edit_read_path( $path );
        if ( empty( $resolved['success'] ) ) {
            if ( method_exists( $this, 'append_capability_audit_event' ) ) {
                $this->append_capability_audit_event( 'main_coding_edit_dry_run', 'rejected', $context, array(
                    'reason' => 'path_validation_failed',
                    'path'   => $path,
                ) );
            }
            $resolved['audit_id'] = $audit_id;
            return $resolved;
        }

        $abs_path = isset( $resolved['path'] ) ? (string) $resolved['path'] : '';
        $content  = @file_get_contents( $abs_path );
        if ( false === $content ) {
            if ( method_exists( $this, 'append_capability_audit_event' ) ) {
                $this->append_capability_audit_event( 'main_coding_edit_dry_run', 'rejected', $context, array(
                    'reason' => 'read_failed',
                    'path'   => $path,
                ) );
            }
            return array(
                'success'  => false,
                'error'    => 'Failed to read file for dry-run.',
                'audit_id' => $audit_id,
            );
        }

        $edit = $this->apply_main_coding_file_edit_to_content( $content, $args, $abs_path, $audit_id );
        if ( empty( $edit['success'] ) ) {
            if ( method_exists( $this, 'append_capability_audit_event' ) ) {
                $this->append_capability_audit_event( 'main_coding_edit_dry_run', 'rejected', $context, array(
                    'reason'     => 'match_failed',
                    'path'       => $path,
                    'match_mode' => isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : '',
                    'error'      => isset( $edit['error'] ) ? (string) $edit['error'] : '',
                ) );
            }
            return $edit;
        }

        $new_content = isset( $edit['content'] ) ? (string) $edit['content'] : $content;
        $audit_path  = $this->append_main_coding_audit_event( 'edit_dry_run_success', array(
            'audit_id'         => $audit_id,
            'mode'             => (string) $mode,
            'session_id'       => isset( $context['session_id'] ) ? (string) $context['session_id'] : '',
            'path'             => $path,
            'resolved_path'    => $abs_path,
            'match_mode'       => isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : 'exact',
            'occurrence_count' => isset( $edit['occurrence_count'] ) ? intval( $edit['occurrence_count'] ) : 0,
            'replaced_count'   => isset( $edit['replaced_count'] ) ? intval( $edit['replaced_count'] ) : 0,
        ) );
        if ( method_exists( $this, 'append_capability_audit_event' ) ) {
            $this->append_capability_audit_event( 'main_coding_edit_dry_run', 'completed', $context, array(
                'path'             => $path,
                'match_mode'       => isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : 'exact',
                'occurrence_count' => isset( $edit['occurrence_count'] ) ? intval( $edit['occurrence_count'] ) : 0,
                'replaced_count'   => isset( $edit['replaced_count'] ) ? intval( $edit['replaced_count'] ) : 0,
            ) );
        }

        return array(
            'success'          => true,
            'dry_run'          => true,
            'would_match'      => true,
            'would_write'      => false,
            'path'             => isset( $resolved['relative_path'] ) ? (string) $resolved['relative_path'] : $path,
            'match_mode'       => isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : 'exact',
            'occurrence_count' => isset( $edit['occurrence_count'] ) ? intval( $edit['occurrence_count'] ) : 0,
            'replaced_count'   => isset( $edit['replaced_count'] ) ? intval( $edit['replaced_count'] ) : 0,
            'diff'             => $this->build_main_coding_file_edit_preview_diff( $content, $new_content ),
            'audit_id'         => $audit_id,
            'audit_log_path'   => $audit_path,
        ) + $this->build_main_coding_file_edit_transition_notice( $edit, $args );
    }

    private function build_main_coding_file_edit_transition_notice( $edit, $args ) {
        $edit  = is_array( $edit ) ? $edit : array();
        $args  = is_array( $args ) ? $args : array();
        $found = isset( $edit['occurrence_count'] ) ? intval( $edit['occurrence_count'] ) : 0;
        $done  = isset( $edit['replaced_count'] ) ? intval( $edit['replaced_count'] ) : 0;
        if ( $found <= 1 || $done >= $found || ! empty( $args['replace_all'] ) || array_key_exists( 'expected_occurrences', $args ) ) {
            return array();
        }

        $mode = isset( $edit['match_mode'] ) ? (string) $edit['match_mode'] : 'exact';
        return array(
            '_warning' => 'Found ' . $found . ' ' . $mode . ' matches but replaced only ' . $done . '. Set replace_all=true to replace all matches, or expected_occurrences=1 to confirm that replacing only the first match is intentional.',
            'suggested_next_step' => array(
                'tool'   => 'main_coding_file_edit',
                'reason' => 'Multiple matches were found while replace_all was not set.',
                'args_to_add' => array(
                    'replace_all' => true,
                ),
            ),
        );
    }

    private function resolve_main_coding_file_edit_read_path( $path ) {
        if ( ! class_exists( 'Niwa_AI_Admin_Autonomy_File_Guard' ) ) {
            $guard_file = dirname( __FILE__ ) . '/class-admin-autonomy-file-guard.php';
            if ( file_exists( $guard_file ) ) {
                require_once $guard_file;
            }
        }

        if ( class_exists( 'Niwa_AI_Admin_Autonomy_File_Guard' ) ) {
            $guard    = new Niwa_AI_Admin_Autonomy_File_Guard();
            $resolved = $guard->validate_read( $path );
            if ( empty( $resolved['success'] ) ) {
                return array(
                    'success' => false,
                    'error'   => isset( $resolved['error'] ) ? (string) $resolved['error'] : 'Path validation failed.',
                );
            }

            return array(
                'success'       => true,
                'path'          => isset( $resolved['path'] ) ? (string) $resolved['path'] : '',
                'relative_path' => isset( $resolved['relative_path'] ) ? (string) $resolved['relative_path'] : (string) $path,
                'risk_level'    => isset( $resolved['risk_level'] ) ? (string) $resolved['risk_level'] : 'unknown',
            );
        }

        $legacy = $this->resolve_code_path( $path );
        if ( empty( $legacy['ok'] ) ) {
            return array(
                'success' => false,
                'error'   => isset( $legacy['error'] ) ? (string) $legacy['error'] : 'Path validation failed.',
            );
        }

        return array(
            'success'       => true,
            'path'          => isset( $legacy['path'] ) ? (string) $legacy['path'] : '',
            'relative_path' => isset( $legacy['path'] ) ? $this->to_relative_code_path( $legacy['path'] ) : (string) $path,
            'risk_level'    => 'unknown',
        );
    }

    private function get_main_coding_file_edit_max_write_bytes() {
        return ( class_exists( 'Niwa_AI_Admin_Autonomy_File_Guard' ) && defined( 'Niwa_AI_Admin_Autonomy_File_Guard::MAX_WRITE_BYTES' ) )
            ? intval( Niwa_AI_Admin_Autonomy_File_Guard::MAX_WRITE_BYTES )
            : ( 8 * 1024 * 1024 );
    }

    private function apply_main_coding_file_edit_to_content( $content, $args, $work_path, $audit_id ) {
        $content    = (string) $content;
        $args       = is_array( $args ) ? $args : array();
        $match_mode = isset( $args['match_mode'] ) ? sanitize_key( (string) $args['match_mode'] ) : 'exact';
        if ( '' === $match_mode ) {
            $match_mode = 'exact';
        }
        if ( ! in_array( $match_mode, array( 'exact', 'anchor', 'line_range', 'regex' ), true ) ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'unsupported_match_mode',
                'Unsupported match_mode. Use exact, anchor, line_range, or regex.',
                $audit_id,
                $work_path,
                $content,
                isset( $args['target_content'] ) ? (string) $args['target_content'] : ''
            );
        }

        if ( 'anchor' === $match_mode ) {
            return $this->apply_main_coding_file_edit_anchor_mode( $content, $args, $work_path, $audit_id );
        }
        if ( 'line_range' === $match_mode ) {
            return $this->apply_main_coding_file_edit_line_range_mode( $content, $args, $work_path, $audit_id );
        }
        if ( 'regex' === $match_mode ) {
            return $this->apply_main_coding_file_edit_regex_mode( $content, $args, $work_path, $audit_id );
        }

        return $this->apply_main_coding_file_edit_exact_mode( $content, $args, $work_path, $audit_id );
    }

    private function apply_main_coding_file_edit_exact_mode( $content, $args, $work_path, $audit_id ) {
        $target_content      = isset( $args['target_content'] ) ? (string) $args['target_content'] : '';
        $replacement_content = isset( $args['replacement_content'] ) ? (string) $args['replacement_content'] : '';
        if ( '' === $target_content ) {
            return array(
                'success'  => false,
                'error'    => 'target_content cannot be empty.',
                'audit_id' => $audit_id,
            );
        }

        $normalize_whitespace = ! empty( $args['normalize_whitespace'] );
        $matches              = $normalize_whitespace
            ? $this->find_main_coding_file_edit_whitespace_matches( $content, $target_content )
            : $this->find_main_coding_file_edit_literal_matches( $content, $target_content );
        if ( empty( $matches ) ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'target_content_not_found',
                $normalize_whitespace ? 'target_content not found after whitespace normalization.' : 'target_content not found in the file. Ensure exact spacing, tabs, and line endings match.',
                $audit_id,
                $work_path,
                $content,
                $target_content
            );
        }

        $expected = $this->get_main_coding_file_edit_expected_occurrences( $args );
        if ( null !== $expected && count( $matches ) !== $expected ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'expected_occurrences_mismatch',
                'Expected ' . $expected . ' occurrences, found ' . count( $matches ) . '. Refusing to edit to prevent unintended changes.',
                $audit_id,
                $work_path,
                $content,
                $target_content
            );
        }

        $selected    = ! empty( $args['replace_all'] ) ? $matches : array( $matches[0] );
        $replacement = $this->normalize_main_coding_file_edit_line_endings( $content, $replacement_content );

        return array(
            'success'          => true,
            'content'          => $this->replace_main_coding_file_edit_ranges( $content, $selected, $replacement ),
            'match_mode'       => 'exact',
            'occurrence_count' => count( $matches ),
            'replaced_count'   => count( $selected ),
        );
    }

    private function apply_main_coding_file_edit_anchor_mode( $content, $args, $work_path, $audit_id ) {
        $start_anchor        = isset( $args['start_anchor'] ) ? (string) $args['start_anchor'] : '';
        $end_anchor          = isset( $args['end_anchor'] ) ? (string) $args['end_anchor'] : '';
        $replacement_content = isset( $args['replacement_content'] ) ? (string) $args['replacement_content'] : '';
        $include_anchors     = ! empty( $args['include_anchors'] );
        if ( '' === $start_anchor || '' === $end_anchor ) {
            return array(
                'success'  => false,
                'error'    => 'start_anchor and end_anchor are required for anchor match_mode.',
                'audit_id' => $audit_id,
            );
        }
        if ( $start_anchor === $end_anchor ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'anchor_pair_ambiguous',
                'start_anchor and end_anchor must be different for anchor match_mode.',
                $audit_id,
                $work_path,
                $content,
                $start_anchor
            );
        }

        $ranges = array();
        $offset = 0;
        while ( true ) {
            $start = strpos( $content, $start_anchor, $offset );
            if ( false === $start ) {
                break;
            }
            $end = strpos( $content, $end_anchor, $start + strlen( $start_anchor ) );
            if ( false === $end ) {
                break;
            }
            $nested_start = strpos( $content, $start_anchor, $start + strlen( $start_anchor ) );
            if ( false !== $nested_start && $nested_start < $end ) {
                return $this->build_main_coding_file_edit_failure_payload(
                    'nested_anchor_ambiguous',
                    'Nested start_anchor was found before the matching end_anchor. Refusing anchor edit to avoid replacing the wrong block; use line_range, regex, or more specific anchors.',
                    $audit_id,
                    $work_path,
                    $content,
                    $start_anchor . "\n" . $end_anchor
                );
            }
            $range_start = $include_anchors ? $start : $start + strlen( $start_anchor );
            $range_end   = $include_anchors ? $end + strlen( $end_anchor ) : $end;
            $ranges[]    = array(
                'start'  => $range_start,
                'length' => max( 0, $range_end - $range_start ),
            );
            $offset = $end + strlen( $end_anchor );
        }

        if ( empty( $ranges ) ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'anchor_pair_not_found',
                'Could not find start_anchor followed by end_anchor.',
                $audit_id,
                $work_path,
                $content,
                $start_anchor . "\n" . $end_anchor
            );
        }

        $expected = $this->get_main_coding_file_edit_expected_occurrences( $args );
        if ( null !== $expected && count( $ranges ) !== $expected ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'expected_occurrences_mismatch',
                'Expected ' . $expected . ' anchor block occurrences, found ' . count( $ranges ) . '.',
                $audit_id,
                $work_path,
                $content,
                $start_anchor . "\n" . $end_anchor
            );
        }

        $selected    = ! empty( $args['replace_all'] ) ? $ranges : array( $ranges[0] );
        $replacement = $this->normalize_main_coding_file_edit_line_endings( $content, $replacement_content );

        return array(
            'success'          => true,
            'content'          => $this->replace_main_coding_file_edit_ranges( $content, $selected, $replacement ),
            'match_mode'       => 'anchor',
            'occurrence_count' => count( $ranges ),
            'replaced_count'   => count( $selected ),
        );
    }

    private function apply_main_coding_file_edit_line_range_mode( $content, $args, $work_path, $audit_id ) {
        $start_line          = isset( $args['start_line'] ) ? intval( $args['start_line'] ) : 0;
        $end_line            = isset( $args['end_line'] ) ? intval( $args['end_line'] ) : 0;
        $replacement_content = isset( $args['replacement_content'] ) ? (string) $args['replacement_content'] : '';
        $range               = $this->get_main_coding_file_edit_line_range_offsets( $content, $start_line, $end_line );
        if ( empty( $range['success'] ) ) {
            return array(
                'success'  => false,
                'error'    => isset( $range['error'] ) ? (string) $range['error'] : 'Invalid line range.',
                'audit_id' => $audit_id,
            );
        }

        $expected = $this->get_main_coding_file_edit_expected_occurrences( $args );
        if ( null !== $expected && 1 !== $expected ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'expected_occurrences_mismatch',
                'line_range mode has exactly 1 replacement range; expected_occurrences must be 1 when provided.',
                $audit_id,
                $work_path,
                $content,
                ''
            );
        }

        $replacement = $this->normalize_main_coding_file_edit_line_endings( $content, $replacement_content );

        return array(
            'success'          => true,
            'content'          => substr_replace( $content, $replacement, intval( $range['start'] ), intval( $range['length'] ) ),
            'match_mode'       => 'line_range',
            'occurrence_count' => 1,
            'replaced_count'   => 1,
        );
    }

    private function apply_main_coding_file_edit_regex_mode( $content, $args, $work_path, $audit_id ) {
        $pattern_raw         = isset( $args['pattern'] ) ? (string) $args['pattern'] : '';
        $replacement_content = isset( $args['replacement'] ) ? (string) $args['replacement'] : ( isset( $args['replacement_content'] ) ? (string) $args['replacement_content'] : '' );
        $pattern             = $this->build_main_coding_file_edit_regex_pattern( $pattern_raw, isset( $args['flags'] ) ? (string) $args['flags'] : '' );
        if ( '' === $pattern ) {
            return array(
                'success'  => false,
                'error'    => 'pattern is required for regex match_mode.',
                'audit_id' => $audit_id,
            );
        }

        $match_result = @preg_match_all( $pattern, $content, $unused );
        if ( false === $match_result ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'invalid_regex',
                'Invalid PCRE pattern.',
                $audit_id,
                $work_path,
                $content,
                $pattern_raw
            );
        }
        if ( 0 === intval( $match_result ) ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'regex_no_matches',
                'Regex pattern matched 0 occurrences.',
                $audit_id,
                $work_path,
                $content,
                $pattern_raw
            );
        }

        $expected = $this->get_main_coding_file_edit_expected_occurrences( $args );
        if ( null !== $expected && intval( $match_result ) !== $expected ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'expected_occurrences_mismatch',
                'Expected ' . $expected . ' regex occurrences, found ' . intval( $match_result ) . '.',
                $audit_id,
                $work_path,
                $content,
                $pattern_raw
            );
        }

        $replacement = $this->normalize_main_coding_file_edit_line_endings( $content, $replacement_content );
        $limit       = ! empty( $args['replace_all'] ) ? -1 : 1;
        $count       = 0;
        $new_content = @preg_replace( $pattern, $replacement, $content, $limit, $count );
        if ( ! is_string( $new_content ) ) {
            return $this->build_main_coding_file_edit_failure_payload(
                'regex_replace_failed',
                'Regex replacement failed.',
                $audit_id,
                $work_path,
                $content,
                $pattern_raw
            );
        }

        return array(
            'success'          => true,
            'content'          => $new_content,
            'match_mode'       => 'regex',
            'occurrence_count' => intval( $match_result ),
            'replaced_count'   => intval( $count ),
        );
    }

    private function get_main_coding_file_edit_expected_occurrences( $args ) {
        if ( ! is_array( $args ) || ! array_key_exists( 'expected_occurrences', $args ) || '' === (string) $args['expected_occurrences'] ) {
            return null;
        }

        return max( 0, intval( $args['expected_occurrences'] ) );
    }

    private function find_main_coding_file_edit_literal_matches( $content, $target ) {
        $matches = array();
        $offset  = 0;
        $target  = (string) $target;
        while ( '' !== $target ) {
            $pos = strpos( $content, $target, $offset );
            if ( false === $pos ) {
                break;
            }
            $matches[] = array(
                'start'  => $pos,
                'length' => strlen( $target ),
            );
            $offset = $pos + max( 1, strlen( $target ) );
        }

        return $matches;
    }

    private function find_main_coding_file_edit_whitespace_matches( $content, $target ) {
        $normalized_content = $this->normalize_main_coding_file_edit_text_with_map( $content );
        $normalized_target  = $this->normalize_main_coding_file_edit_text_with_map( $target );
        $haystack           = isset( $normalized_content['text'] ) ? (string) $normalized_content['text'] : '';
        $needle             = isset( $normalized_target['text'] ) ? (string) $normalized_target['text'] : '';
        $map                = isset( $normalized_content['map'] ) && is_array( $normalized_content['map'] ) ? $normalized_content['map'] : array();
        $matches            = array();
        $offset             = 0;
        while ( '' !== $needle ) {
            $pos = strpos( $haystack, $needle, $offset );
            if ( false === $pos ) {
                break;
            }
            $last = $pos + strlen( $needle ) - 1;
            if ( isset( $map[ $pos ], $map[ $last ] ) ) {
                $start     = intval( $map[ $pos ]['start'] );
                $end       = intval( $map[ $last ]['end'] );
                $matches[] = array(
                    'start'  => $start,
                    'length' => max( 0, $end - $start ),
                );
            }
            $offset = $pos + max( 1, strlen( $needle ) );
        }

        return $matches;
    }

    private function normalize_main_coding_file_edit_text_with_map( $text ) {
        $text   = (string) $text;
        $length = strlen( $text );
        $out    = '';
        $map    = array();
        for ( $i = 0; $i < $length; $i++ ) {
            if ( preg_match( '/\s/', $text[ $i ] ) ) {
                $start = $i;
                while ( $i < $length && preg_match( '/\s/', $text[ $i ] ) ) {
                    $i++;
                }
                $out   .= ' ';
                $map[] = array( 'start' => $start, 'end' => $i );
                $i--;
                continue;
            }

            $out   .= $text[ $i ];
            $map[] = array( 'start' => $i, 'end' => $i + 1 );
        }

        return array(
            'text' => $out,
            'map'  => $map,
        );
    }

    private function replace_main_coding_file_edit_ranges( $content, $ranges, $replacement ) {
        $ranges = is_array( $ranges ) ? $ranges : array();
        usort(
            $ranges,
            function ( $left, $right ) {
                return intval( $right['start'] ) <=> intval( $left['start'] );
            }
        );

        foreach ( $ranges as $range ) {
            $content = substr_replace( $content, (string) $replacement, intval( $range['start'] ), intval( $range['length'] ) );
        }

        return $content;
    }

    private function get_main_coding_file_edit_line_range_offsets( $content, $start_line, $end_line ) {
        $start_line = intval( $start_line );
        $end_line   = intval( $end_line );
        if ( $start_line < 1 || $end_line < $start_line ) {
            return array( 'success' => false, 'error' => 'start_line must be >= 1 and end_line must be >= start_line.' );
        }

        $line_starts = array( 0 );
        $length      = strlen( $content );
        for ( $i = 0; $i < $length; $i++ ) {
            if ( "\n" === $content[ $i ] ) {
                $line_starts[] = $i + 1;
            }
        }
        $total_lines = count( $line_starts );
        if ( $start_line > $total_lines ) {
            return array( 'success' => false, 'error' => 'start_line is beyond the end of file. File has ' . $total_lines . ' lines.' );
        }
        if ( $end_line > $total_lines ) {
            return array( 'success' => false, 'error' => 'end_line is beyond the end of file. File has ' . $total_lines . ' lines.' );
        }

        $start = $line_starts[ $start_line - 1 ];
        $end   = $end_line < $total_lines ? $line_starts[ $end_line ] : $length;

        return array(
            'success' => true,
            'start'   => $start,
            'length'  => max( 0, $end - $start ),
        );
    }

    private function build_main_coding_file_edit_regex_pattern( $pattern, $flags ) {
        $pattern = (string) $pattern;
        if ( '' === $pattern ) {
            return '';
        }

        $flags = preg_replace( '/[^imsxuADSUXJ]/', '', (string) $flags );
        $flags = is_string( $flags ) ? $flags : '';
        if ( preg_match( '#^(.).+\1[imsxuADSUXJ]*$#s', $pattern ) ) {
            return $pattern;
        }

        return '~' . str_replace( '~', '\~', $pattern ) . '~' . $flags;
    }

    private function build_main_coding_file_edit_preview_diff( $before, $after, $context_lines = 3 ) {
        $before_lines = preg_split( "/(\r\n|\n|\r)/", (string) $before );
        $after_lines  = preg_split( "/(\r\n|\n|\r)/", (string) $after );
        if ( ! is_array( $before_lines ) ) {
            $before_lines = array();
        }
        if ( ! is_array( $after_lines ) ) {
            $after_lines = array();
        }
        if ( $before_lines === $after_lines ) {
            return "No changes.\n";
        }

        $prefix = 0;
        while ( isset( $before_lines[ $prefix ], $after_lines[ $prefix ] ) && $before_lines[ $prefix ] === $after_lines[ $prefix ] ) {
            $prefix++;
        }

        $before_suffix = count( $before_lines ) - 1;
        $after_suffix  = count( $after_lines ) - 1;
        while ( $before_suffix >= $prefix && $after_suffix >= $prefix && $before_lines[ $before_suffix ] === $after_lines[ $after_suffix ] ) {
            $before_suffix--;
            $after_suffix--;
        }

        $context_lines = max( 0, min( 10, intval( $context_lines ) ) );
        $before_start  = max( 0, $prefix - $context_lines );
        $after_start   = max( 0, $prefix - $context_lines );
        $before_end    = min( count( $before_lines ) - 1, $before_suffix + $context_lines );
        $after_end     = min( count( $after_lines ) - 1, $after_suffix + $context_lines );
        $out           = array(
            '--- before',
            '+++ after',
            '@@ -' . ( $before_start + 1 ) . ',' . max( 0, $before_end - $before_start + 1 ) . ' +' . ( $after_start + 1 ) . ',' . max( 0, $after_end - $after_start + 1 ) . ' @@',
        );

        for ( $i = $before_start; $i <= $before_end; $i++ ) {
            if ( $i < $prefix || $i > $before_suffix ) {
                $out[] = ' ' . $before_lines[ $i ];
            } else {
                $out[] = '-' . $before_lines[ $i ];
            }
        }
        for ( $i = $after_start; $i <= $after_end; $i++ ) {
            if ( $i >= $prefix && $i <= $after_suffix ) {
                $out[] = '+' . $after_lines[ $i ];
            }
        }

        return $this->truncate_block( implode( "\n", $out ), 12000 );
    }

    /**
     * Normalize the replacement content to match the dominant line-ending
     * style of the surrounding file. Prevents silent mixed CRLF/LF that
     * would later trip the apply preflight or leave inconsistent files.
     */
    private function normalize_main_coding_file_edit_line_endings( $original_content, $replacement_content ) {
        $original_content    = (string) $original_content;
        $replacement_content = (string) $replacement_content;

        $crlf_count = substr_count( $original_content, "\r\n" );
        if ( 0 === $crlf_count ) {
            // Original is pure-LF (or has no newlines) — leave replacement as-is.
            return $replacement_content;
        }

        $bare_lf_count = substr_count( $original_content, "\n" ) - $crlf_count;
        if ( $bare_lf_count > $crlf_count ) {
            // Mixed but LF-dominant — do not impose CRLF.
            return $replacement_content;
        }

        // Original is CRLF-dominant. Coerce any LF in the replacement to CRLF,
        // collapsing first so we never double-convert existing CRLFs.
        $normalized = str_replace( "\r\n", "\n", $replacement_content );
        $normalized = str_replace( "\r", "\n", $normalized );
        $normalized = str_replace( "\n", "\r\n", $normalized );

        return $normalized;
    }

    private function build_main_coding_file_edit_failure_payload( $failure_reason, $error, $audit_id, $work_path = '', $content = '', $target_content = '' ) {
        $audit_path = $this->append_main_coding_audit_event( 'file_edit_failed', array(
            'audit_id'       => (string) $audit_id,
            'failure_reason' => sanitize_key( (string) $failure_reason ),
            'work_path'      => (string) $work_path,
            'target_bytes'   => strlen( (string) $target_content ),
            'error'          => (string) $error,
        ) );

        $payload = array(
            'success'        => false,
            'error'          => (string) $error,
            'failure_reason' => sanitize_key( (string) $failure_reason ),
            'audit_id'       => (string) $audit_id,
            'audit_log_path' => $audit_path,
            'suggested_fallback' => array(
                'primary_tool' => 'main_coding_file_edit',
                'primary_fix'  => 'Re-read the returned work_path window, copy a more exact target_content block, and retry main_coding_file_edit first.',
                'fallback_tool' => 'main_coding_shell_run',
                'fallback_reason' => 'Use shell only when exact text replacement cannot express the change, such as regex/bulk edits or generated dynamic content.',
                'shell_scope'  => 'Only edit the same prepared work_path file, never the production target directly.',
            ),
        );

        if ( '' !== (string) $work_path ) {
            $payload['work_path'] = (string) $work_path;
        }

        if ( '' !== (string) $content && '' !== (string) $target_content ) {
            $payload['nearest_match'] = $this->find_main_coding_file_edit_nearest_match( $content, $target_content );
        }

        return $payload;
    }

    private function find_main_coding_file_edit_nearest_match( $content, $target_content ) {
        $content        = (string) $content;
        $target_content = (string) $target_content;
        $lines          = preg_split( "/(\r\n|\n|\r)/", $content );
        if ( ! is_array( $lines ) || empty( $lines ) || '' === trim( $target_content ) ) {
            return array();
        }

        $target_lines = preg_split( "/(\r\n|\n|\r)/", trim( $target_content ) );
        $window_size  = max( 1, min( 12, is_array( $target_lines ) ? count( $target_lines ) : 1 ) );
        $target_norm  = $this->normalize_main_coding_file_edit_similarity_text( $target_content );
        $best         = array(
            'score'      => -1,
            'line_start' => 1,
            'line_end'   => min( count( $lines ), $window_size ),
            'excerpt'    => '',
        );

        $line_count = count( $lines );
        for ( $i = 0; $i < $line_count; $i++ ) {
            $slice = array_slice( $lines, $i, $window_size );
            if ( empty( $slice ) ) {
                continue;
            }
            $excerpt = implode( "\n", $slice );
            $score   = $this->score_main_coding_file_edit_similarity( $target_norm, $this->normalize_main_coding_file_edit_similarity_text( $excerpt ) );
            if ( $score > $best['score'] ) {
                $best = array(
                    'score'      => $score,
                    'line_start' => $i + 1,
                    'line_end'   => min( $line_count, $i + count( $slice ) ),
                    'excerpt'    => $this->truncate_block( $excerpt, 1200 ),
                );
            }
        }

        return array(
            'line_start'  => $best['line_start'],
            'line_end'    => $best['line_end'],
            'confidence'  => max( 0, min( 1, round( $best['score'], 3 ) ) ),
            'match_basis' => 'normalized_levenshtein_window',
            'excerpt'     => $best['excerpt'],
        );
    }

    private function normalize_main_coding_file_edit_similarity_text( $text ) {
        $text = strtolower( trim( (string) $text ) );
        $text = preg_replace( '/\s+/', ' ', $text );
        return is_string( $text ) ? $text : '';
    }

    private function score_main_coding_file_edit_similarity( $left, $right ) {
        $left  = (string) $left;
        $right = (string) $right;
        if ( '' === $left || '' === $right ) {
            return 0.0;
        }
        if ( false !== strpos( $right, $left ) || false !== strpos( $left, $right ) ) {
            return 1.0;
        }

        $max_len = max( strlen( $left ), strlen( $right ) );
        if ( $max_len < 1 ) {
            return 0.0;
        }

        $left_sample  = substr( $left, 0, 240 );
        $right_sample = substr( $right, 0, 240 );
        $distance     = levenshtein( $left_sample, $right_sample );
        $sample_max   = max( strlen( $left_sample ), strlen( $right_sample ), 1 );

        return max( 0.0, 1.0 - ( $distance / $sample_max ) );
    }

    /**
     * Load the operation manifest, stamp the file_edit metadata, save back.
     * Non-fatal: a manifest failure does not block the on-disk edit.
     */
    private function update_main_coding_manifest_after_file_edit( $op_id, $work_path, $audit_id ) {
        $loaded = $this->load_main_coding_manifest( $op_id );
        if ( empty( $loaded['success'] ) ) {
            return array(
                'success' => false,
                'error'   => isset( $loaded['error'] ) ? (string) $loaded['error'] : 'manifest load failed',
            );
        }

        $manifest = isset( $loaded['manifest'] ) && is_array( $loaded['manifest'] ) ? $loaded['manifest'] : array();

        $manifest['last_file_edit_at']       = time();
        $manifest['last_file_edit_audit_id'] = (string) $audit_id;
        $manifest['last_file_edit_path']     = $this->normalize_main_coding_shell_path( $work_path );
        $manifest['file_edit_count']         = isset( $manifest['file_edit_count'] ) ? intval( $manifest['file_edit_count'] ) + 1 : 1;

        if ( ! $this->save_main_coding_manifest( $manifest ) ) {
            return array( 'success' => false, 'error' => 'manifest save failed' );
        }

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