<?php
namespace Nibiru\Module\Ai\Plugins;

/**
 * Embeddings — turn text into vectors via Ollama (or OpenAI fallback).
 *
 *   $vec = $ai->embed()->one('hello world');                // float[]
 *   $vecs = $ai->embed()->batch(['a', 'b', 'c']);           // float[][]
 *   $sim = \Nibiru\Module\Ai\Plugins\Embed::cosine($a, $b);  // 0..1
 */
class Embed
{
    protected \stdClass $cfg;
    protected Ollama $ollama;

    public function __construct(\stdClass $cfg)
    {
        $this->cfg = $cfg;
        $this->ollama = new Ollama($cfg);
    }

    /**
     * Embed a single string. Returns a flat float[].
     */
    public function one(string $text): array
    {
        $model = $this->cfg->embed_model ?? 'nomic-embed-text';
        $res = $this->ollama->embed($model, $text);
        if (!isset($res['embedding']) || !is_array($res['embedding'])) {
            throw new \RuntimeException('Ollama embed: no `embedding` in response.');
        }
        return array_map('floatval', $res['embedding']);
    }

    /**
     * Embed many strings. Sequential under the hood (Ollama embeddings
     * endpoint is single-input), but rate-limited by config.
     */
    public function batch(array $texts): array
    {
        $out = [];
        foreach ($texts as $t) {
            $out[] = $this->one((string) $t);
        }
        return $out;
    }

    /**
     * Cosine similarity between two equal-length vectors. Returns 0–1.
     */
    public static function cosine(array $a, array $b): float
    {
        $dot = 0.0;
        $na = 0.0;
        $nb = 0.0;
        $len = min(count($a), count($b));
        for ($i = 0; $i < $len; $i++) {
            $dot += $a[$i] * $b[$i];
            $na  += $a[$i] * $a[$i];
            $nb  += $b[$i] * $b[$i];
        }
        $denom = sqrt($na) * sqrt($nb);
        return $denom === 0.0 ? 0.0 : $dot / $denom;
    }

    /**
     * Pack a vector to a base64 string for compact storage in JSON.
     */
    public static function pack(array $vec): string
    {
        return base64_encode(pack('f*', ...$vec));
    }

    /**
     * Inverse of pack().
     */
    public static function unpack(string $b64): array
    {
        $bin = base64_decode($b64, true);
        if ($bin === false) return [];
        return array_values(unpack('f*', $bin));
    }
}
