<?php
/**
 * MEMANTO Context Enhancer
 *
 * Orchestrates when and what to remember/recall from MEMANTO.
 * Hooks into the existing Context Service to provide memory
 * enrichment without replacing any existing behavior.
 *
 * @package WP_Agentic_Writer
 * @since   0.3.0
 */

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

/**
 * Class WP_Agentic_Writer_Memanto_Context_Enhancer
 *
 * Every public method checks is_active() first and returns gracefully
 * if MEMANTO is not available. The plugin never breaks.
 */
class WP_Agentic_Writer_Memanto_Context_Enhancer
{
    /**
     * Singleton instance.
     *
     * @var WP_Agentic_Writer_Memanto_Context_Enhancer
     */
    private static $instance = null;

    /**
     * MEMANTO client reference.
     *
     * @var WP_Agentic_Writer_Memanto_Client
     */
    private $client;

    /**
     * Get singleton instance.
     *
     * @return WP_Agentic_Writer_Memanto_Context_Enhancer
     */
    public static function get_instance()
    {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor.
     *
     * Registers all WordPress action/filter hooks so that the sidebar
     * handlers only need a single do_action() call at each trigger point.
     */
    private function __construct()
    {
        $this->client = WP_Agentic_Writer_Memanto_Client::get_instance();

        // Session lifecycle.
        add_action(
            "wpaw_memanto_session_start",
            [$this, "on_session_start"],
            10,
            3,
        );
        add_action(
            "wpaw_memanto_session_end",
            [$this, "on_session_end"],
            10,
            2,
        );

        // Write-through: remember on meaningful events.
        add_action(
            "wpaw_memanto_user_message",
            [$this, "on_user_message"],
            10,
            3,
        );
        add_action(
            "wpaw_memanto_plan_generated",
            [$this, "on_plan_generated"],
            10,
            2,
        );
        add_action(
            "wpaw_memanto_plan_approved",
            [$this, "on_plan_approved"],
            10,
            2,
        );
        add_action(
            "wpaw_memanto_plan_rejected",
            [$this, "on_plan_rejected"],
            10,
            2,
        );
        add_action(
            "wpaw_memanto_section_written",
            [$this, "on_section_written"],
            10,
            3,
        );
        add_action(
            "wpaw_memanto_block_refined",
            [$this, "on_block_refined"],
            10,
            3,
        );
        add_action(
            "wpaw_memanto_config_saved",
            [$this, "on_config_saved"],
            10,
            2,
        );
    }

    // =========================================================================
    // Session Lifecycle
    // =========================================================================

    /**
     * Called when a conversation session starts.
     * Ensures agents exist and recalls previous session state.
     *
     * @param string $session_id Session ID.
     * @param int    $post_id    Post ID.
     * @param int    $user_id    WordPress user ID.
     */
    public function on_session_start($session_id, $post_id, $user_id)
    {
        if (!$this->client->is_active()) {
            return;
        }

        // Ensure user agent exists.
        if ($user_id > 0) {
            $this->client->ensure_agent(
                $this->client->get_user_agent_id($user_id),
            );
        }

        // Ensure post agent exists.
        if ($post_id > 0) {
            $this->client->ensure_agent(
                $this->client->get_post_agent_id($post_id),
            );
        }
    }

    /**
     * Called when a conversation session ends.
     * Stores a session summary memory and deactivates the session.
     *
     * @param string $session_id Session ID.
     * @param int    $post_id    Post ID.
     */
    public function on_session_end($session_id, $post_id)
    {
        if (!$this->client->is_active() || $post_id <= 0) {
            return;
        }

        $post_agent = $this->client->get_post_agent_id($post_id);

        // Store a session summary.
        $this->client->remember(
            $post_agent,
            "Session ended: " . $session_id,
            "context",
            ["session:" . $session_id, "post:" . $post_id],
            "Session end",
        );

        // Deactivate MEMANTO session to trigger summary generation.
        $this->client->deactivate_session($post_agent);
    }

    // =========================================================================
    // Write-Through: Remember on Meaningful Events
    // =========================================================================

    /**
     * Store a memory when user sends a chat message.
     *
     * @param string $session_id Session ID.
     * @param string $content    User message content.
     * @param int    $post_id    Post ID.
     */
    public function on_user_message($session_id, $content, $post_id)
    {
        if (!$this->client->is_active() || $post_id <= 0) {
            return;
        }

        $this->client->remember(
            $this->client->get_post_agent_id($post_id),
            "User instruction: " . wp_strip_all_tags($content),
            "instruction",
            ["post:" . $post_id, "session:" . $session_id],
        );
    }

    /**
     * Store a memory when a plan is generated.
     *
     * @param int   $post_id Post ID.
     * @param array $plan    Plan data.
     */
    public function on_plan_generated($post_id, $plan)
    {
        if (!$this->client->is_active() || $post_id <= 0) {
            return;
        }

        $title = $plan["title"] ?? "Untitled";
        $sections =
            isset($plan["sections"]) && is_array($plan["sections"])
                ? count($plan["sections"])
                : 0;

        $this->client->remember(
            $this->client->get_post_agent_id($post_id),
            sprintf('Plan generated: "%s" with %d sections', $title, $sections),
            "artifact",
            ["post:" . $post_id, "type:plan"],
            "Plan: " . $title,
        );
    }

    /**
     * Store a memory when user approves a plan.
     *
     * @param int   $post_id Post ID.
     * @param array $plan    Plan data.
     */
    public function on_plan_approved($post_id, $plan)
    {
        if (!$this->client->is_active() || $post_id <= 0) {
            return;
        }

        $title = $plan["title"] ?? "Untitled";

        $this->client->remember(
            $this->client->get_post_agent_id($post_id),
            sprintf('User approved plan: "%s"', $title),
            "decision",
            ["post:" . $post_id, "type:plan"],
            "Plan approved",
        );
    }

    /**
     * Store a memory when user rejects or requests plan changes.
     *
     * @param int    $post_id Post ID.
     * @param string $reason  Rejection/revision reason.
     */
    public function on_plan_rejected($post_id, $reason)
    {
        if (!$this->client->is_active() || $post_id <= 0) {
            return;
        }

        $this->client->remember(
            $this->client->get_post_agent_id($post_id),
            "User requested plan revision: " . wp_strip_all_tags($reason),
            "error",
            ["post:" . $post_id, "type:plan"],
            "Plan revision",
        );
    }

    /**
     * Store a memory when a section is written.
     *
     * @param int    $post_id    Post ID.
     * @param string $section_id Section identifier.
     * @param string $summary    Brief section summary.
     */
    public function on_section_written($post_id, $section_id, $summary)
    {
        if (!$this->client->is_active() || $post_id <= 0) {
            return;
        }

        $this->client->remember(
            $this->client->get_post_agent_id($post_id),
            "Section written (" .
                $section_id .
                "): " .
                wp_strip_all_tags($summary),
            "artifact",
            ["post:" . $post_id, "section:" . $section_id],
            "Section: " . $section_id,
        );
    }

    /**
     * Store a memory when a block is refined.
     *
     * @param int    $post_id     Post ID.
     * @param string $block_id    Block identifier.
     * @param string $instruction Refinement instruction.
     */
    public function on_block_refined($post_id, $block_id, $instruction)
    {
        if (!$this->client->is_active() || $post_id <= 0) {
            return;
        }

        $this->client->remember(
            $this->client->get_post_agent_id($post_id),
            "Block refined (" .
                $block_id .
                "): " .
                wp_strip_all_tags($instruction),
            "instruction",
            ["post:" . $post_id, "block:" . $block_id],
        );
    }

    /**
     * Store a memory when post config is saved.
     *
     * @param int   $post_id Post ID.
     * @param array $config  Post config data.
     */
    public function on_config_saved($post_id, $config)
    {
        if (!$this->client->is_active()) {
            return;
        }

        $config_summary = sprintf(
            "tone=%s, audience=%s, length=%s, language=%s",
            $config["tone"] ?? "default",
            $config["audience"] ?? "general",
            $config["article_length"] ?? "medium",
            $config["language"] ?? "auto",
        );

        // Store to post agent.
        if ($post_id > 0) {
            $this->client->remember(
                $this->client->get_post_agent_id($post_id),
                "Article config: " . $config_summary,
                "preference",
                ["post:" . $post_id],
                "Post config",
            );
        }

        // Store to user agent (cross-post preferences).
        $user_id = get_current_user_id();
        if ($user_id > 0) {
            $this->client->remember(
                $this->client->get_user_agent_id($user_id),
                "User preference: " . $config_summary,
                "preference",
                ["user:" . $user_id],
                "Writing preferences",
            );
        }
    }

    // =========================================================================
    // Recall: Retrieve Memories for Context Enrichment
    // =========================================================================

    /**
     * Recall relevant memories to enrich AI prompt context.
     *
     * @param int    $post_id        Post ID.
     * @param int    $user_id        WordPress user ID.
     * @param string $current_message User's current message (for semantic search).
     * @return array Recalled memory items, each with 'type', 'content', 'title'.
     */
    public function recall_for_context(
        $post_id,
        $user_id,
        $current_message = "",
    ) {
        if (!$this->client->is_active()) {
            return [];
        }

        $memories = [];
        $seen = [];

        // 1. Recent post memories.
        if ($post_id > 0) {
            $post_agent = $this->client->get_post_agent_id($post_id);
            $recent = $this->client->recall_recent($post_agent, 10);

            foreach ($this->normalize_memories($recent) as $item) {
                $hash = md5($item["content"]);
                if (!isset($seen[$hash])) {
                    $seen[$hash] = true;
                    $memories[] = $item;
                }
            }

            // 2. Semantic recall based on current message.
            if (!empty($current_message)) {
                $semantic = $this->client->recall(
                    $post_agent,
                    $current_message,
                    [],
                    5,
                );
                foreach ($this->normalize_memories($semantic) as $item) {
                    $hash = md5($item["content"]);
                    if (!isset($seen[$hash])) {
                        $seen[$hash] = true;
                        $memories[] = $item;
                    }
                }
            }
        }

        // 3. User preferences (cross-post).
        if ($user_id > 0) {
            $user_prefs = $this->client->recall(
                $this->client->get_user_agent_id($user_id),
                "writing preferences tone audience language",
                ["preference"],
                5,
            );

            foreach ($this->normalize_memories($user_prefs) as $item) {
                $hash = md5($item["content"]);
                if (!isset($seen[$hash])) {
                    $seen[$hash] = true;
                    $memories[] = $item;
                }
            }
        }

        return $memories;
    }

    /**
     * Restore session state from MEMANTO when reopening a post.
     *
     * Returns a structured restore payload that the frontend can use to:
     * - Display a "Restored from memory" badge
     * - Build a restored-session system message for the AI
     * - Pre-fill config from prior preferences
     *
     * @since 0.4.0
     * @param int $post_id Post ID being reopened.
     * @param int $user_id Current user ID.
     * @return array { restored: bool, memories: array, preferences: array, summary: string }
     */
    public function restore_session($post_id, $user_id)
    {
        $empty = [
            "restored" => false,
            "memories" => [],
            "preferences" => [],
            "summary" => "",
        ];

        if (!$this->client->is_active() || $post_id <= 0) {
            return $empty;
        }

        // Recall recent post memories (no current message — restore is about history).
        $post_agent = $this->client->get_post_agent_id($post_id);
        $recent = $this->client->recall_recent($post_agent, 15);
        $memories = $this->normalize_memories($recent);

        // Recall user preferences (for cross-post config carry-over).
        $preferences = [];
        if ($user_id > 0) {
            $user_prefs = $this->client->recall(
                $this->client->get_user_agent_id($user_id),
                "writing preferences tone audience language length",
                ["preference"],
                5,
            );
            $preferences = $this->normalize_memories($user_prefs);
        }

        if (empty($memories) && empty($preferences)) {
            return $empty;
        }

        // Build a human-readable summary for the restore badge tooltip.
        $summary = $this->build_restore_summary($memories, $preferences);

        return [
            "restored" => true,
            "memories" => $memories,
            "preferences" => $preferences,
            "summary" => $summary,
        ];
    }

    /**
     * Build a compact summary string of restored memories.
     *
     * @param array $memories    Restored memories.
     * @param array $preferences Restored preferences.
     * @return string Summary text.
     */
    private function build_restore_summary($memories, $preferences)
    {
        $parts = [];

        if (!empty($memories)) {
            $parts[] = sprintf(
                /* translators: %d: number of memories */
                _n(
                    "%d memory",
                    "%d memories",
                    count($memories),
                    "wp-agentic-writer",
                ),
                count($memories),
            );
        }

        if (!empty($preferences)) {
            $parts[] = sprintf(
                /* translators: %d: number of preferences */
                _n(
                    "%d preference",
                    "%d preferences",
                    count($preferences),
                    "wp-agentic-writer",
                ),
                count($preferences),
            );
        }

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

    /**
     * Get user's writing preferences recalled from MEMANTO.
     *
     * Used when creating a new post to pre-fill the post config
     * with the user's habitual tone, audience, etc.
     *
     * @since 0.4.0
     * @param int $user_id WordPress user ID.
     * @return array { restored: bool, config: array }
     */
    public function get_user_preferences_for_new_post($user_id)
    {
        $empty = ["restored" => false, "config" => []];

        if (!$this->client->is_active() || $user_id <= 0) {
            return $empty;
        }

        $user_prefs = $this->client->recall(
            $this->client->get_user_agent_id($user_id),
            "writing preferences tone audience language length",
            ["preference"],
            3,
        );

        $prefs = $this->normalize_memories($user_prefs);
        if (empty($prefs)) {
            return $empty;
        }

        // Extract config fields from preference content.
        // Memory format: "User preference: tone=professional, audience=experts, length=long, language=en"
        $config = $this->extract_config_from_preferences($prefs);

        if (empty($config)) {
            return $empty;
        }

        return ["restored" => true, "config" => $config];
    }

    /**
     * Parse preference memory contents and extract config fields.
     *
     * @param array $prefs Normalized preference memories.
     * @return array Extracted config: { tone, audience, article_length, language }.
     */
    private function extract_config_from_preferences($prefs)
    {
        $config = [];

        // Combine all preference content into one blob for parsing.
        $blob = "";
        foreach ($prefs as $pref) {
            $blob .= " " . ($pref["content"] ?? "");
        }

        // Match key=value patterns.
        if (preg_match('/tone\s*=\s*([^,\n]+)/i', $blob, $m)) {
            $val = trim($m[1]);
            if ("" !== $val && "default" !== strtolower($val)) {
                $config["tone"] = $val;
            }
        }
        if (preg_match('/audience\s*=\s*([^,\n]+)/i', $blob, $m)) {
            $val = trim($m[1]);
            if ("" !== $val && "general" !== strtolower($val)) {
                $config["audience"] = $val;
            }
        }
        if (preg_match('/(?:article_)?length\s*=\s*([^,\n]+)/i', $blob, $m)) {
            $val = trim($m[1]);
            if ("" !== $val && "medium" !== strtolower($val)) {
                $config["article_length"] = $val;
            }
        }
        if (preg_match('/language\s*=\s*([^,\n]+)/i', $blob, $m)) {
            $val = trim($m[1]);
            if ("" !== $val && "auto" !== strtolower($val)) {
                $config["language"] = $val;
            }
        }

        return $config;
    }

    /**
     * Build a "restored session" system message for the AI.
     *
     * This message summarizes prior post work so the AI can resume
     * mid-article without re-asking the user for context.
     *
     * @since 0.4.0
     * @param array $restore_payload Result from restore_session().
     * @return string System message content (empty if nothing to restore).
     */
    public function build_session_restore_message($restore_payload)
    {
        if (empty($restore_payload["restored"])) {
            return "";
        }

        $memories = $restore_payload["memories"] ?? [];
        $preferences = $restore_payload["preferences"] ?? [];

        if (empty($memories) && empty($preferences)) {
            return "";
        }

        $lines = ["SESSION RESTORED FROM MEMORY"];
        $lines[] =
            "The user has returned to this post. Below is context from prior sessions.";
        $lines[] =
            "Use this to continue where the conversation left off without re-asking.";

        // Summarize prior post work.
        if (!empty($memories)) {
            $lines[] = "";
            $lines[] = "## Prior session activity";

            $grouped = [];
            foreach ($memories as $memory) {
                $type = $memory["type"] ?? "context";
                if (!isset($grouped[$type])) {
                    $grouped[$type] = [];
                }
                $grouped[$type][] = $memory;
            }

            // Render in priority order: plan > decision > instruction > artifact > error > context.
            $priority = [
                "artifact",
                "decision",
                "instruction",
                "error",
                "learning",
                "context",
                "preference",
            ];
            foreach ($priority as $type) {
                if (empty($grouped[$type])) {
                    continue;
                }
                $type_label = ucfirst($type) . "s";
                $lines[] = "### {$type_label}";
                foreach ($grouped[$type] as $m) {
                    $content = trim($m["content"] ?? "");
                    if ("" === $content) {
                        continue;
                    }
                    $title = !empty($m["title"])
                        ? " [" . trim($m["title"]) . "]"
                        : "";
                    $lines[] = "- {$content}{$title}";
                }
            }
        }

        // Append user preferences (compact).
        if (!empty($preferences)) {
            $lines[] = "";
            $lines[] = "## User writing preferences";
            foreach ($preferences as $pref) {
                $content = trim($pref["content"] ?? "");
                if ("" !== $content) {
                    $lines[] = "- {$content}";
                }
            }
        }

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

    /**
     * Normalize raw MEMANTO response into a uniform array.
     *
     * @param array $raw Raw response from recall/recall_recent.
     * @return array Normalized items: { type, content, title }.
     */
    private function normalize_memories($raw)
    {
        if (!is_array($raw)) {
            return [];
        }

        // Handle different possible response shapes.
        $items = $raw;

        // If response has a 'memories' or 'results' key, unwrap.
        if (isset($raw["memories"]) && is_array($raw["memories"])) {
            $items = $raw["memories"];
        } elseif (isset($raw["results"]) && is_array($raw["results"])) {
            $items = $raw["results"];
        }

        $normalized = [];
        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }
            $normalized[] = [
                "type" => $item["type"] ?? "context",
                "content" => $item["content"] ?? ($item["text"] ?? ""),
                "title" => $item["title"] ?? "",
            ];
        }

        return $normalized;
    }
}
