<?php

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

if (!function_exists('aimogenpro_chroma_normalize_base_url')) {
    function aimogenpro_chroma_normalize_base_url($url)
    {
        $url = trim((string) $url);

        if ($url === '') {
            return '';
        }

        if (!preg_match('#^https?://#i', $url)) {
            $url = 'https://' . $url;
        }

        return rtrim($url, '/');
    }
}

if (!function_exists('aimogenpro_chroma_endpoint')) {
    function aimogenpro_chroma_endpoint($base_url, $tenant, $database, $path = '')
    {
        $base_url = aimogenpro_chroma_normalize_base_url($base_url);

        return $base_url
            . '/api/v2/tenants/' . rawurlencode($tenant)
            . '/databases/' . rawurlencode($database)
            . $path;
    }
}

if (!function_exists('aimogenpro_chroma_request_once')) {
    function aimogenpro_chroma_request_once($method, $url, $headers = array(), $body = null)
    {
        $args = array(
            'method'      => strtoupper($method),
            'timeout'     => 60,
            'redirection' => 3,
            'headers'     => $headers,
        );

        if ($body !== null) {
            $args['body'] = wp_json_encode($body);
        }

        $response = wp_remote_request($url, $args);

        if (is_wp_error($response)) {
            return $response;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        $raw  = (string) wp_remote_retrieve_body($response);

        $decoded = null;

        if ($raw !== '') {
            $decoded = json_decode($raw, true);
        }

        if ($code >= 200 && $code < 300) {
            if ($raw === '') {
                return array();
            }

            if (json_last_error() === JSON_ERROR_NONE) {
                return $decoded;
            }

            return array(
                'raw' => $raw,
            );
        }

        $message = $raw;

        if (is_array($decoded)) {
            if (isset($decoded['error'])) {
                $message = is_scalar($decoded['error']) ? (string) $decoded['error'] : wp_json_encode($decoded['error']);
            } elseif (isset($decoded['message'])) {
                $message = (string) $decoded['message'];
            } elseif (isset($decoded['detail'])) {
                $message = is_scalar($decoded['detail']) ? (string) $decoded['detail'] : wp_json_encode($decoded['detail']);
            }
        }

        return new WP_Error(
            'chroma_http_error',
            'Chroma API error (' . $code . '): ' . $message,
            array(
                'status' => $code,
                'body'   => $raw,
            )
        );
    }
}

if (!function_exists('aimogenpro_chroma_request')) {
    function aimogenpro_chroma_request($method, $url, $api_key = '', $body = null)
    {
        $headers = array(
            'Accept'       => 'application/json',
            'Content-Type' => 'application/json',
        );

        $api_key = trim((string) $api_key);

        if ($api_key !== '') {
            $headers['x-chroma-token'] = $api_key;
        }

        $result = aimogenpro_chroma_request_once($method, $url, $headers, $body);

        /*
         * Some self-hosted Chroma setups use Authorization: Bearer instead.
         * Chroma Cloud normally uses x-chroma-token.
         */
        if (is_wp_error($result) && $api_key !== '') {
            $data   = $result->get_error_data();
            $status = isset($data['status']) ? (int) $data['status'] : 0;

            if ($status === 401 || $status === 403) {
                unset($headers['x-chroma-token']);
                $headers['Authorization'] = 'Bearer ' . $api_key;

                $result = aimogenpro_chroma_request_once($method, $url, $headers, $body);
            }
        }

        return $result;
    }
}

if (!function_exists('aimogenpro_chroma_get_collection_id')) {
    function aimogenpro_chroma_get_collection_id($api_key, $base_url, $tenant, $database, $collection_name)
    {
        $collection_name = trim((string) $collection_name);

        if ($collection_name === '') {
            return new WP_Error('missing_chroma_collection', 'Missing Chroma collection name.');
        }
        $cache_key = 'aimogenpro_chroma_collection_id_' . md5($base_url . '|' . $tenant . '|' . $database . '|' . $collection_name);
        $cached_id = get_transient($cache_key);

        if (!empty($cached_id)) {
            return (string) $cached_id;
        }

        $direct_url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections/' . rawurlencode($collection_name)
        );

        $direct = aimogenpro_chroma_request('GET', $direct_url, $api_key);

        if (!is_wp_error($direct) && is_array($direct)) {
            if (!empty($direct['id'])) {
                set_transient($cache_key, (string) $direct['id'], HOUR_IN_SECONDS);
                return (string) $direct['id'];
            }

            if (!empty($direct['collection_id'])) {
                set_transient($cache_key, (string) $direct['collection_id'], HOUR_IN_SECONDS);
                return (string) $direct['collection_id'];
            }

            if (!empty($direct['name']) && $direct['name'] === $collection_name) {
                
                set_transient($cache_key, (string) $collection_name, HOUR_IN_SECONDS);
                return $collection_name;
            }
        }

        $list_url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections'
        );

        $collections_response = aimogenpro_chroma_request('GET', $list_url, $api_key);

        if (is_wp_error($collections_response)) {
            return $collections_response;
        }

        $collections = array();

        if (isset($collections_response['collections']) && is_array($collections_response['collections'])) {
            $collections = $collections_response['collections'];
        } elseif (isset($collections_response['data']) && is_array($collections_response['data'])) {
            $collections = $collections_response['data'];
        } elseif (is_array($collections_response)) {
            $collections = $collections_response;
        }

        foreach ($collections as $collection) {
            if (!is_array($collection)) {
                continue;
            }

            $name = isset($collection['name']) ? (string) $collection['name'] : '';
            $id   = '';

            if (!empty($collection['id'])) {
                $id = (string) $collection['id'];
            } elseif (!empty($collection['collection_id'])) {
                $id = (string) $collection['collection_id'];
            }

            if ($name === $collection_name || $id === $collection_name) {
                $id = $id !== '' ? $id : $collection_name;
                set_transient($cache_key, (string) $id, HOUR_IN_SECONDS);
                return $id;
            }
        }

        return new WP_Error(
            'chroma_collection_not_found',
            'Chroma collection not found: ' . $collection_name
        );
    }
}

if (!function_exists('aimogenpro_chroma_query_vectors')) {
    function aimogenpro_chroma_query_vectors($api_key, $base_url, $tenant, $database, $collection_name, $top_k, $embedding, $where = array(), $where_document = array())
    {
        $collection_id = aimogenpro_chroma_get_collection_id(
            $api_key,
            $base_url,
            $tenant,
            $database,
            $collection_name
        );

        if (is_wp_error($collection_id)) {
            return $collection_id;
        }

        $top_k = (int) $top_k;

        if ($top_k < 1) {
            $top_k = 1;
        }

        if ($top_k > 10000) {
            $top_k = 10000;
        }

        $embedding = array_values(array_map('floatval', (array) $embedding));

        if (empty($embedding)) {
            return new WP_Error('missing_chroma_embedding', 'Missing embedding vector for Chroma query.');
        }

        $body = array(
            'query_embeddings' => array($embedding),
            'n_results'        => $top_k,
            'include'          => array('documents', 'metadatas', 'distances'),
        );

        if (!empty($where)) {
            $body['where'] = $where;
        }

        if (!empty($where_document)) {
            $body['where_document'] = $where_document;
        }

        $query_url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections/' . rawurlencode($collection_id) . '/query'
        );

        $response = aimogenpro_chroma_request('POST', $query_url, $api_key, $body);

        if (is_wp_error($response)) {
            return $response;
        }

        $ids       = isset($response['ids'][0]) && is_array($response['ids'][0]) ? $response['ids'][0] : array();
        $documents = isset($response['documents'][0]) && is_array($response['documents'][0]) ? $response['documents'][0] : array();
        $metadatas = isset($response['metadatas'][0]) && is_array($response['metadatas'][0]) ? $response['metadatas'][0] : array();
        $distances = isset($response['distances'][0]) && is_array($response['distances'][0]) ? $response['distances'][0] : array();

        $matches = array();

        foreach ($ids as $i => $id) {
            $distance = isset($distances[$i]) ? $distances[$i] : null;

            $matches[] = array(
                'id'       => (string) $id,
                'distance' => $distance,
                'score'    => is_numeric($distance) ? 1 / (1 + (float) $distance) : null,
                'document' => isset($documents[$i]) ? (string) $documents[$i] : '',
                'metadata' => isset($metadatas[$i]) && is_array($metadatas[$i]) ? $metadatas[$i] : array(),
            );
        }

        return $matches;
    }
}

if (!function_exists('aimogenpro_chroma_upsert_vectors')) {
    function aimogenpro_chroma_upsert_vectors($api_key, $base_url, $tenant, $database, $collection_name, $records)
    {
        $collection_id = aimogenpro_chroma_get_collection_id(
            $api_key,
            $base_url,
            $tenant,
            $database,
            $collection_name
        );

        if (is_wp_error($collection_id)) {
            return $collection_id;
        }

        $ids        = array();
        $embeddings = array();
        $documents  = array();
        $metadatas  = array();

        foreach ((array) $records as $record) {
            if (empty($record['id']) || empty($record['vector']) || !is_array($record['vector'])) {
                continue;
            }

            $ids[]        = (string) $record['id'];
            $embeddings[] = array_values(array_map('floatval', $record['vector']));
            $documents[]  = isset($record['document']) ? (string) $record['document'] : null;
            $metadatas[]  = isset($record['payload']) && is_array($record['payload']) ? $record['payload'] : array();
        }

        if (empty($ids)) {
            return new WP_Error('missing_chroma_records', 'No valid Chroma records to upsert.');
        }

        $body = array(
            'ids'        => $ids,
            'embeddings' => $embeddings,
            'documents'  => $documents,
            'metadatas'  => $metadatas,
        );

        $url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections/' . rawurlencode($collection_id) . '/upsert'
        );

        return aimogenpro_chroma_request('POST', $url, $api_key, $body);
    }
}

if (!function_exists('aimogenpro_chroma_delete_vectors')) {
    function aimogenpro_chroma_delete_vectors($api_key, $base_url, $tenant, $database, $collection_name, $args = array())
    {
        $collection_id = aimogenpro_chroma_get_collection_id(
            $api_key,
            $base_url,
            $tenant,
            $database,
            $collection_name
        );

        if (is_wp_error($collection_id)) {
            return $collection_id;
        }

        $body = array();

        if (!empty($args['ids']) && is_array($args['ids'])) {
            $body['ids'] = array_values(array_map('strval', $args['ids']));
        }

        if (!empty($args['where']) && is_array($args['where'])) {
            $body['where'] = $args['where'];
        }

        if (empty($body)) {
            return new WP_Error('missing_chroma_delete_args', 'Missing Chroma delete ids or where filter.');
        }

        $url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections/' . rawurlencode($collection_id) . '/delete'
        );

        return aimogenpro_chroma_request('POST', $url, $api_key, $body);
    }
}

if (!function_exists('aimogenpro_chroma_matches_to_content')) {
    function aimogenpro_chroma_matches_to_content($matches)
    {
        $data = '';

        foreach ((array) $matches as $match) {
            $document = isset($match['document']) ? trim((string) $match['document']) : '';

            if ($document !== '') {
                $data .= $data === '' ? $document : "\n" . $document;
                continue;
            }

            /*
             * Fallback for older local Aiomatic records.
             * New Chroma records should store the text in Chroma documents.
             */
            $vector_id = isset($match['id']) ? trim((string) $match['id']) : '';

            if ($vector_id === '') {
                continue;
            }

            $meta_keys = array(
                'chroma_id',
                'chroma_vector_id'
            );

            foreach ($meta_keys as $meta_key) {
                $posts = get_posts(array(
                    'post_type'      => 'aiomatic_embeddings',
                    'posts_per_page' => 1,
                    'fields'         => 'all',
                    'meta_query'     => array(
                        array(
                            'key'     => $meta_key,
                            'value'   => $vector_id,
                            'compare' => '=',
                        ),
                    ),
                ));

                if (!empty($posts)) {
                    $content = trim((string) $posts[0]->post_content);

                    if ($content !== '') {
                        $data .= $data === '' ? $content : "\n" . $content;
                    }

                    break;
                }
            }
        }

        return $data;
    }
}if (!function_exists('aimogenpro_chroma_add_index')) {
    function aimogenpro_chroma_add_index($api_key, $base_url, $tenant, $database, $name, $metadata = array())
    {
        $name = trim((string) $name);

        if ($name === '') {
            return new WP_Error('missing_chroma_collection_name', 'Missing Chroma collection name.');
        }

        $body = array(
            'name'          => $name,
            'get_or_create' => true,
        );

        if (!empty($metadata) && is_array($metadata)) {
            $body['metadata'] = $metadata;
        }

        $url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections'
        );

        $response = aimogenpro_chroma_request('POST', $url, $api_key, $body);

        if (is_wp_error($response)) {
            return $response;
        }

        delete_transient('aimogenpro_chroma_collection_id_' . md5($base_url . '|' . $tenant . '|' . $database . '|' . $name));

        return $name;
    }
}

if (!function_exists('aimogenpro_chroma_delete_index')) {
    function aimogenpro_chroma_delete_index($api_key, $base_url, $tenant, $database, $name)
    {
        $collection_id = aimogenpro_chroma_get_collection_id(
            $api_key,
            $base_url,
            $tenant,
            $database,
            $name
        );

        if (is_wp_error($collection_id)) {
            return $collection_id;
        }

        $url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections/' . rawurlencode($collection_id)
        );

        $response = aimogenpro_chroma_request('DELETE', $url, $api_key);

        if (is_wp_error($response)) {
            return $response;
        }

        delete_transient('aimogenpro_chroma_collection_id_' . md5($base_url . '|' . $tenant . '|' . $database . '|' . $name));

        return true;
    }
}

if (!function_exists('aimogenpro_chroma_list_indexes')) {
    function aimogenpro_chroma_list_indexes($api_key, $base_url, $tenant, $database, $limit = 1000, $offset = 0)
    {
        $limit  = max(1, min(1000, (int) $limit));
        $offset = max(0, (int) $offset);

        $url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections?limit=' . $limit . '&offset=' . $offset
        );

        $response = aimogenpro_chroma_request('GET', $url, $api_key);

        if (is_wp_error($response)) {
            return $response;
        }

        $collections = array();

        if (isset($response['collections']) && is_array($response['collections'])) {
            $collections = $response['collections'];
        } elseif (isset($response['data']) && is_array($response['data'])) {
            $collections = $response['data'];
        } elseif (is_array($response)) {
            $collections = $response;
        }

        $indexes = array();

        foreach ($collections as $collection) {
            if (!is_array($collection)) {
                continue;
            }

            $indexes[] = array(
                'id'        => isset($collection['id']) ? (string) $collection['id'] : '',
                'name'      => isset($collection['name']) ? (string) $collection['name'] : '',
                'dimension' => isset($collection['dimension']) ? $collection['dimension'] : null,
                'metadata'  => isset($collection['metadata']) && is_array($collection['metadata']) ? $collection['metadata'] : array(),
                'host'      => aimogenpro_chroma_normalize_base_url($base_url),
                'ready'     => true,
            );
        }

        return $indexes;
    }
}

if (!function_exists('aimogenpro_chroma_add_vector')) {
    function aimogenpro_chroma_add_vector($api_key, $base_url, $tenant, $database, $collection_name, $vector)
    {
        $id = '';

        if (!empty($vector['id'])) {
            $id = (string) $vector['id'];
        } else {
            $id = aimogenpro_chroma_get_uuid();
        }

        $values = array();

        if (isset($vector['values']) && is_array($vector['values'])) {
            $values = $vector['values'];
        } elseif (isset($vector['vector']) && is_array($vector['vector'])) {
            $values = $vector['vector'];
        }

        if (empty($values)) {
            return new WP_Error('missing_chroma_vector_values', 'Missing Chroma vector values.');
        }

        $document = '';

        if (isset($vector['document'])) {
            $document = (string) $vector['document'];
        } elseif (isset($vector['text'])) {
            $document = (string) $vector['text'];
        } elseif (isset($vector['content'])) {
            $document = (string) $vector['content'];
        }

        $payload = array();

        if (isset($vector['metadata']) && is_array($vector['metadata'])) {
            $payload = $vector['metadata'];
        } elseif (isset($vector['payload']) && is_array($vector['payload'])) {
            $payload = $vector['payload'];
        }

        if (!isset($payload['title']) && !empty($vector['id'])) {
            $payload['title'] = (string) $vector['id'];
        }

        $record = array(
            'id'       => $id,
            'vector'   => $values,
            'document' => $document,
            'payload'  => $payload,
        );

        $response = aimogenpro_chroma_upsert_vectors(
            $api_key,
            $base_url,
            $tenant,
            $database,
            $collection_name,
            array($record)
        );

        if (is_wp_error($response)) {
            return $response;
        }

        return $id;
    }
}

if (!function_exists('aimogenpro_chroma_get_vector')) {
    function aimogenpro_chroma_get_vector($api_key, $base_url, $tenant, $database, $collection_name, $vector_id, $with_vector = true)
    {
        $collection_id = aimogenpro_chroma_get_collection_id(
            $api_key,
            $base_url,
            $tenant,
            $database,
            $collection_name
        );

        if (is_wp_error($collection_id)) {
            return $collection_id;
        }

        $include = array('documents', 'metadatas');

        if ($with_vector) {
            $include[] = 'embeddings';
        }

        $body = array(
            'ids'     => array((string) $vector_id),
            'include' => $include,
        );

        $url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections/' . rawurlencode($collection_id) . '/get'
        );

        $response = aimogenpro_chroma_request('POST', $url, $api_key, $body);

        if (is_wp_error($response)) {
            return $response;
        }

        if (empty($response['ids'][0])) {
            return null;
        }

        $metadata = isset($response['metadatas'][0]) && is_array($response['metadatas'][0]) ? $response['metadatas'][0] : array();

        return array(
            'id'       => (string) $response['ids'][0],
            'type'     => isset($metadata['type']) ? (string) $metadata['type'] : 'manual',
            'title'    => isset($metadata['title']) ? (string) $metadata['title'] : '',
            'document' => isset($response['documents'][0]) ? (string) $response['documents'][0] : '',
            'metadata' => $metadata,
            'values'   => isset($response['embeddings'][0]) && is_array($response['embeddings'][0]) ? $response['embeddings'][0] : array(),
        );
    }
}

if (!function_exists('aimogenpro_chroma_list_vectors')) {
    function aimogenpro_chroma_list_vectors($api_key, $base_url, $tenant, $database, $collection_name, $limit = 100, $offset = 0, $where = array())
    {
        $collection_id = aimogenpro_chroma_get_collection_id(
            $api_key,
            $base_url,
            $tenant,
            $database,
            $collection_name
        );

        if (is_wp_error($collection_id)) {
            return $collection_id;
        }

        $limit  = max(1, min(1000, (int) $limit));
        $offset = max(0, (int) $offset);

        $body = array(
            'limit'   => $limit,
            'offset'  => $offset,
            'include' => array('metadatas'),
        );

        /*
         * If your Chroma server rejects listing without a where filter,
         * store a simple metadata marker on insert, for example:
         * source => aiomatic
         * Then call this with array('source' => 'aiomatic').
         */
        if (!empty($where) && is_array($where)) {
            $body['where'] = $where;
        }

        $url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections/' . rawurlencode($collection_id) . '/get'
        );

        $response = aimogenpro_chroma_request('POST', $url, $api_key, $body);

        if (is_wp_error($response)) {
            return $response;
        }

        if (empty($response['ids']) || !is_array($response['ids'])) {
            return array();
        }

        return array_values(array_map('strval', $response['ids']));
    }
}

if (!function_exists('aimogenpro_chroma_count_vectors')) {
    function aimogenpro_chroma_count_vectors($api_key, $base_url, $tenant, $database, $collection_name)
    {
        $collection_id = aimogenpro_chroma_get_collection_id(
            $api_key,
            $base_url,
            $tenant,
            $database,
            $collection_name
        );

        if (is_wp_error($collection_id)) {
            return $collection_id;
        }

        $url = aimogenpro_chroma_endpoint(
            $base_url,
            $tenant,
            $database,
            '/collections/' . rawurlencode($collection_id) . '/count'
        );

        return aimogenpro_chroma_request('GET', $url, $api_key);
    }
}

if (!function_exists('aimogenpro_chroma_get_uuid')) {
    function aimogenpro_chroma_get_uuid($len = 32, $strong = true)
    {
        $data = openssl_random_pseudo_bytes($len, $strong);

        if ($data === false || strlen($data) < 16) {
            return wp_generate_uuid4();
        }

        $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
        $data[8] = chr(ord($data[8]) & 0x3f | 0x80);

        return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
    }
}