<?php
if (!defined('ABSPATH')) exit;

if (!class_exists('VMO_Project_Media_Queue')) {
    class VMO_Project_Media_Queue {
        const META_QUEUE = '_vmo_project_media_queue';
        const META_TOKEN = '_vmo_project_media_queue_token';
        const PROCESS_HOOK = 'vmo_process_project_media_queue_event';
        const LOCK_PREFIX = 'vmo_project_media_queue_lock_';

        public function __construct() {
            add_action('wp_ajax_vmo_queue_project_media', array($this, 'ajax_queue_project_media'));
            add_action('wp_ajax_vmo_get_project_media_queue', array($this, 'ajax_get_project_media_queue'));
            add_action('wp_ajax_vmo_process_project_media_queue', array($this, 'ajax_process_project_media_queue'));
            add_action('wp_ajax_nopriv_vmo_process_project_media_queue', array($this, 'ajax_process_project_media_queue'));
            add_action(self::PROCESS_HOOK, array($this, 'process_project_media_queue_event'));
        }

        protected function prepare_long_running_runtime() {
            if (function_exists('set_time_limit')) {
                @set_time_limit(0);
            }
            if (function_exists('ignore_user_abort')) {
                @ignore_user_abort(true);
            }
            if (function_exists('ini_set')) {
                @ini_set('memory_limit', '512M');
            }
        }

        public function ajax_queue_project_media() {
            check_ajax_referer('vmo_builder_nonce', 'security');

            if (!is_user_logged_in()) {
                wp_send_json_error(array('message' => 'Not logged in.'), 403);
            }

            $post_id = $this->resolve_post_id($_POST);
            $post = $this->get_editable_post($post_id, get_current_user_id());
            if (!$post) {
                wp_send_json_error(array('message' => 'A saved project or draft is required before queueing media.'), 404);
            }

            $targets = $this->decode_targets($_POST['targets'] ?? '');
            $queue = $this->merge_targets_into_queue($post, $targets);
            $this->save_queue($post->ID, $queue);

            if (!empty($queue['items'])) {
                $this->dispatch_async($post->ID);
                $this->schedule_fallback($post->ID);
            }

            wp_send_json_success($this->prepare_queue_response($queue));
        }

        public function ajax_get_project_media_queue() {
            check_ajax_referer('vmo_builder_nonce', 'security');

            if (!is_user_logged_in()) {
                wp_send_json_error(array('message' => 'Not logged in.'), 403);
            }

            $post_id = $this->resolve_post_id($_POST);
            $post = $this->get_editable_post($post_id, get_current_user_id());
            if (!$post) {
                wp_send_json_error(array('message' => 'Project not found.'), 404);
            }

            $queue = $this->get_queue($post->ID);
            if ($queue && $this->queue_has_pending_work($queue)) {
                $this->dispatch_async($post->ID);
                $this->schedule_fallback($post->ID);
            }

            wp_send_json_success($this->prepare_queue_response($queue ?: $this->empty_queue($post->ID, $post->post_author)));
        }

        public function ajax_process_project_media_queue() {
            $this->prepare_long_running_runtime();

            $post_id = $this->resolve_post_id($_REQUEST);
            if (!$post_id) {
                wp_send_json_error(array('message' => 'Project ID required.'), 400);
            }

            if (!$this->authorize_process_request($post_id, $_REQUEST['token'] ?? '')) {
                wp_send_json_error(array('message' => 'Unauthorized queue request.'), 403);
            }

            $queue = $this->process_queue($post_id);
            if (is_wp_error($queue)) {
                wp_send_json_error(array('message' => $queue->get_error_message()), 500);
            }

            wp_send_json_success($this->prepare_queue_response($queue));
        }

        public function process_project_media_queue_event($post_id) {
            $this->prepare_long_running_runtime();
            $this->process_queue(intval($post_id));
        }

        public function enqueue_manifest_media_for_post($post_id, array $context = array()) {
            $post_id = intval($post_id);
            $post = $post_id ? get_post($post_id) : null;
            if (!$post) {
                return new WP_Error('missing_post', 'Project not found.');
            }
            if (!isset($context['media_assets'])) {
                $context['media_assets'] = $this->get_post_media_assets($post_id);
            }

            $html = (string) $post->post_content;
            $html_with_ids = $html;
            $targets = $this->extract_targets_from_html($html, $html_with_ids, $context);

            if ($html_with_ids !== $html) {
                if (function_exists('vmo_with_generated_html_post_kses_disabled')) {
                    vmo_with_generated_html_post_kses_disabled(function () use ($post_id, $html_with_ids) {
                        return wp_update_post(array(
                            'ID' => $post_id,
                            'post_content' => $html_with_ids,
                        ));
                    });
                } else {
                    wp_update_post(array(
                        'ID' => $post_id,
                        'post_content' => $html_with_ids,
                    ));
                }
                update_post_meta($post_id, 'vmo_project_raw_html', $html_with_ids);
                $post->post_content = $html_with_ids;
                $html = $html_with_ids;
            }

            $manifest = function_exists('vmo_get_website_media_manifest_service')
                ? vmo_get_website_media_manifest_service()->build_from_html($html, $context)
                : array('required_assets' => array());
            if (is_array($manifest)) {
                update_post_meta($post_id, 'vmo_media_manifest', wp_json_encode($manifest));
            }

            $targets = array_merge($targets, $this->targets_from_manifest_assets($manifest, $targets, $context));
            if (!$targets) {
                return $this->prepare_queue_response($this->get_queue($post_id) ?: $this->empty_queue($post_id, intval($post->post_author)));
            }

            $queue = $this->merge_targets_into_queue($post, $targets);
            $this->save_queue($post_id, $queue);

            if (!empty($queue['items'])) {
                $this->dispatch_async($post_id);
                $this->schedule_fallback($post_id);
            }

            if ($this->queue_has_pending_work($queue)) {
                update_post_meta($post_id, 'vmo_media_complete', 'no');
                update_post_meta($post_id, 'vmo_preview_ready', 'no');
                update_post_meta($post_id, 'vmo_generation_completed', 'no');
                $this->mark_operations_manifest_media_pending($post_id, $queue);
            }

            return $this->prepare_queue_response($queue);
        }

        protected function process_queue($post_id) {
            $this->prepare_long_running_runtime();

            $post_id = intval($post_id);
            if (!$post_id) {
                return new WP_Error('missing_post', 'Project ID is required.');
            }

            if (!$this->acquire_lock($post_id)) {
                return $this->get_queue($post_id) ?: $this->empty_queue($post_id, 0);
            }

            try {
                $post = get_post($post_id);
                if (!$post) {
                    return new WP_Error('missing_post', 'Project not found.');
                }

                $queue = $this->get_queue($post_id);
                if (!$queue) {
                    return $this->empty_queue($post_id, intval($post->post_author));
                }

                $item_index = $this->find_next_item_index($queue);
                if ($item_index === -1) {
                    $this->refresh_queue($queue);
                    $this->save_queue($post_id, $queue);
                    if (!$this->queue_has_pending_work($queue) && !empty($queue['counts']['total'])) {
                        $this->refresh_publish_readiness($post);
                    }
                    return $queue;
                }

                $item = $queue['items'][$item_index];
                $processed_item = $this->process_item($post, $item);
                $queue['items'][$item_index] = $processed_item;

                if (!empty($processed_item['status']) && $processed_item['status'] === 'complete' && !empty($processed_item['url'])) {
                    $this->apply_item_to_post($post, $processed_item);
                }

                $this->refresh_queue($queue);
                $this->save_queue($post_id, $queue);

                if ($this->queue_has_pending_work($queue)) {
                    $this->dispatch_async($post_id);
                    $this->schedule_fallback($post_id);
                } else {
                    $this->refresh_publish_readiness($post);
                }

                return $queue;
            } finally {
                $this->release_lock($post_id);
            }
        }

        protected function process_item($post, $item) {
            $item['updated_at'] = current_time('mysql');
            $item['attempts'] = intval($item['attempts'] ?? 0) + 1;
            $item['error'] = '';

            if (($item['type'] ?? '') === 'video') {
                return $this->process_video_item($post, $item);
            }

            return $this->process_image_item($post, $item);
        }

        protected function process_image_item($post, $item) {
            $this->prepare_long_running_runtime();

            $item['status'] = 'processing';

            $result = function_exists('vmo_generate_image_asset')
                ? vmo_generate_image_asset(array(
                    'user_id' => intval($post->post_author),
                    'desc' => $item['prompt'] ?? '',
                    'position' => 'auto',
                    'size' => '1024x1024',
                    'section' => 'website-content',
                    'project_id' => intval($post->ID),
                    'charge_tokens' => empty($item['tokens_charged']),
                    'require_consultation' => true,
                ))
                : array('error' => 'Image helper is unavailable.');

            if (!empty($result['error'])) {
                $item['status'] = 'failed';
                $item['error'] = $result['error'];
                return $item;
            }

            $item['status'] = 'complete';
            $item['url'] = esc_url_raw($result['url'] ?? '');
            $item['attachment_id'] = intval($result['attachment_id'] ?? 0);
            $item['tokens_charged'] = true;
            $item['completed_at'] = current_time('mysql');

            return $item;
        }

        protected function process_video_item($post, $item) {
            $this->prepare_long_running_runtime();

            $generator = $this->get_video_generator();
            if (!$generator) {
                $item['status'] = 'failed';
                $item['error'] = 'Video generator is unavailable.';
                return $item;
            }

            $model = sanitize_text_field($item['model'] ?? 'sora-2');
            $seconds = sanitize_text_field($item['seconds'] ?? '4');
            $size = sanitize_text_field($item['size'] ?? '1280x720');
            $user_id = intval($post->post_author);

            if (empty($item['remote_job_id'])) {
                $token_cost = class_exists('VMO_Sora_Video_Generator')
                    ? VMO_Sora_Video_Generator::get_token_cost($model, $seconds)
                    : 25200;

                if (empty($item['tokens_charged']) && function_exists('vmo_deduct_tokens')) {
                    if (!vmo_deduct_tokens($user_id, $token_cost, 'sora_video', 'Queued AI video generation')) {
                        $item['status'] = 'failed';
                        $item['error'] = 'Insufficient tokens for video generation.';
                        return $item;
                    }
                    $item['tokens_charged'] = true;
                    $item['token_cost'] = $token_cost;
                }

                $result = $generator->generate_video($item['prompt'] ?? '', array(
                    'model' => $model,
                    'seconds' => $seconds,
                    'size' => $size,
                ));

                if (!empty($result['error'])) {
                    $this->refund_video_tokens($user_id, $item);
                    $item['status'] = 'failed';
                    $item['error'] = $result['error'];
                    return $item;
                }

                $item['status'] = 'processing';
                $item['remote_job_id'] = sanitize_text_field($result['video_id'] ?? '');
                $item['progress'] = intval($result['progress'] ?? 0);
                $item['started_at'] = current_time('mysql');
                return $item;
            }

            $status = $generator->get_video_status($item['remote_job_id']);
            if (!empty($status['error'])) {
                $item['status'] = 'processing';
                $item['error'] = '';
                return $item;
            }

            $normalized = strtolower($status['status'] ?? '');
            $item['progress'] = intval($status['progress'] ?? 0);
            $item['status_text'] = $normalized;

            if ($normalized === 'completed' || $normalized === 'succeeded') {
                $download = $generator->download_and_save_video(
                    $item['remote_job_id'],
                    $user_id,
                    'VMO Website Video',
                    array(
                        'video_type' => 'website_background',
                        'prompt' => $item['prompt'] ?? '',
                        'project_id' => intval($post->ID),
                    )
                );

                if (!empty($download['error'])) {
                    $item['status'] = 'failed';
                    $item['error'] = $download['error'];
                    return $item;
                }

                $item['status'] = 'complete';
                $item['url'] = esc_url_raw($download['url'] ?? '');
                $item['attachment_id'] = intval($download['attachment_id'] ?? 0);
                $item['completed_at'] = current_time('mysql');
                return $item;
            }

            if ($normalized === 'failed' || $normalized === 'error') {
                $this->refund_video_tokens($user_id, $item);
                $item['status'] = 'failed';
                $item['error'] = is_array($status['error'] ?? null)
                    ? sanitize_text_field($status['error']['message'] ?? 'Video generation failed.')
                    : sanitize_text_field($status['error'] ?? 'Video generation failed.');
                return $item;
            }

            $item['status'] = 'processing';
            return $item;
        }

        protected function refund_video_tokens($user_id, &$item) {
            if (empty($item['tokens_charged']) || !empty($item['tokens_refunded']) || !function_exists('vmo_add_tokens')) {
                return;
            }

            $amount = intval($item['token_cost'] ?? 0);
            if ($amount > 0) {
                vmo_add_tokens($user_id, $amount, 'sora_refund', 'Queued video generation failed - refund');
                $item['tokens_refunded'] = true;
            }
        }

        protected function apply_item_to_post($post, $item) {
            $updated_post_html = $this->apply_media_to_markup((string) $post->post_content, $item);
            if ($updated_post_html !== (string) $post->post_content) {
                if (function_exists('vmo_with_generated_html_post_kses_disabled')) {
                    vmo_with_generated_html_post_kses_disabled(function () use ($post, $updated_post_html) {
                        return wp_update_post(array(
                            'ID' => intval($post->ID),
                            'post_content' => $updated_post_html,
                        ));
                    });
                } else {
                    wp_update_post(array(
                        'ID' => intval($post->ID),
                        'post_content' => $updated_post_html,
                    ));
                }
                update_post_meta($post->ID, 'vmo_project_raw_html', $updated_post_html);
                $post->post_content = $updated_post_html;
            }

            $updated_state = null;
            $state_json = get_post_meta($post->ID, 'vmo_state', true);
            if ($state_json) {
                $state = json_decode($state_json, true);
                if (is_array($state)) {
                    $updated_state = $this->apply_item_to_state($state, $item);
                    update_post_meta($post->ID, 'vmo_state', wp_json_encode($updated_state));
                }
            }

            $updated_pages_map = null;
            $pages_map = json_decode((string) get_post_meta($post->ID, 'vmo_pages_map', true), true);
            if (is_array($pages_map)) {
                $updated_pages_map = $this->apply_item_to_pages_map($pages_map, $item);
                update_post_meta($post->ID, 'vmo_pages_map', wp_json_encode($updated_pages_map));
            }

            update_post_meta($post->ID, 'vmo_last_updated', current_time('mysql'));

            if ($post->post_type === 'vmo_project') {
                $this->sync_project_row($post, $updated_post_html);
            }

            $this->sync_consultation_website_data($post, $updated_post_html, $updated_state, $updated_pages_map);
        }

        protected function sync_project_row($post, $html) {
            global $wpdb;

            $table_name = $wpdb->prefix . 'vmo_projects';
            $state_json = get_post_meta($post->ID, 'vmo_state', true);
            $chat = get_post_meta($post->ID, 'vmo_chat', true);
            $images = get_post_meta($post->ID, 'vmo_images', true);

            $data = array(
                'html' => $html,
                'state_json' => $state_json,
                'chat' => $chat,
                'images' => maybe_serialize($images),
                'updated_at' => current_time('mysql'),
            );

            $wpdb->update(
                $table_name,
                $data,
                array(
                    'user_id' => intval($post->post_author),
                    'project_title' => $post->post_title,
                )
            );
        }

        protected function apply_item_to_state($state, $item) {
            if (empty($state['blocks']) || !is_array($state['blocks'])) {
                return $state;
            }

            foreach ($state['blocks'] as $index => $block) {
                if (empty($block['html'])) {
                    continue;
                }

                $state['blocks'][$index]['html'] = $this->apply_media_to_markup($block['html'], $item);
            }

            return $state;
        }

        protected function apply_item_to_pages_map($pages_map, $item) {
            foreach ($pages_map as $page_key => $page_value) {
                if (is_string($page_value)) {
                    $pages_map[$page_key] = $this->apply_media_to_markup($page_value, $item);
                    continue;
                }

                if (!is_array($page_value)) {
                    continue;
                }

                if (!empty($page_value['html']) && is_string($page_value['html'])) {
                    $page_value['html'] = $this->apply_media_to_markup($page_value['html'], $item);
                }

                if (!empty($page_value['content']) && is_string($page_value['content'])) {
                    $page_value['content'] = $this->apply_media_to_markup($page_value['content'], $item);
                }

                $pages_map[$page_key] = $page_value;
            }

            return $pages_map;
        }

        protected function sync_consultation_website_data($post, $homepage_html, $builder_state = null, $pages_map = null, $qa_report = null) {
            $session_id = (string) get_post_meta($post->ID, 'vmo_session_id', true);
            if ($session_id === '') {
                return;
            }

            if (!class_exists('VMO_Consultation_Database')) {
                $db_file = plugin_dir_path(__FILE__) . 'class-vmo-consultation-database.php';
                if (file_exists($db_file)) {
                    require_once $db_file;
                }
            }

            if (!class_exists('VMO_Consultation_Database')) {
                return;
            }

            $db = new VMO_Consultation_Database();
            $existing = $db->get_consultation_by_session($session_id);
            if (!$existing) {
                return;
            }

            $website_data = json_decode((string) ($existing->website_data ?? '{}'), true);
            if (!is_array($website_data)) {
                $website_data = array();
            }

            $website_data['draft_post_id'] = intval($post->ID);
            $website_data['homepage_html'] = is_string($homepage_html) ? $homepage_html : (string) $post->post_content;

            $draft_assembled = true;
            $checklist = is_array($qa_report['checklist'] ?? null) ? $qa_report['checklist'] : array();
            $media_complete = array_key_exists('media_complete', $checklist)
                ? !empty($checklist['media_complete'])
                : !empty($website_data['media_complete']);
            $preview_ready = $draft_assembled && $media_complete && is_array($qa_report) && !empty($qa_report['preview_ready']);
            $publish_ready = $preview_ready && is_array($qa_report) && !empty($qa_report['publish_ready']);
            $final_preview_url = ($preview_ready && $media_complete) ? get_preview_post_link($post->ID) : '';
            $website_data['preview_url'] = $final_preview_url;

            update_post_meta($post->ID, 'vmo_media_complete', $media_complete ? 'yes' : 'no');
            update_post_meta($post->ID, 'vmo_preview_ready', $preview_ready ? 'yes' : 'no');
            update_post_meta($post->ID, 'vmo_generation_completed', $preview_ready ? 'yes' : 'no');
            update_post_meta($post->ID, 'vmo_preview_url', $final_preview_url);

            if (is_array($builder_state)) {
                $website_data['builder_state'] = $builder_state;
            }

            if (is_array($pages_map)) {
                $website_data['pages_map'] = $pages_map;
            }

            if (is_array($qa_report)) {
                $website_data['qa_report'] = $qa_report;
                $website_data['publish_ready'] = $publish_ready;
            }

            $website_data['generated_at'] = current_time('mysql');
            $website_data['generation_draft_assembled'] = $draft_assembled;
            $website_data['draft_assembled'] = $draft_assembled;
            $website_data['media_complete'] = $media_complete;
            $website_data['preview_ready'] = $preview_ready;
            $website_data['draft_ready'] = $preview_ready;
            $website_data['generation_completed'] = $preview_ready;
            if ($preview_ready) {
                $website_data['generation_completed_at'] = current_time('mysql');
            } else {
                unset($website_data['generation_completed_at']);
            }

            $website_payload = is_array($website_data['website'] ?? null) ? $website_data['website'] : array();
            $website_payload['post_id'] = intval($post->ID);
            $website_payload['preview_url'] = $final_preview_url;
            $website_payload['homepage_html'] = $website_data['homepage_html'];
            $website_payload['draft_assembled'] = $draft_assembled;
            $website_payload['media_complete'] = $media_complete;
            $website_payload['preview_ready'] = $preview_ready;
            $website_payload['draft_ready'] = $preview_ready;
            $website_payload['publish_ready'] = $publish_ready;
            if (is_array($builder_state)) {
                $website_payload['builder_state'] = $builder_state;
            }
            if (is_array($pages_map)) {
                $website_payload['pages_map'] = $pages_map;
                $website_payload['pages_created'] = array_values(array_keys($pages_map));
            }
            if (is_array($qa_report)) {
                $website_payload['qa_report'] = $qa_report;
            }
            $website_data['website'] = $website_payload;

            $operations_manifest = json_decode((string) get_post_meta($post->ID, 'vmo_website_operations_manifest', true), true);
            if (is_array($operations_manifest)) {
                $operations_manifest['updated_at'] = current_time('mysql');
                if (!isset($operations_manifest['integrations']) || !is_array($operations_manifest['integrations'])) {
                    $operations_manifest['integrations'] = array();
                }
                if (!isset($operations_manifest['integrations']['media_generation']) || !is_array($operations_manifest['integrations']['media_generation'])) {
                    $operations_manifest['integrations']['media_generation'] = array();
                }
                $operations_manifest['integrations']['media_generation']['complete'] = $media_complete;

                if (!isset($operations_manifest['dashboard']) || !is_array($operations_manifest['dashboard'])) {
                    $operations_manifest['dashboard'] = array();
                }
                if (!isset($operations_manifest['dashboard']['indicators']) || !is_array($operations_manifest['dashboard']['indicators'])) {
                    $operations_manifest['dashboard']['indicators'] = array();
                }
                $operations_manifest['dashboard']['indicators']['media_complete'] = $media_complete;
                $operations_manifest['dashboard']['indicators']['preview_ready'] = $preview_ready;
                $operations_manifest['dashboard']['indicators']['generation_completed'] = $preview_ready;
                $operations_manifest['dashboard']['indicators']['publish_ready'] = $publish_ready;
                if (isset($qa_report['final_qa_gate']['score'])) {
                    $operations_manifest['dashboard']['indicators']['qa_score'] = (int) $qa_report['final_qa_gate']['score'];
                } elseif (isset($qa_report['score'])) {
                    $operations_manifest['dashboard']['indicators']['qa_score'] = (int) $qa_report['score'];
                }

                $next_actions = is_array($operations_manifest['dashboard']['next_actions'] ?? null) ? $operations_manifest['dashboard']['next_actions'] : array();
                $filtered_actions = array();
                foreach ($next_actions as $action) {
                    $action_key = is_array($action) ? (string) ($action['key'] ?? '') : '';
                    if ($media_complete && $action_key === 'complete_media_generation') {
                        continue;
                    }
                    if ($publish_ready && $action_key === 'publish_review') {
                        continue;
                    }
                    $filtered_actions[] = $action;
                }
                if (!$media_complete && !in_array('complete_media_generation', array_column(array_filter($filtered_actions, 'is_array'), 'key'), true)) {
                    $filtered_actions[] = array('key' => 'complete_media_generation', 'label' => 'Finish required generated media', 'owner' => 'ai');
                }
                if (!$publish_ready && !in_array('publish_review', array_column(array_filter($filtered_actions, 'is_array'), 'key'), true)) {
                    $filtered_actions[] = array('key' => 'publish_review', 'label' => 'Review final QA before publishing', 'owner' => 'user_approval');
                }
                $operations_manifest['dashboard']['next_actions'] = array_values($filtered_actions);
                $website_data['operations_manifest'] = $operations_manifest;
                update_post_meta($post->ID, 'vmo_website_operations_manifest', wp_json_encode($operations_manifest));
                update_user_meta((int) $post->post_author, 'vmo_website_operations_manifest', $operations_manifest);
                update_user_meta((int) $post->post_author, 'vmo_website_operations_manifest_' . $session_id, $operations_manifest);
            }

            $operating_state = is_array($website_data['operating_state'] ?? null) ? $website_data['operating_state'] : array();
            $website_state = is_array($operating_state['website'] ?? null) ? $operating_state['website'] : array();
            $website_state['draft_post_id'] = intval($post->ID);
            $website_state['preview_url'] = $final_preview_url;
            $website_state['pages_count'] = is_array($pages_map) ? count($pages_map) : intval($website_state['pages_count'] ?? 0);
            $website_state['draft_assembled'] = $draft_assembled;
            $website_state['media_complete'] = $media_complete;
            $website_state['preview_ready'] = $preview_ready;
            $website_state['draft_ready'] = $preview_ready;
            $website_state['publish_ready'] = $publish_ready;
            if (is_array($checklist)) {
                $website_state['qa_checklist'] = $checklist;
            }
            $website_state['status'] = $publish_ready
                ? 'publish_ready'
                : ($preview_ready ? 'draft_ready' : 'media_generation_in_progress');
            $operating_state['website'] = $website_state;

            $setup_state = is_array($operating_state['setup'] ?? null) ? $operating_state['setup'] : array();
            $setup_state['next_milestone'] = $media_complete
                ? ($publish_ready ? 'Review and Publish Website' : 'Resolve Draft QA and Publish Website')
                : 'Complete required media generation';
            $operating_state['setup'] = $setup_state;
            $operating_state['generated_at'] = current_time('mysql');
            $website_data['operating_state'] = $operating_state;

            $db->save_consultation((int) $post->post_author, $session_id, array(
                'website_data' => wp_json_encode($website_data),
                'status' => $preview_ready ? 'completed' : 'generating_media',
            ));

            update_user_meta((int) $post->post_author, 'vmo_business_operating_state', $operating_state);
            update_user_meta((int) $post->post_author, 'vmo_business_operating_state_' . $session_id, $operating_state);

            $dashboard_class_file = plugin_dir_path(__FILE__) . 'class-vmo-kpi-dashboard.php';
            if (file_exists($dashboard_class_file)) {
                require_once $dashboard_class_file;
                if (class_exists('VMO_KPI_Dashboard')) {
                    $kpi_dashboard = new VMO_KPI_Dashboard();
                    $website_status = $publish_ready
                        ? 'publish_ready'
                        : ($preview_ready ? 'draft_ready' : 'media_generation_in_progress');
                    $next_milestone = $media_complete
                        ? ($publish_ready ? 'Review and Publish Website' : 'Resolve Draft QA and Publish Website')
                        : 'Complete required media generation';
                    $kpi_dashboard->set_kpi((int) $post->post_author, 'website_status', $website_status, array(
                        'category' => 'website',
                        'source' => 'project_media_queue_sync',
                    ));
                    $kpi_dashboard->set_kpi((int) $post->post_author, 'next_milestone', $next_milestone, array(
                        'category' => 'progress',
                        'source' => 'project_media_queue_sync',
                    ));
                }
            }
        }

        protected function refresh_publish_readiness($post) {
            if (!$post || intval($post->ID) <= 0 || $post->post_type !== 'vmo_project') {
                return;
            }

            $session_id = (string) get_post_meta($post->ID, 'vmo_session_id', true);
            $user_id = intval($post->post_author);
            if ($session_id === '' || $user_id <= 0) {
                return;
            }

            $pages_map = json_decode((string) get_post_meta($post->ID, 'vmo_pages_map', true), true);
            $business_data = json_decode((string) get_post_meta($post->ID, 'vmo_business_data', true), true);
            $business_package = json_decode((string) get_post_meta($post->ID, 'vmo_business_package', true), true);
            if (!is_array($pages_map)) {
                $pages_map = array();
            }
            if (!is_array($business_data)) {
                $business_data = array();
            }
            if (!is_array($business_package)) {
                $business_package = array();
            }
            $media_assets = $this->get_post_media_assets($post->ID);

            $website_payload = array(
                'content' => (string) $post->post_content,
                'homepage_html' => (string) $post->post_content,
                'pages_map' => $pages_map,
            );

            $qa_context = array(
                'related_post_id' => intval($post->ID),
                'draft_key' => (string) $post->ID,
                'visual_preset' => (string) get_post_meta($post->ID, 'vmo_visual_preset', true),
                'business_data' => $business_data,
                'media_assets' => $media_assets,
                'product_generation_packets' => $business_data['ecommerce']['product_generation_packets'] ?? ($business_package['product_generation_packets'] ?? array()),
            );
            $stored_final_context = json_decode((string) get_post_meta($post->ID, 'vmo_final_qa_gate', true), true);
            if (is_array($stored_final_context) && !empty($stored_final_context['selected_visual_preset'])) {
                $qa_context['visual_preset'] = (string) $stored_final_context['selected_visual_preset'];
            }

            $qa_report = array();
            if (function_exists('vmo_get_agent_orchestrator')) {
                $qa_report = vmo_get_agent_orchestrator()->run_website_qa($user_id, $session_id, $website_payload, $business_package, $qa_context);
            } elseif (function_exists('vmo_get_website_qa_service')) {
                $qa_report = vmo_get_website_qa_service()->run_qa($user_id, $session_id, $website_payload, $business_package, $qa_context);
            }

            if (function_exists('vmo_get_website_final_qa_gate')) {
                $final_qa_gate = vmo_get_website_final_qa_gate()->evaluate($user_id, $session_id, $website_payload, $business_package, $qa_context);
                $qa_report = vmo_get_website_final_qa_gate()->merge_with_existing_report($qa_report, $final_qa_gate);
            }

            if (!is_array($qa_report) || empty($qa_report)) {
                return;
            }

            $publish_ready = !empty($qa_report['publish_ready']);
            update_post_meta($post->ID, 'vmo_publish_ready', $publish_ready ? 'yes' : 'no');
            update_post_meta($post->ID, 'vmo_ready_to_publish', $publish_ready ? 'yes' : 'no');
            update_post_meta($post->ID, 'vmo_publish_checklist', wp_json_encode($qa_report['checklist'] ?? array()));
            update_post_meta($post->ID, 'vmo_website_qa_report', wp_json_encode($qa_report));
            update_post_meta($post->ID, 'vmo_final_qa_gate', wp_json_encode($qa_report['final_qa_gate'] ?? array()));

            $state = json_decode((string) get_post_meta($post->ID, 'vmo_state', true), true);
            if (!is_array($state)) {
                $state = null;
            }

            $this->sync_consultation_website_data($post, (string) $post->post_content, $state, $pages_map, $qa_report);
        }

        protected function apply_media_to_markup($html, $item) {
            $markup = is_string($html) ? $html : '';
            if ($markup === '') {
                return $markup;
            }

            libxml_use_internal_errors(true);

            $doc = new DOMDocument('1.0', 'UTF-8');
            $wrapped_html = '<!DOCTYPE html><html><body><div id="vmo-queue-root">' . $markup . '</div></body></html>';
            $load_result = $doc->loadHTML(mb_convert_encoding($wrapped_html, 'HTML-ENTITIES', 'UTF-8'));

            if (!$load_result) {
                libxml_clear_errors();
                return $markup;
            }

            $xpath = new DOMXPath($doc);
            $root_nodes = $xpath->query('//*[@id="vmo-queue-root"]');
            if (!$root_nodes || !$root_nodes->length) {
                libxml_clear_errors();
                return $markup;
            }

            $nodes = $this->find_target_nodes($xpath, $item);
            if (!$nodes || !$nodes->length) {
                libxml_clear_errors();
                return $markup;
            }

            foreach ($nodes as $node) {
                $this->apply_to_dom_node($doc, $node, $item);
            }

            $result = $this->inner_html($root_nodes->item(0));
            libxml_clear_errors();
            return $result ?: $markup;
        }

        protected function find_target_nodes($xpath, $item) {
            $media_id = sanitize_text_field($item['media_id'] ?? '');
            if ($media_id !== '') {
                return $xpath->query('//*[@data-media-id=' . $this->xpath_literal($media_id) . ']');
            }

            $type = sanitize_text_field($item['type'] ?? 'image');
            if ($type === 'video') {
                return $xpath->query('//*[@data-vmo-sora or @data-type="video" or self::video]');
            }

            return $xpath->query('//*[@data-vmo-generate or @data-type="image" or self::img]');
        }

        protected function apply_to_dom_node($doc, $node, $item) {
            $type = sanitize_text_field($item['type'] ?? 'image');
            $url = esc_url_raw($item['url'] ?? '');
            if ($url === '') {
                return;
            }

            $tag_name = strtolower($node->nodeName);
            $this->strip_generation_markers($node);

            if ($type === 'image') {
                if ($tag_name === 'img') {
                    $node->setAttribute('src', $url);
                    $node->setAttribute('loading', 'lazy');
                    if (!$node->hasAttribute('alt') || trim($node->getAttribute('alt')) === '') {
                        $node->setAttribute('alt', sanitize_text_field($item['alt_text'] ?? $item['label'] ?? 'Generated website media'));
                    }
                    return;
                }

                $node->setAttribute('data-vmo-generated', 'image');
                $style = trim($node->getAttribute('style'));
                $style .= ($style !== '' ? ';' : '') . 'background-image:url(' . $url . ');background-size:cover;background-position:center;background-repeat:no-repeat;';
                $node->setAttribute('style', $style);
                $this->clear_node_children($node);
                return;
            }

            if ($tag_name === 'video') {
                $node->setAttribute('src', $url);
                $node->setAttribute('autoplay', 'autoplay');
                $node->setAttribute('muted', 'muted');
                $node->setAttribute('loop', 'loop');
                $node->setAttribute('playsinline', 'playsinline');
                $this->clear_node_children($node);
                return;
            }

            $replacement = $doc->createElement('video');
            $replacement->setAttribute('src', $url);
            $replacement->setAttribute('autoplay', 'autoplay');
            $replacement->setAttribute('muted', 'muted');
            $replacement->setAttribute('loop', 'loop');
            $replacement->setAttribute('playsinline', 'playsinline');
            $replacement->setAttribute('data-type', 'video');

            if ($node->attributes) {
                foreach ($node->attributes as $attribute) {
                    if (!$attribute) continue;
                    if (strpos($attribute->nodeName, 'data-vmo-') === 0) continue;
                    if ($attribute->nodeName === 'data-type') continue;
                    $replacement->setAttribute($attribute->nodeName, $attribute->nodeValue);
                }
            }

            $style = trim($replacement->getAttribute('style'));
            $style .= ($style !== '' ? ';' : '') . 'width:100%;height:100%;object-fit:cover;display:block;';
            $replacement->setAttribute('style', $style);
            $node->parentNode->replaceChild($replacement, $node);
        }

        protected function strip_generation_markers($node) {
            $attributes = array('data-vmo-generate', 'data-vmo-prompt', 'data-vmo-sora', 'data-vmo-sora-prompt');
            foreach ($attributes as $attribute) {
                if ($node->hasAttribute($attribute)) {
                    $node->removeAttribute($attribute);
                }
            }
        }

        protected function clear_node_children($node) {
            while ($node->firstChild) {
                $node->removeChild($node->firstChild);
            }
        }

        protected function inner_html($node) {
            $html = '';
            foreach ($node->childNodes as $child) {
                $html .= $node->ownerDocument->saveHTML($child);
            }
            return $html;
        }

        protected function xpath_literal($value) {
            if (strpos($value, "'") === false) {
                return "'" . $value . "'";
            }

            if (strpos($value, '"') === false) {
                return '"' . $value . '"';
            }

            $parts = explode("'", $value);
            return "concat('" . implode("', \"'\", '", $parts) . "')";
        }

        protected function decode_targets($raw_targets) {
            if (is_array($raw_targets)) {
                return $raw_targets;
            }

            if (!is_string($raw_targets) || $raw_targets === '') {
                return array();
            }

            $decoded = json_decode(wp_unslash($raw_targets), true);
            return is_array($decoded) ? $decoded : array();
        }

        protected function extract_targets_from_html($html, &$html_with_ids, array $context = array()) {
            $html = is_string($html) ? $html : '';
            $html_with_ids = $html;
            if ($html === '' || !class_exists('DOMDocument')) {
                return array();
            }

            libxml_use_internal_errors(true);
            $doc = new DOMDocument('1.0', 'UTF-8');
            $wrapped_html = '<!DOCTYPE html><html><body><div id="vmo-queue-root">' . $html . '</div></body></html>';
            $load_result = $doc->loadHTML(mb_convert_encoding($wrapped_html, 'HTML-ENTITIES', 'UTF-8'));
            if (!$load_result) {
                libxml_clear_errors();
                return array();
            }

            $xpath = new DOMXPath($doc);
            $nodes = $xpath->query('//*[@data-vmo-generate or @data-vmo-sora or contains(concat(" ", normalize-space(@class), " "), " vmo-media-placeholder ")]');
            if (!$nodes || !$nodes->length) {
                libxml_clear_errors();
                return array();
            }

            $targets = array();
            $changed = false;
            foreach ($nodes as $index => $node) {
                $tag = strtolower($node->nodeName);
                $data_type = strtolower($node->getAttribute('data-type'));
                $type = ($node->hasAttribute('data-vmo-sora') || $data_type === 'video' || $tag === 'video') ? 'video' : 'image';
                $prompt = trim((string) ($node->getAttribute('data-vmo-prompt') ?: $node->getAttribute('data-vmo-sora-prompt')));
                if ($prompt === '') {
                    $prompt = $this->default_media_prompt($type, $context, 'Website section media');
                }

                $media_id = sanitize_text_field((string) $node->getAttribute('data-media-id'));
                if ($media_id === '') {
                    $media_id = 'media_' . substr(md5($type . '|' . $prompt . '|' . $index), 0, 12);
                    $node->setAttribute('data-media-id', $media_id);
                    $changed = true;
                }

                $targets[] = array(
                    'type' => $type,
                    'prompt' => $prompt,
                    'mediaId' => $media_id,
                    'label' => sanitize_text_field($node->getAttribute('aria-label') ?: ucfirst($type) . ' asset'),
                    'blockIndex' => $index,
                    'source' => 'canonical_media_placeholder',
                    'model' => $type === 'video' ? 'sora-2-pro' : '',
                    'manifestAssetId' => sanitize_key((string) $node->getAttribute('data-vmo-manifest-asset')),
                    'altText' => sanitize_text_field($node->getAttribute('alt') ?: $node->getAttribute('aria-label') ?: 'Generated website media'),
                );
            }

            if ($changed) {
                $root_nodes = $xpath->query('//*[@id="vmo-queue-root"]');
                if ($root_nodes && $root_nodes->length) {
                    $html_with_ids = $this->inner_html($root_nodes->item(0));
                }
            }

            libxml_clear_errors();
            return $targets;
        }

        protected function targets_from_manifest_assets($manifest, array $existing_targets, array $context = array()) {
            if (!is_array($manifest) || empty($manifest['required_assets']) || !is_array($manifest['required_assets'])) {
                return array();
            }

            $existing_manifest_ids = array();
            foreach ($existing_targets as $target) {
                $asset_id = sanitize_key((string) ($target['manifestAssetId'] ?? $target['manifest_asset_id'] ?? ''));
                if ($asset_id !== '') {
                    $existing_manifest_ids[$asset_id] = true;
                }
            }

            $targets = array();
            foreach ($manifest['required_assets'] as $index => $asset) {
                if (!is_array($asset) || empty($asset['required_before_preview'])) {
                    continue;
                }

                $asset_id = sanitize_key((string) ($asset['id'] ?? 'required_asset_' . $index));
                if ($asset_id === '' || isset($existing_manifest_ids[$asset_id])) {
                    continue;
                }

                $targets[] = array(
                    'type' => $this->queue_type_from_manifest_asset($asset),
                    'prompt' => $this->prompt_from_manifest_asset($asset, $context),
                    'mediaId' => 'manifest_' . $asset_id,
                    'label' => sanitize_text_field((string) ($asset['purpose'] ?? ucwords(str_replace('_', ' ', $asset_id)))),
                    'blockIndex' => 9000 + $index,
                    'source' => 'canonical_media_manifest',
                    'manifestAssetId' => $asset_id,
                    'placement' => sanitize_key((string) ($asset['placement'] ?? 'website')),
                    'group' => sanitize_key((string) ($asset['group'] ?? 'website')),
                    'model' => strpos((string) ($asset['type'] ?? ''), 'video') !== false ? 'sora-2-pro' : '',
                    'altText' => sanitize_text_field((string) ($asset['alt_text'] ?? $asset['purpose'] ?? 'Generated website media')),
                    'desktopVariantRequired' => !empty($asset['desktop_variant_required']),
                    'mobileVariantRequired' => !empty($asset['mobile_variant_required']),
                    'posterRequired' => !empty($asset['poster_required']),
                );
            }

            return $targets;
        }

        protected function default_media_prompt($type, array $context, $fallback) {
            $canonical = is_array($context['canonical_generation_context'] ?? null) ? $context['canonical_generation_context'] : array();
            $business_name = sanitize_text_field((string) ($context['business_name'] ?? $canonical['business_name'] ?? 'this business'));
            $industry = sanitize_text_field((string) ($context['industry'] ?? $canonical['industry'] ?? ''));
            $medium = $type === 'video' ? 'short cinematic website video loop' : 'premium website visual';
            return trim($medium . ' for ' . $business_name . ($industry ? ' in ' . $industry : '') . ', ' . $fallback . ', polished, inspectable, conversion-ready');
        }

        protected function queue_type_from_manifest_asset(array $asset) {
            $type = strtolower((string) ($asset['type'] ?? 'image'));
            return strpos($type, 'video') !== false ? 'video' : 'image';
        }

        protected function prompt_from_manifest_asset(array $asset, array $context) {
            if (!empty($asset['prompt'])) {
                return sanitize_textarea_field((string) $asset['prompt']);
            }
            $purpose = sanitize_textarea_field((string) ($asset['purpose'] ?? 'Required premium website visual'));
            $placement = sanitize_text_field((string) ($asset['placement'] ?? 'website'));
            $canonical = is_array($context['canonical_generation_context'] ?? null) ? $context['canonical_generation_context'] : array();
            $preset = sanitize_text_field((string) ($canonical['selected_visual_preset'] ?? $context['visual_preset'] ?? 'premium WordPress website'));
            return trim($purpose . ' Placement: ' . $placement . '. Visual preset: ' . $preset . '. Make it specific, premium, brand-ready, mobile-safe, and not generic stock.');
        }

        protected function mark_operations_manifest_media_pending($post_id, array $queue) {
            $manifest = json_decode((string) get_post_meta($post_id, 'vmo_website_operations_manifest', true), true);
            if (!is_array($manifest)) {
                return;
            }

            $manifest['updated_at'] = current_time('mysql');
            if (!isset($manifest['integrations']) || !is_array($manifest['integrations'])) {
                $manifest['integrations'] = array();
            }
            if (!isset($manifest['integrations']['media_generation']) || !is_array($manifest['integrations']['media_generation'])) {
                $manifest['integrations']['media_generation'] = array();
            }
            $manifest['integrations']['media_generation']['complete'] = false;
            $manifest['integrations']['media_generation']['queue_counts'] = is_array($queue['counts'] ?? null) ? $queue['counts'] : array();

            if (!isset($manifest['dashboard']) || !is_array($manifest['dashboard'])) {
                $manifest['dashboard'] = array();
            }
            if (!isset($manifest['dashboard']['indicators']) || !is_array($manifest['dashboard']['indicators'])) {
                $manifest['dashboard']['indicators'] = array();
            }
            $manifest['dashboard']['indicators']['media_complete'] = false;
            $manifest['dashboard']['indicators']['preview_ready'] = false;
            $manifest['dashboard']['indicators']['generation_completed'] = false;

            $next_actions = is_array($manifest['dashboard']['next_actions'] ?? null) ? $manifest['dashboard']['next_actions'] : array();
            $has_media_action = false;
            foreach ($next_actions as $action) {
                if (is_array($action) && ($action['key'] ?? '') === 'complete_media_generation') {
                    $has_media_action = true;
                    break;
                }
            }
            if (!$has_media_action) {
                $next_actions[] = array('key' => 'complete_media_generation', 'label' => 'Finish required generated media', 'owner' => 'ai');
            }
            $manifest['dashboard']['next_actions'] = $next_actions;

            update_post_meta($post_id, 'vmo_website_operations_manifest', wp_json_encode($manifest));
        }

        protected function merge_targets_into_queue($post, $targets) {
            $queue = $this->get_queue($post->ID);
            if (!$queue) {
                $queue = $this->empty_queue($post->ID, intval($post->post_author));
            }

            $normalized_targets = $this->normalize_targets($targets);
            $existing_by_key = array();
            foreach ($queue['items'] as $existing_item) {
                if (!empty($existing_item['key'])) {
                    $existing_by_key[$existing_item['key']] = $existing_item;
                }
            }

            $active_keys = array();
            $merged = array();

            foreach ($normalized_targets as $target) {
                $key = $target['key'];
                $active_keys[$key] = true;
                if (isset($existing_by_key[$key])) {
                    $merged[] = array_merge($existing_by_key[$key], $target, array('active' => true));
                    continue;
                }

                $merged[] = $target;
            }

            foreach ($queue['items'] as $existing_item) {
                $key = $existing_item['key'] ?? '';
                if ($key === '' || isset($active_keys[$key])) {
                    continue;
                }

                if (($existing_item['status'] ?? '') === 'processing') {
                    $existing_item['active'] = false;
                    $merged[] = $existing_item;
                    continue;
                }

                $existing_item['status'] = 'superseded';
                $existing_item['active'] = false;
                $merged[] = $existing_item;
            }

            $queue['items'] = $merged;
            $queue['post_id'] = intval($post->ID);
            $queue['user_id'] = intval($post->post_author);
            $queue['post_type'] = sanitize_key($post->post_type);
            $queue['token'] = $this->get_or_create_queue_token($post->ID);
            $queue['last_dispatch_at'] = current_time('mysql');

            $this->refresh_queue($queue);
            return $queue;
        }

        protected function normalize_targets($targets) {
            $normalized = array();

            foreach ((array) $targets as $index => $target) {
                if (!is_array($target)) {
                    continue;
                }

                $type = sanitize_text_field($target['type'] ?? 'image');
                if ($type !== 'video') {
                    $type = 'image';
                }

                $prompt = sanitize_textarea_field($target['prompt'] ?? '');
                if ($prompt === '') {
                    continue;
                }

                $media_id = sanitize_text_field($target['mediaId'] ?? ($target['media_id'] ?? ''));
                $key = $media_id !== ''
                    ? 'media:' . $media_id
                    : 'hash:' . md5($type . '|' . $prompt . '|' . intval($target['blockIndex'] ?? -1) . '|' . $index);

                $normalized[] = array(
                    'key' => $key,
                    'media_id' => $media_id,
                    'type' => $type,
                    'prompt' => $prompt,
                    'label' => sanitize_text_field($target['label'] ?? ucfirst($type)),
                    'block_index' => isset($target['blockIndex']) ? intval($target['blockIndex']) : -1,
                    'source' => sanitize_text_field($target['source'] ?? 'builder'),
                    'manifest_asset_id' => sanitize_key((string) ($target['manifestAssetId'] ?? $target['manifest_asset_id'] ?? '')),
                    'placement' => sanitize_key((string) ($target['placement'] ?? '')),
                    'group' => sanitize_key((string) ($target['group'] ?? '')),
                    'alt_text' => sanitize_text_field((string) ($target['altText'] ?? $target['alt_text'] ?? '')),
                    'desktop_variant_required' => !empty($target['desktopVariantRequired']) || !empty($target['desktop_variant_required']),
                    'mobile_variant_required' => !empty($target['mobileVariantRequired']) || !empty($target['mobile_variant_required']),
                    'poster_required' => !empty($target['posterRequired']) || !empty($target['poster_required']),
                    'model' => sanitize_text_field($target['model'] ?? ($type === 'video' && $index === 0 ? 'sora-2-pro' : 'sora-2')),
                    'seconds' => sanitize_text_field($target['seconds'] ?? '4'),
                    'size' => sanitize_text_field($target['size'] ?? '1280x720'),
                    'queue_position' => isset($target['queuePosition']) ? intval($target['queuePosition']) : $index,
                    'status' => 'pending',
                    'active' => true,
                    'attempts' => 0,
                    'progress' => 0,
                    'remote_job_id' => '',
                    'url' => '',
                    'attachment_id' => 0,
                    'error' => '',
                    'tokens_charged' => false,
                    'tokens_refunded' => false,
                    'created_at' => current_time('mysql'),
                    'updated_at' => current_time('mysql'),
                );
            }

            return $normalized;
        }

        protected function find_next_item_index($queue) {
            $items = isset($queue['items']) && is_array($queue['items']) ? $queue['items'] : array();
            if (!$items) {
                return -1;
            }

            $processing_video_index = -1;
            $pending_index = -1;

            foreach ($items as $index => $item) {
                if (empty($item['active']) && ($item['status'] ?? '') !== 'processing') {
                    continue;
                }

                if (($item['status'] ?? '') === 'processing' && ($item['type'] ?? '') === 'video') {
                    $processing_video_index = $index;
                    break;
                }

                if (($item['status'] ?? '') === 'pending' && $pending_index === -1) {
                    $pending_index = $index;
                }
            }

            if ($processing_video_index !== -1) {
                return $processing_video_index;
            }

            return $pending_index;
        }

        protected function refresh_queue(&$queue) {
            $items = isset($queue['items']) && is_array($queue['items']) ? $queue['items'] : array();
            $active_items = array_filter($items, function($item) {
                return !empty($item['active']);
            });

            $counts = array(
                'total' => count($active_items),
                'pending' => 0,
                'processing' => 0,
                'complete' => 0,
                'failed' => 0,
            );

            foreach ($active_items as $item) {
                $status = $item['status'] ?? 'pending';
                if (isset($counts[$status])) {
                    $counts[$status] += 1;
                }
            }

            if ($counts['pending'] > 0 || $counts['processing'] > 0) {
                $queue['status'] = 'processing';
            } elseif ($counts['failed'] > 0 && $counts['complete'] > 0) {
                $queue['status'] = 'partial';
            } elseif ($counts['failed'] > 0) {
                $queue['status'] = 'failed';
            } else {
                $queue['status'] = 'complete';
            }

            $queue['counts'] = $counts;
            $queue['updated_at'] = current_time('mysql');
        }

        protected function queue_has_pending_work($queue) {
            $counts = $queue['counts'] ?? array();
            return !empty($counts['pending']) || !empty($counts['processing']);
        }

        protected function get_queue($post_id) {
            $queue = get_post_meta($post_id, self::META_QUEUE, true);
            return is_array($queue) ? $queue : null;
        }

        protected function save_queue($post_id, $queue) {
            update_post_meta($post_id, self::META_QUEUE, $queue);
            $post = get_post($post_id);
            if ($post && function_exists('vmo_get_website_media_manifest_service')) {
                $business_package = json_decode((string) get_post_meta($post_id, 'vmo_business_package', true), true);
                $business_data = json_decode((string) get_post_meta($post_id, 'vmo_business_data', true), true);
                if (!is_array($business_package)) {
                    $business_package = array();
                }
                if (!is_array($business_data)) {
                    $business_data = array();
                }
                $context = array(
                    'visual_preset' => (string) get_post_meta($post_id, 'vmo_visual_preset', true),
                    'queue' => $queue,
                    'business_package' => $business_package,
                    'business_data' => $business_data,
                    'media_assets' => $this->get_post_media_assets($post_id),
                    'product_generation_packets' => $business_data['ecommerce']['product_generation_packets'] ?? ($business_package['product_generation_packets'] ?? array()),
                );
                $manifest = vmo_get_website_media_manifest_service()->build_from_html((string) $post->post_content, $context);
                if ($this->queue_has_pending_work($queue)) {
                    $manifest['media_complete'] = false;
                    $manifest['readiness']['preview_blocked'] = true;
                    $manifest['readiness']['reason'] = 'Media queue still has pending or processing assets.';
                }
                $manifest['queue_counts'] = is_array($queue['counts'] ?? null) ? $queue['counts'] : array();
                update_post_meta($post_id, 'vmo_media_manifest', wp_json_encode($manifest));
            }
        }

        protected function empty_queue($post_id, $user_id) {
            return array(
                'post_id' => intval($post_id),
                'user_id' => intval($user_id),
                'status' => 'complete',
                'counts' => array(
                    'total' => 0,
                    'pending' => 0,
                    'processing' => 0,
                    'complete' => 0,
                    'failed' => 0,
                ),
                'items' => array(),
                'updated_at' => current_time('mysql'),
            );
        }

        protected function prepare_queue_response($queue) {
            $queue = is_array($queue) ? $queue : $this->empty_queue(0, 0);
            $items = array();
            $post_id = intval($queue['post_id'] ?? 0);
            $qa_report = $post_id ? json_decode((string) get_post_meta($post_id, 'vmo_website_qa_report', true), true) : array();
            $media_manifest = $post_id ? json_decode((string) get_post_meta($post_id, 'vmo_media_manifest', true), true) : array();
            if (!is_array($qa_report)) {
                $qa_report = array();
            }
            if (!is_array($media_manifest)) {
                $media_manifest = array();
            }
            $checklist = is_array($qa_report['checklist'] ?? null) ? $qa_report['checklist'] : array();
            $saved_media_complete = $post_id ? get_post_meta($post_id, 'vmo_media_complete', true) === 'yes' : false;
            $media_complete = array_key_exists('media_complete', $checklist) ? !empty($checklist['media_complete']) : $saved_media_complete;
            if (array_key_exists('media_complete', $media_manifest)) {
                $media_complete = $media_complete && !empty($media_manifest['media_complete']);
            }
            $preview_ready = ($post_id > 0) && $media_complete && get_post_meta($post_id, 'vmo_preview_ready', true) === 'yes';
            $preview_url = $preview_ready ? (string) get_post_meta($post_id, 'vmo_preview_url', true) : '';
            if ($preview_ready && $preview_url === '') {
                $preview_url = get_preview_post_link($post_id);
            }
            $homepage_html = $preview_ready && $post_id > 0 ? (string) get_post_field('post_content', $post_id) : '';

            foreach (($queue['items'] ?? array()) as $item) {
                $items[] = array(
                    'key' => $item['key'] ?? '',
                    'media_id' => $item['media_id'] ?? '',
                    'type' => $item['type'] ?? 'image',
                    'label' => $item['label'] ?? '',
                    'status' => $item['status'] ?? 'pending',
                    'url' => $item['url'] ?? '',
                    'attachment_id' => intval($item['attachment_id'] ?? 0),
                    'error' => $item['error'] ?? '',
                    'progress' => intval($item['progress'] ?? 0),
                    'active' => !empty($item['active']),
                );
            }

            return array(
                'post_id' => $post_id,
                'status' => $queue['status'] ?? 'complete',
                'counts' => $queue['counts'] ?? array(),
                'updated_at' => $queue['updated_at'] ?? '',
                'items' => $items,
                'media_complete' => $media_complete,
                'preview_ready' => $preview_ready,
                'preview_url' => $preview_ready ? $preview_url : '',
                'homepage_html' => $preview_ready ? $homepage_html : '',
                'content' => $preview_ready ? $homepage_html : '',
                'publish_ready' => $post_id ? get_post_meta($post_id, 'vmo_publish_ready', true) === 'yes' : false,
                'media_manifest' => $media_manifest,
                'qa_report' => $qa_report,
            );
        }

        protected function get_post_media_assets($post_id) {
            $media_assets = json_decode((string) get_post_meta(intval($post_id), 'vmo_media_assets', true), true);
            return is_array($media_assets) ? $media_assets : array();
        }

        protected function resolve_post_id($payload) {
            return intval($payload['project_id'] ?? ($payload['post_id'] ?? 0));
        }

        protected function get_editable_post($post_id, $user_id) {
            $post_id = intval($post_id);
            if (!$post_id || !$user_id) {
                return null;
            }

            $post = get_post($post_id);
            if (!$post) {
                return null;
            }

            if (intval($post->post_author) !== intval($user_id) && !current_user_can('edit_post', $post_id)) {
                return null;
            }

            return $post;
        }

        protected function authorize_process_request($post_id, $token) {
            $post_id = intval($post_id);
            if ($post_id && is_user_logged_in() && current_user_can('edit_post', $post_id)) {
                return true;
            }

            $stored = get_post_meta($post_id, self::META_TOKEN, true);
            $token = sanitize_text_field(wp_unslash($token));

            return $stored && $token && hash_equals($stored, $token);
        }

        protected function get_or_create_queue_token($post_id) {
            $token = get_post_meta($post_id, self::META_TOKEN, true);
            if ($token) {
                return $token;
            }

            $token = wp_generate_password(40, false, false);
            update_post_meta($post_id, self::META_TOKEN, $token);
            return $token;
        }

        protected function dispatch_async($post_id) {
            $token = $this->get_or_create_queue_token($post_id);
            wp_remote_post(admin_url('admin-ajax.php'), array(
                'timeout' => 1,
                'blocking' => false,
                'body' => array(
                    'action' => 'vmo_process_project_media_queue',
                    'post_id' => intval($post_id),
                    'token' => $token,
                ),
            ));
        }

        protected function schedule_fallback($post_id) {
            if (!wp_next_scheduled(self::PROCESS_HOOK, array(intval($post_id)))) {
                wp_schedule_single_event(time() + 60, self::PROCESS_HOOK, array(intval($post_id)));
            }
        }

        protected function acquire_lock($post_id) {
            $key = self::LOCK_PREFIX . intval($post_id);
            if (get_transient($key)) {
                return false;
            }

            set_transient($key, 1, 5 * MINUTE_IN_SECONDS);
            return true;
        }

        protected function release_lock($post_id) {
            delete_transient(self::LOCK_PREFIX . intval($post_id));
        }

        protected function get_video_generator() {
            if (!empty($GLOBALS['vmo_sora_video_generator']) && $GLOBALS['vmo_sora_video_generator'] instanceof VMO_Sora_Video_Generator) {
                return $GLOBALS['vmo_sora_video_generator'];
            }

            return null;
        }
    }
}

if (!function_exists('vmo_get_project_media_queue_service')) {
    function vmo_get_project_media_queue_service() {
        static $service = null;
        if (!$service && class_exists('VMO_Project_Media_Queue')) {
            $service = new VMO_Project_Media_Queue();
        }
        return $service;
    }
}

if (class_exists('VMO_Project_Media_Queue')) {
    vmo_get_project_media_queue_service();
}