import { buildUrl, toServiceException } from "../_util.js";
import { ListParamsSchema } from "../pagination.js";
import { safeStringify } from "../../json.js";
import { StructuredError } from "../../error.js";
import { z } from 'zod';
/**
 * Minimum TTL value in seconds (1 minute)
 */
export const VECTOR_MIN_TTL_SECONDS = 60;
/**
 * Maximum TTL value in seconds (90 days)
 */
export const VECTOR_MAX_TTL_SECONDS = 7776000;
/**
 * Default TTL value in seconds (30 days) - used when no TTL is specified
 */
export const VECTOR_DEFAULT_TTL_SECONDS = 2592000;
/**
 * Base properties shared by all vector upsert operations
 */
export const VectorUpsertBaseSchema = z.object({
    /**
     * the key of the vector object which can be used as a reference. the value of this key is opaque to the vector storage.
     */
    key: z
        .string()
        .describe('the key of the vector object which can be used as a reference. the value of this key is opaque to the vector storage.'),
    /**
     * the metadata to upsert
     */
    metadata: z.record(z.string(), z.unknown()).optional().describe('the metadata to upsert'),
    /**
     * Time-to-live in seconds for the vector. Controls when the vector expires and is automatically deleted.
     * - `undefined` (not provided): Vector expires after 30 days (default)
     * - `null` or `0`: Vector never expires
     * - positive number (≥60): Vector expires after the specified number of seconds (max 90 days)
     *
     * @remarks
     * TTL values below 60 seconds are clamped to 60 seconds by the server.
     * TTL values above 7,776,000 seconds (90 days) are clamped to 90 days.
     *
     * @default 2592000 (30 days)
     */
    ttl: z
        .number()
        .nullable()
        .optional()
        .describe('Time-to-live in seconds for the vector. Controls when the vector expires and is automatically deleted.'),
});
/**
 * Upsert using pre-computed embeddings
 */
export const VectorUpsertEmbeddingsSchema = VectorUpsertBaseSchema.extend({
    /**
     * the embeddings to upsert
     */
    embeddings: z.array(z.number()).describe('the embeddings to upsert'),
    document: z.never().optional(),
});
/**
 * Upsert using text that will be converted to embeddings
 */
export const VectorUpsertTextSchema = VectorUpsertBaseSchema.extend({
    /**
     * the text to use for the embedding
     */
    document: z.string().describe('the text to use for the embedding'),
    embeddings: z.never().optional(),
});
export const VectorUpsertParamsSchema = z.discriminatedUnion('document', [
    VectorUpsertTextSchema,
    VectorUpsertEmbeddingsSchema.extend({ document: z.undefined().optional() }),
]);
/**
 * Parameters for searching vectors
 */
export const VectorSearchParamsSchema = (metadataSchema) => z.object({
    /**
     * The text query to search for in the vector storage. This will be converted to embeddings
     * and used to find semantically similar documents.
     *
     * @example "comfortable office chair"
     * @example "machine learning algorithms"
     */
    query: z
        .string()
        .describe('The text query to search for in the vector storage. This will be converted to embeddings and used to find semantically similar documents.'),
    /**
     * Maximum number of search results to return. If not specified, the server default will be used.
     * Must be a positive integer.
     *
     * @default 10
     * @example 5
     * @example 20
     */
    limit: z
        .number()
        .optional()
        .describe('Maximum number of search results to return. If not specified, the server default will be used.'),
    /**
     * Minimum similarity threshold for results. Only vectors with similarity scores greater than or equal
     * to this value will be returned. Value must be between 0.0 and 1.0, where 1.0 means exact match
     * and 0.0 means no similarity requirement.
     *
     * @minimum 0.0
     * @maximum 1.0
     * @example 0.7
     * @example 0.5
     */
    similarity: z
        .number()
        .min(0)
        .max(1)
        .optional()
        .describe('Minimum similarity threshold for results. Only vectors with similarity scores greater than or equal to this value will be returned.'),
    /**
     * Metadata filters to apply to the search. Only vectors whose metadata matches all specified
     * key-value pairs will be included in results. Must be a valid JSON object if provided.
     *
     * @example { category: "furniture", inStock: true }
     * @example { userId: "123", type: "product" }
     */
    metadata: metadataSchema
        .optional()
        .describe('Metadata filters to apply to the search. Only vectors whose metadata matches all specified key-value pairs will be included in results.'),
});
/**
 * Result of a vector search operation with optional type-safe metadata
 */
export const VectorSearchResultSchema = (metadataSchema) => z.object({
    /**
     * the unique id of the object in vector storage
     */
    id: z.string().describe('the unique id of the object in vector storage'),
    /**
     * the key used when the vector object was added to vector storage
     */
    key: z.string().describe('the key used when the vector object was added to vector storage'),
    /**
     * the metadata of the vector object when it was stored
     */
    metadata: metadataSchema
        .optional()
        .describe('the metadata of the vector object when it was stored'),
    /**
     * the distance of the vector object from the query from 0-1. The larger the number, the more similar the vector object is to the query.
     */
    similarity: z
        .number()
        .describe('the distance of the vector object from the query from 0-1. The larger the number, the more similar the vector object is to the query.'),
    /**
     * the expiration time of the vector as an ISO 8601 timestamp.
     * undefined if the vector does not expire.
     */
    expiresAt: z
        .string()
        .optional()
        .describe('the expiration time of the vector as an ISO 8601 timestamp.'),
});
/**
 * Extended search result that includes the document and embeddings
 */
export const VectorSearchResultWithDocumentSchema = (metadataSchema) => VectorSearchResultSchema(metadataSchema).extend({
    /**
     * the document that was used to create the vector object
     */
    document: z
        .string()
        .optional()
        .describe('the document that was used to create the vector object'),
    /**
     * the embeddings of the vector object
     */
    embeddings: z.array(z.number()).optional().describe('the embeddings of the vector object'),
});
/**
 * Result of a vector upsert operation
 */
export const VectorUpsertResultSchema = z.object({
    /**
     * the key from the original upsert document
     */
    key: z.string().describe('the key from the original upsert document'),
    /**
     * the generated id for this vector in storage
     */
    id: z.string().describe('the generated id for this vector in storage'),
});
/**
 * Result when a vector is found by key
 */
export const VectorResultFoundSchema = (metadataSchema) => z.object({
    /**
     * the vector data
     */
    data: VectorSearchResultWithDocumentSchema(metadataSchema).describe('the vector data'),
    /**
     * the vector was found
     */
    exists: z.literal(true).describe('the vector was found'),
});
/**
 * Result when a vector is not found by key
 */
export const VectorResultNotFoundSchema = z.object({
    /**
     * no data available
     */
    data: z.never().describe('no data available'),
    /**
     * the vector was not found
     */
    exists: z.literal(false).describe('the vector was not found'),
});
export const VectorResultSchema = (metadataSchema) => z.discriminatedUnion('exists', [
    VectorResultFoundSchema(metadataSchema),
    VectorResultNotFoundSchema,
]);
/**
 * Statistics for a vector namespace
 */
export const VectorNamespaceStatsSchema = z.object({
    /**
     * Total size in bytes
     */
    sum: z.number().describe('Total size in bytes'),
    /**
     * Number of vectors in the namespace
     */
    count: z.number().describe('Number of vectors in the namespace'),
    /**
     * Unix timestamp (milliseconds) when the namespace was created
     */
    createdAt: z
        .number()
        .optional()
        .describe('Unix timestamp (milliseconds) when the namespace was created'),
    /**
     * Unix timestamp (milliseconds) when the namespace was last used
     */
    lastUsed: z
        .number()
        .optional()
        .describe('Unix timestamp (milliseconds) when the namespace was last used'),
    /**
     * Whether this namespace is system-managed (internal)
     */
    internal: z
        .boolean()
        .optional()
        .describe('Whether this namespace is system-managed (internal).'),
});
/**
 * Statistics for an individual vector item
 */
export const VectorItemStatsSchema = z.object({
    /**
     * The embedding vector
     */
    embedding: z.array(z.number()).optional().describe('The embedding vector'),
    /**
     * The original document text
     */
    document: z.string().optional().describe('The original document text'),
    /**
     * Size in bytes
     */
    size: z.number().describe('Size in bytes'),
    /**
     * Metadata associated with the vector
     */
    metadata: z
        .record(z.string(), z.unknown())
        .optional()
        .describe('Metadata associated with the vector'),
    /**
     * Unix timestamp (milliseconds) when the vector was first accessed
     */
    firstUsed: z
        .number()
        .describe('Unix timestamp (milliseconds) when the vector was first accessed'),
    /**
     * Unix timestamp (milliseconds) when the vector was last accessed
     */
    lastUsed: z.number().describe('Unix timestamp (milliseconds) when the vector was last accessed'),
    /**
     * Number of times the vector was accessed.
     * Note: This is only tracked in cloud storage; local development returns undefined.
     */
    count: z.number().optional().describe('Number of times the vector was accessed.'),
    /**
     * the expiration time of the vector as an ISO 8601 timestamp.
     * undefined if the vector does not expire.
     */
    expiresAt: z
        .string()
        .optional()
        .describe('the expiration time of the vector as an ISO 8601 timestamp.'),
});
/**
 * Namespace statistics with sampled vector results
 */
export const VectorNamespaceStatsWithSamplesSchema = VectorNamespaceStatsSchema.extend({
    /**
     * A sample of vectors in the namespace (up to 20)
     */
    sampledResults: z
        .record(z.string(), VectorItemStatsSchema)
        .optional()
        .describe('A sample of vectors in the namespace (up to 20)'),
});
export const VectorSortFieldSchema = z.enum(['name', 'size', 'records', 'created', 'lastUsed']);
/**
 * Parameters for getting all namespace statistics with optional pagination
 */
export const VectorGetAllStatsParamsSchema = ListParamsSchema(VectorSortFieldSchema).extend({
    /**
     * Filter namespaces by name substring
     */
    name: z.string().optional().describe('Filter namespaces by name substring'),
});
/**
 * Paginated response for vector namespace statistics
 */
export const VectorStatsPaginatedSchema = z.object({
    /**
     * Map of namespace names to their statistics
     */
    namespaces: z
        .record(z.string(), VectorNamespaceStatsSchema)
        .describe('Map of namespace names to their statistics'),
    /**
     * Total number of namespaces across all pages
     */
    total: z.number().describe('Total number of namespaces across all pages'),
    /**
     * Number of namespaces requested per page
     */
    limit: z.number().describe('Number of namespaces requested per page'),
    /**
     * Number of namespaces skipped
     */
    offset: z.number().describe('Number of namespaces skipped'),
    /**
     * Whether there are more namespaces available
     */
    hasMore: z.boolean().describe('Whether there are more namespaces available'),
});
const VectorStorageNameRequiredError = StructuredError('VectorStorageNameRequiredError', 'Vector storage name is required and must be a non-empty string');
const VectorStorageKeyRequiredError = StructuredError('VectorStorageKeyRequiredError', 'Vector storage key is required and must be a non-empty string');
const VectorStorageKeysError = StructuredError('VectorStorageKeysError', 'Vector storage requires all keys to be non-empty strings');
const VectorStorageQueryError = StructuredError('VectorStorageQueryError', 'Vector storage query property is required and must be a non-empty string');
const VectorStorageLimitError = StructuredError('VectorStorageLimitError', 'Vector storage limit property must be positive number');
const VectorStorageSimilarityError = StructuredError('VectorStorageSimilarityError', 'Vector storage similarity property must be a number between 0.0 and 1.0');
const VectorStorageMetadataError = StructuredError('VectorStorageMetadataError', 'Vector storage metadata property must be a valid object');
const VectorStorageDocumentRequiredError = StructuredError('VectorStorageDocumentRequiredError', 'Vector storage requires at least one document for this method');
const VectorStorageDocumentKeyMissingError = StructuredError('VectorStorageDocumentKeyMissingError', 'Vector storage requires each document to have a non-empty key');
const VectorStorageInvalidError = StructuredError('VectorStorageInvalidError', 'Vector storage requires each document to have either embeddings or document property');
const VectorStorageEmbeddingInvalidError = StructuredError('VectorStorageEmbeddingInvalidError', 'Vector storage requires each embeddings property to have a non-empty array of numbers');
const VectorStorageDocumentInvalidError = StructuredError('VectorStorageDocumentInvalidError', 'Vector storage requires each document property to have a non-empty string value');
const VectorStorageResponseError = StructuredError('VectorStorageResponseError')();
export class VectorStorageService {
    #adapter;
    #baseUrl;
    constructor(baseUrl, adapter) {
        this.#adapter = adapter;
        this.#baseUrl = baseUrl;
    }
    async upsert(name, ...documents) {
        if (!name || typeof name !== 'string' || name.trim().length === 0) {
            throw new VectorStorageNameRequiredError();
        }
        if (!documents || documents.length === 0) {
            throw new VectorStorageDocumentRequiredError();
        }
        for (const doc of documents) {
            if (!doc.key || typeof doc.key !== 'string' || doc.key.trim().length === 0) {
                throw new VectorStorageDocumentKeyMissingError();
            }
            if (!('embeddings' in doc) && !('document' in doc)) {
                throw new VectorStorageInvalidError();
            }
            if ('embeddings' in doc && doc.embeddings) {
                if (!Array.isArray(doc.embeddings) || doc.embeddings.length === 0) {
                    throw new VectorStorageEmbeddingInvalidError();
                }
            }
            if ('document' in doc && doc.document) {
                if (typeof doc.document !== 'string' || doc.document.trim().length === 0) {
                    throw new VectorStorageDocumentInvalidError();
                }
            }
        }
        const url = buildUrl(this.#baseUrl, `/vector/${encodeURIComponent(name)}`);
        const signal = AbortSignal.timeout(30_000);
        // Transform documents to handle TTL: null → 0 for "no expiration"
        const requestDocs = documents.map((doc) => ({
            ...doc,
            // TTL handling: only include if explicitly provided
            // null or 0 = no expiration (send 0 to server), positive = TTL in seconds
            // undefined = not sent, server uses default (30 days)
            ...(doc.ttl !== undefined && { ttl: doc.ttl === null ? 0 : doc.ttl }),
        }));
        const res = await this.#adapter.invoke(url, {
            method: 'PUT',
            body: safeStringify(requestDocs),
            contentType: 'application/json',
            signal,
            telemetry: {
                name: 'agentuity.vector.upsert',
                attributes: {
                    name,
                    count: String(documents.length),
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                return res.data.data.map((o, index) => {
                    const doc = documents[index];
                    return {
                        key: doc ? doc.key : '',
                        id: o.id,
                    };
                });
            }
            throw new VectorStorageResponseError({
                status: res.response.status,
                message: res.data.message,
            });
        }
        throw await toServiceException('PUT', url, res.response);
    }
    async get(name, key) {
        if (!name || typeof name !== 'string' || name.trim().length === 0) {
            throw new VectorStorageNameRequiredError();
        }
        if (!key || typeof key !== 'string' || key.trim().length === 0) {
            throw new VectorStorageKeyRequiredError();
        }
        const url = buildUrl(this.#baseUrl, `/vector/${encodeURIComponent(name)}/${encodeURIComponent(key)}`);
        const signal = AbortSignal.timeout(30_000); // 30s timeout for Neon cold starts
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.vector.get',
                attributes: {
                    name,
                    key,
                },
            },
        });
        if (res.response.status === 404) {
            return { exists: false };
        }
        if (res.ok) {
            if (res.data.success) {
                return {
                    data: res.data.data,
                    exists: true,
                };
            }
            throw new VectorStorageResponseError({
                status: res.response.status,
                message: res.data.message,
            });
        }
        throw await toServiceException('GET', url, res.response);
    }
    async getMany(name, ...keys) {
        if (!name || typeof name !== 'string' || name.trim().length === 0) {
            throw new VectorStorageNameRequiredError();
        }
        if (keys.length === 0) {
            return new Map();
        }
        for (const key of keys) {
            if (!key || typeof key !== 'string' || key.trim().length === 0) {
                throw new VectorStorageKeysError();
            }
        }
        const results = await Promise.all(keys.map((key) => this.get(name, key)));
        const resultMap = new Map();
        results.forEach((result, index) => {
            const key = keys[index];
            if (result.exists && key) {
                resultMap.set(key, result.data);
            }
        });
        return resultMap;
    }
    async search(name, params) {
        if (!name || typeof name !== 'string' || name.trim().length === 0) {
            throw new VectorStorageNameRequiredError();
        }
        if (!params.query || typeof params.query !== 'string' || params.query.trim().length === 0) {
            throw new VectorStorageQueryError();
        }
        if (params.limit !== undefined) {
            if (typeof params.limit !== 'number' || params.limit <= 0) {
                throw new VectorStorageLimitError();
            }
        }
        if (params.similarity !== undefined) {
            if (typeof params.similarity !== 'number' ||
                params.similarity < 0 ||
                params.similarity > 1) {
                throw new VectorStorageSimilarityError();
            }
        }
        if (params.metadata !== undefined) {
            if (typeof params.metadata !== 'object' || params.metadata === null) {
                throw new VectorStorageMetadataError();
            }
        }
        const url = buildUrl(this.#baseUrl, `/vector/search/${encodeURIComponent(name)}`);
        const signal = AbortSignal.timeout(30_000);
        const attributes = {
            name,
            query: params.query,
        };
        if (params.limit !== undefined) {
            attributes.limit = String(params.limit);
        }
        if (params.similarity !== undefined) {
            attributes.similarity = String(params.similarity);
        }
        const res = await this.#adapter.invoke(url, {
            method: 'POST',
            body: safeStringify(params),
            contentType: 'application/json',
            signal,
            telemetry: {
                name: 'agentuity.vector.search',
                attributes,
            },
        });
        if (res.response.status === 404) {
            return [];
        }
        if (res.ok) {
            if (res.data.success) {
                return res.data.data;
            }
            throw new VectorStorageResponseError({
                status: res.response.status,
                message: res.data.message,
            });
        }
        throw await toServiceException('POST', url, res.response);
    }
    async delete(name, ...keys) {
        if (!name || typeof name !== 'string' || name.trim().length === 0) {
            throw new VectorStorageNameRequiredError();
        }
        if (keys.length === 0) {
            return 0;
        }
        for (const key of keys) {
            if (!key || typeof key !== 'string' || key.trim().length === 0) {
                throw new VectorStorageKeysError();
            }
        }
        const signal = AbortSignal.timeout(30_000);
        let url;
        let body;
        const firstKey = keys[0];
        if (keys.length === 1 && firstKey) {
            url = buildUrl(this.#baseUrl, `/vector/${encodeURIComponent(name)}/${encodeURIComponent(firstKey)}`);
        }
        else {
            url = buildUrl(this.#baseUrl, `/vector/${encodeURIComponent(name)}`);
            body = safeStringify({ keys });
        }
        const res = await this.#adapter.invoke(url, {
            method: 'DELETE',
            ...(body && { body, contentType: 'application/json' }),
            signal,
            telemetry: {
                name: 'agentuity.vector.delete',
                attributes: {
                    name,
                    count: String(keys.length),
                },
            },
        });
        // Treat missing vectors as non-fatal for idempotency, like get()/search()
        if (res.response.status === 404) {
            return 0;
        }
        if (res.ok) {
            if (res.data.success) {
                return res.data.data;
            }
            throw new VectorStorageResponseError({
                status: res.response.status,
                message: res.data.message,
            });
        }
        throw await toServiceException('DELETE', url, res.response);
    }
    async exists(name) {
        if (!name || typeof name !== 'string' || name.trim().length === 0) {
            throw new VectorStorageNameRequiredError();
        }
        const stats = await this.getStats(name);
        return stats.count > 0;
    }
    async getStats(name) {
        if (!name || typeof name !== 'string' || name.trim().length === 0) {
            throw new VectorStorageNameRequiredError();
        }
        const url = buildUrl(this.#baseUrl, `/vector/stats/${encodeURIComponent(name)}`);
        const signal = AbortSignal.timeout(30_000); // 30s timeout for Neon cold starts
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.vector.getStats',
                attributes: {
                    name,
                },
            },
        });
        if (res.ok) {
            return res.data;
        }
        if (res.response.status === 404) {
            return { sum: 0, count: 0 };
        }
        throw await toServiceException('GET', url, res.response);
    }
    async getAllStats(params) {
        const queryParams = new URLSearchParams();
        if (params?.limit !== undefined) {
            queryParams.set('limit', String(params.limit));
        }
        if (params?.offset !== undefined) {
            queryParams.set('offset', String(params.offset));
        }
        if (params?.sort) {
            queryParams.set('sort', params.sort);
        }
        if (params?.direction) {
            queryParams.set('direction', params.direction);
        }
        if (params?.name) {
            queryParams.set('name', params.name);
        }
        const queryString = queryParams.toString();
        const url = buildUrl(this.#baseUrl, `/vector/stats${queryString ? `?${queryString}` : ''}`);
        const signal = AbortSignal.timeout(30_000); // 30s timeout for Neon cold starts
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.vector.getAllStats',
                attributes: {},
            },
        });
        if (res.ok) {
            return res.data;
        }
        throw await toServiceException('GET', url, res.response);
    }
    async getNamespaces() {
        const url = buildUrl(this.#baseUrl, '/vector/namespaces');
        const signal = AbortSignal.timeout(30_000); // 30s timeout for Neon cold starts
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.vector.getNamespaces',
                attributes: {},
            },
        });
        if (res.ok) {
            return res.data;
        }
        throw await toServiceException('GET', url, res.response);
    }
    async deleteNamespace(name) {
        if (!name || typeof name !== 'string' || name.trim().length === 0) {
            throw new VectorStorageNameRequiredError();
        }
        const url = buildUrl(this.#baseUrl, `/vector/${encodeURIComponent(name)}`);
        const signal = AbortSignal.timeout(30_000);
        const res = await this.#adapter.invoke(url, {
            method: 'DELETE',
            signal,
            telemetry: {
                name: 'agentuity.vector.deleteNamespace',
                attributes: {
                    name,
                },
            },
        });
        if (res.response.status === 404) {
            return;
        }
        if (res.ok) {
            if (res.data.success) {
                return;
            }
            throw new VectorStorageResponseError({
                status: res.response.status,
                message: res.data.message,
            });
        }
        throw await toServiceException('DELETE', url, res.response);
    }
}
//# sourceMappingURL=service.js.map