<?php
/**
 * VMO Website Agent Runtime.
 *
 * Activates the registered website-generation design agents (see
 * includes/agents/class-vmo-website-generation-agents.php) as REAL, live LLM
 * calls executed through VMO_Agent_Orchestrator, and consolidates their output
 * into a single "design directive" that is injected into the per-page
 * generation prompts of the canonical pipeline.
 *
 * Founder-confirmed June 2026: EVERY plan gets the full premium build — quality
 * is never tier-gated. The provider cost of this layer is absorbed as a
 * one-time initial-setup bonus (see vmo-initial-setup-policy.php), so this
 * runtime is built to run ONCE per generation and is aggressively cached:
 *  - per-request memoization (the per-page loop reuses one directive), and
 *  - a transient keyed by user+session+brief signature, so reruns/refinements
 *    within the window reuse the directive instead of re-paying for agents.
 *
 * Fail-safe by design: if there is no API key, the orchestrator is missing, the
 * registry is empty, or any agent errors, build_design_directive() returns a
 * directive with an empty 'brief' and the pipeline falls back to its existing
 * template + single-call-per-page path. It can never break a website build.
 *
 * @package VMO_AI_Website_Builder
 * @since   1.0.61
 */

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

if (!class_exists('VMO_Website_Agent_Runtime')) {

class VMO_Website_Agent_Runtime {

    private static $instance = null;

    /** Per-request memoization keyed by cache key. */
    private $memo = array();

    /** Cache window: long enough to cover a generation + immediate reruns. */
    const CACHE_TTL = 21600; // 6 hours in seconds

    /** Output token budget per agent — keeps the layer's provider cost bounded. */
    const AGENT_MAX_TOKENS = 700;

    /** Agents that are post-generation only and are skipped during the build. */
    private $skip_during_build = array('website_refinement_assistant');

    public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Is the premium agent design layer enabled? Default true. Filterable so it
     * can be disabled globally or per business without code changes.
     */
    public function is_enabled(array $business_data = array()) {
        return (bool) apply_filters('vmo_enable_agent_design_layer', true, $business_data);
    }

    /**
     * Build (or reuse a cached) consolidated design directive for a generation.
     *
     * @param array $business_data The pipeline business_data (carries user_id,
     *                             session_id, canonical_generation_context, etc.)
     * @return array{brief:string,agents:array,executed:bool,model_count:int}
     */
    public function build_design_directive(array $business_data) {
        $empty = array('brief' => '', 'agents' => array(), 'executed' => false, 'model_count' => 0);

        if (!$this->is_enabled($business_data)) {
            return $empty;
        }
        if (!function_exists('vmo_get_website_generation_agents') || !class_exists('VMO_Agent_Orchestrator')) {
            return $empty;
        }

        $user_id = (int) ($business_data['user_id'] ?? 0);
        $session_id = (string) ($business_data['session_id'] ?? '');
        $cache_key = $this->cache_key($user_id, $session_id, $business_data);

        if (isset($this->memo[$cache_key])) {
            return $this->memo[$cache_key];
        }
        $cached = get_transient($cache_key);
        if (is_array($cached) && isset($cached['brief'])) {
            $this->memo[$cache_key] = $cached;
            return $cached;
        }

        $directive = $this->run_agents($user_id, $session_id, $business_data);

        // Cache even a partial/empty directive briefly to avoid re-attempting on
        // every page in the per-page loop.
        set_transient($cache_key, $directive, self::CACHE_TTL);
        $this->memo[$cache_key] = $directive;
        return $directive;
    }

    /**
     * Convenience: return only the rendered brief string for prompt injection.
     */
    public function get_directive_brief(array $business_data) {
        $directive = $this->build_design_directive($business_data);
        return is_array($directive) ? (string) ($directive['brief'] ?? '') : '';
    }

    /**
     * Execute the registered agents in orchestration order, threading each
     * agent's output forward as handoff context, and consolidate the result.
     */
    private function run_agents($user_id, $session_id, array $business_data) {
        $empty = array('brief' => '', 'agents' => array(), 'executed' => false, 'model_count' => 0);

        $agents = vmo_get_website_generation_agents();
        if (!is_array($agents) || empty($agents)) {
            return $empty;
        }

        $orchestrator = VMO_Agent_Orchestrator::get_instance();
        if (!is_object($orchestrator) || !method_exists($orchestrator, 'run_design_agent')) {
            return $empty;
        }

        $agent_context = $this->build_agent_context($business_data);

        $outputs = array();
        $handoff_state = array();
        $models = array();
        $executed_any = false;

        // Seed locked, user-approved design constraints so EVERY agent honors exactly what the
        // user approved (website format + visual direction) and benchmarks against competitor sites.
        $concept = is_array($agent_context['website_design_concept'] ?? null) ? $agent_context['website_design_concept'] : array();
        $locked_constraints = array();
        if (!empty($concept['site_format'])) {
            $format_label = $concept['site_format'] === 'single_page'
                ? 'SINGLE-PAGE website (one long scrolling page with anchor navigation)'
                : ($concept['site_format'] === 'hybrid'
                    ? 'HYBRID (one-page core plus a few key sub-pages)'
                    : 'MULTI-PAGE website');
            $locked_constraints[] = 'Website format (user-approved, do not change): ' . $format_label;
        }
        if (!empty($concept['design_direction'])) {
            $locked_constraints[] = 'Approved visual direction: ' . $concept['design_direction'];
        }
        if (!empty($concept['refinements']) && is_array($concept['refinements'])) {
            $locked_constraints[] = 'User refinements to honor: ' . implode(' | ', array_map('strval', $concept['refinements']));
        }
        if (!empty($agent_context['competitor_design_research'])) {
            $locked_constraints[] = 'Competitor website benchmarking (beat, do not copy): ' . (string) $agent_context['competitor_design_research'];
        }
        if (!empty($locked_constraints)) {
            $handoff_state['user_approved_design_constraints'] = $this->trim_for_handoff(implode('. ', $locked_constraints));
        }

        foreach ($agents as $key => $agent) {
            if (in_array($key, $this->skip_during_build, true)) {
                continue;
            }
            if (!is_object($agent) || !method_exists($agent, 'run')) {
                continue;
            }

            // Obtain the agent's own contract prompt, then augment it with the
            // accumulated handoff state from prior agents.
            $base_prompt = '';
            try {
                $run = $agent->run($agent_context);
                $base_prompt = is_array($run) ? (string) ($run['prompt'] ?? '') : '';
            } catch (\Throwable $e) {
                $base_prompt = '';
            }
            if (trim($base_prompt) === '') {
                continue;
            }

            $prompt = $this->compose_prompt($base_prompt, $handoff_state);

            $result = $orchestrator->run_design_agent(
                $user_id,
                $session_id,
                (string) $key,
                $prompt,
                array(
                    'route_task' => 'website_design_agent',
                    'requires_strategic_reasoning' => true,
                    'max_tokens' => self::AGENT_MAX_TOKENS,
                    'temperature' => 0.35,
                )
            );

            $content = is_array($result) ? trim((string) ($result['content'] ?? '')) : '';
            if ($content === '') {
                // No API key / error: stop early — the whole layer falls back.
                if (in_array(($result['model_used'] ?? ''), array('no_api_key', 'skipped'), true)) {
                    break;
                }
                continue;
            }

            $executed_any = true;
            $models[] = (string) ($result['model_used'] ?? '');
            $outputs[$key] = $content;

            // Thread a trimmed summary forward as handoff state (bounded).
            $handoff_state[$key] = $this->trim_for_handoff($content);
        }

        if (!$executed_any || empty($outputs)) {
            return $empty;
        }

        return array(
            'brief' => $this->render_brief($outputs),
            'agents' => $outputs,
            'executed' => true,
            'model_count' => count(array_filter($models)),
        );
    }

    /**
     * Compact business context handed to each agent's prompt builder.
     */
    private function build_agent_context(array $business_data) {
        $canonical = is_array($business_data['canonical_generation_context'] ?? null)
            ? $business_data['canonical_generation_context']
            : array();

        $services = $business_data['services'] ?? array();
        if (is_array($services)) {
            $services = implode(', ', array_map('strval', $services));
        }
        $goals = $business_data['goals'] ?? array();
        if (is_array($goals)) {
            $goals = implode(', ', array_map('strval', $goals));
        }

        return array(
            'business_name'    => (string) ($business_data['business_name'] ?? ''),
            'business_type'    => (string) ($business_data['business_type'] ?? ''),
            'industry'         => (string) ($business_data['industry'] ?? ''),
            'location'         => (string) ($business_data['location'] ?? ''),
            'target_audience'  => (string) ($business_data['target_audience'] ?? ''),
            'services'         => (string) $services,
            'goals'            => (string) $goals,
            'ecommerce'        => is_array($business_data['ecommerce'] ?? null) ? $business_data['ecommerce'] : array(),
            'brand'            => is_array($business_data['brand'] ?? null) ? $business_data['brand'] : array(),
            'canonical_generation_context' => $canonical,
            'website_design_concept' => is_array($business_data['website_design_concept'] ?? null) ? $business_data['website_design_concept'] : array(),
            'competitor_design_research' => (string) ($business_data['competitor_design_research'] ?? ''),
        );
    }

    /**
     * Append accumulated handoff state to an agent's base prompt and pin the
     * required output shape (strict, compact JSON-or-text decisions).
     */
    private function compose_prompt($base_prompt, array $handoff_state) {
        $prompt = $base_prompt;

        if (!empty($handoff_state)) {
            $prompt .= "\n\nUPSTREAM AGENT DECISIONS (build on these, do not contradict):\n";
            foreach ($handoff_state as $from => $summary) {
                $prompt .= '- ' . str_replace('_', ' ', (string) $from) . ': ' . $summary . "\n";
            }
        }

        $prompt .= "\nRespond with concise, concrete, directly-usable design and content"
            . " decisions for this specific business. No preamble, no markdown fences."
            . " Keep it under " . self::AGENT_MAX_TOKENS . " tokens.";

        return $prompt;
    }

    private function trim_for_handoff($content) {
        $content = preg_replace('/\s+/', ' ', (string) $content);
        if (function_exists('mb_substr')) {
            return trim(mb_substr($content, 0, 600));
        }
        return trim(substr($content, 0, 600));
    }

    /**
     * Consolidate per-agent outputs into a single labelled brief block for
     * injection into page-generation prompts. Bounded in total length so it
     * never bloats the per-page prompt.
     */
    private function render_brief(array $outputs) {
        if (empty($outputs)) {
            return '';
        }

        $lines = array();
        $lines[] = 'PREMIUM DESIGN DIRECTIVE (from the VMO website design agents — follow precisely):';
        foreach ($outputs as $key => $content) {
            $label = ucwords(str_replace('_', ' ', (string) $key));
            $clean = preg_replace('/\s+/', ' ', (string) $content);
            $clean = trim($clean);
            if ($clean === '') {
                continue;
            }
            if (function_exists('mb_substr')) {
                $clean = mb_substr($clean, 0, 500);
            } else {
                $clean = substr($clean, 0, 500);
            }
            $lines[] = '• ' . $label . ': ' . $clean;
        }

        $brief = implode("\n", $lines);

        // Hard cap the whole directive so page prompts stay bounded.
        $max = 6000;
        if (function_exists('mb_strlen') && mb_strlen($brief) > $max) {
            $brief = mb_substr($brief, 0, $max);
        } elseif (strlen($brief) > $max) {
            $brief = substr($brief, 0, $max);
        }

        return $brief . "\n";
    }

    /**
     * Stable cache key from user, session, and a brief signature so meaningful
     * changes to the business inputs produce a fresh directive.
     */
    private function cache_key($user_id, $session_id, array $business_data) {
        $sig_source = array(
            (string) ($business_data['business_name'] ?? ''),
            (string) ($business_data['business_type'] ?? ''),
            (string) ($business_data['industry'] ?? ''),
            is_array($business_data['goals'] ?? null) ? implode(',', $business_data['goals']) : (string) ($business_data['goals'] ?? ''),
        );
        $sig = md5(implode('|', $sig_source));
        return 'vmo_design_directive_' . (int) $user_id . '_' . md5((string) $session_id) . '_' . $sig;
    }
}

}

if (!function_exists('vmo_get_website_agent_runtime')) {
    function vmo_get_website_agent_runtime() {
        return VMO_Website_Agent_Runtime::get_instance();
    }
}
