# coding: utf-8

"""
    Mixpeek API

    This is the Mixpeek API, providing access to various endpoints for data processing and retrieval.

    The version of the OpenAPI document: 1.3.0
    Contact: info@mixpeek.com
    Generated by OpenAPI Generator (https://openapi-generator.tech)

    Do not edit the class manually.
"""  # noqa: E501


from __future__ import annotations
import pprint
import re  # noqa: F401
import json

from pydantic import BaseModel, ConfigDict, Field, StrictStr
from typing import Any, ClassVar, Dict, List, Optional, Union
from typing_extensions import Annotated
from mixpeek.models.on_empty_behavior import OnEmptyBehavior
from mixpeek.models.query import Query
from typing import Optional, Set
from typing_extensions import Self

class StageTypesFeatureSearchConfig(BaseModel):
    """
    Configuration for a single feature search within the feature_filter stage.  Each feature search specifies: - Which feature URI to search (embedding index) - What input to search with (text, URL, or base64) - Search parameters (top_k, score threshold) - Optional weight for fusion  Multiple feature searches are combined using the stage's fusion strategy.  Examples:     Text semantic search:         ```json         {             \"feature_uri\": \"mixpeek://text_extractor@v1/multilingual_e5_large_instruct_v1\",             \"query\": {\"input_mode\": \"text\", \"value\": \"Hello world!\"},             \"top_k\": 100         }         ```      Image visual search with URL (auto-detected):         ```json         {             \"feature_uri\": \"mixpeek://clip_extractor@v1/image_embedding\",             \"query\": {\"input_mode\": \"content\", \"value\": \"{{INPUT.image_url}}\"},             \"top_k\": 50         }         ```      Multimodal search with template and weight:         ```json         {             \"feature_uri\": \"mixpeek://multimodal_extractor@v1/embedding\",             \"query\": {\"input_mode\": \"text\", \"value\": \"{{INPUT.query}}\"},             \"top_k\": 100,             \"weight\": 0.7         }         ```
    """ # noqa: E501
    feature_uri: Annotated[str, Field(min_length=1, strict=True)] = Field(description="REQUIRED. Feature URI specifying which embedding index to search. Format: 'mixpeek://extractor@version/output' or 'namespace://collection/feature'. Must reference a valid dense vector index in the collection. The feature must exist and be indexed for all documents in the collection.")
    query: Query
    top_k: Optional[Annotated[int, Field(le=1000, strict=True, ge=1)]] = Field(default=100, description="OPTIONAL. Number of results to fetch for this specific feature search. Defaults to 100. This is the per-feature top_k, independent of the final_top_k parameter. Higher values: More comprehensive but slower. Lower values: Faster but may miss relevant results. Qdrant will fetch this many results before fusion.")
    min_score: Optional[Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]]] = Field(default=None, description="OPTIONAL. Minimum similarity score threshold for this feature search. NOT REQUIRED - if not specified, no score filtering is applied. Filters out results below this threshold BEFORE fusion. Typical values: 0.5-0.8 depending on model and use case. Lower threshold: More recall, less precision. Higher threshold: More precision, less recall.")
    weight: Optional[Union[Annotated[float, Field(le=1.0, strict=True, ge=0.0)], Annotated[int, Field(le=1, strict=True, ge=0)]]] = Field(default=1.0, description="OPTIONAL. Weight for this feature search when using 'weighted' fusion. Defaults to 1.0 (equal weight). Ignored for 'rrf' and 'max' fusion strategies. Sum of all feature weights should typically equal 1.0 for normalized scores. Higher weight: More influence on final ranking. Example: Text=0.7, Image=0.3 for text-heavy search.")
    on_empty: Optional[OnEmptyBehavior] = Field(default=None, description="OPTIONAL. Behavior when input is empty after template resolution. Defaults to 'error' (fail if input missing).   ┌─────────┬────────────────────────────────────────────────────────────┐ │ Value   │ Behavior                                                   │ ├─────────┼────────────────────────────────────────────────────────────┤ │ error   │ Fail with error (input is required) - DEFAULT              │ │ skip    │ Exclude from fusion (let other searches drive results)     │ │ random  │ Use random vector (always return results)                  │ └─────────┴────────────────────────────────────────────────────────────┘   'error' (default): Strict mode - fail fast if input is missing. Use when input is required and missing input indicates a bug.   'skip': Graceful degradation - exclude this search from fusion. Use for multi-modal search where user may provide text OR image OR both. If all searches skip (all inputs empty), returns error.   'random': Always return results - use random vector as fallback. Use for single-feature optional search where you always want results.")
    collection_identifiers: Optional[List[StrictStr]] = Field(default=None, description="OPTIONAL. Collection identifiers to search for this specific feature search. Can be collection IDs or names. Enables per-search collection targeting for hybrid/multi-feature searches.   Fallback Priority (most specific wins): 1. This field (per-search targeting) - most specific 2. Stage-level collection_identifiers 3. Retriever-level collection_identifiers   Use Cases: - Hybrid search where different features exist in different collections - Text embeddings in col_products, image embeddings in col_media - Fine-grained collection targeting per feature URI   Note: All collections (across all searches) must be declared in the retriever's collection_identifiers at creation time for validation.")
    __properties: ClassVar[List[str]] = ["feature_uri", "query", "top_k", "min_score", "weight", "on_empty", "collection_identifiers"]

    model_config = ConfigDict(
        populate_by_name=True,
        validate_assignment=True,
        protected_namespaces=(),
    )


    def to_str(self) -> str:
        """Returns the string representation of the model using alias"""
        return pprint.pformat(self.model_dump(by_alias=True))

    def to_json(self) -> str:
        """Returns the JSON representation of the model using alias"""
        # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
        return json.dumps(self.to_dict())

    @classmethod
    def from_json(cls, json_str: str) -> Optional[Self]:
        """Create an instance of StageTypesFeatureSearchConfig from a JSON string"""
        return cls.from_dict(json.loads(json_str))

    def to_dict(self) -> Dict[str, Any]:
        """Return the dictionary representation of the model using alias.

        This has the following differences from calling pydantic's
        `self.model_dump(by_alias=True)`:

        * `None` is only added to the output dict for nullable fields that
          were set at model initialization. Other fields with value `None`
          are ignored.
        """
        excluded_fields: Set[str] = set([
        ])

        _dict = self.model_dump(
            by_alias=True,
            exclude=excluded_fields,
            exclude_none=True,
        )
        # override the default output from pydantic by calling `to_dict()` of query
        if self.query:
            _dict['query'] = self.query.to_dict()
        return _dict

    @classmethod
    def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
        """Create an instance of StageTypesFeatureSearchConfig from a dict"""
        if obj is None:
            return None

        if not isinstance(obj, dict):
            return cls.model_validate(obj)

        _obj = cls.model_validate({
            "feature_uri": obj.get("feature_uri"),
            "query": Query.from_dict(obj["query"]) if obj.get("query") is not None else None,
            "top_k": obj.get("top_k") if obj.get("top_k") is not None else 100,
            "min_score": obj.get("min_score"),
            "weight": obj.get("weight") if obj.get("weight") is not None else 1.0,
            "on_empty": obj.get("on_empty"),
            "collection_identifiers": obj.get("collection_identifiers")
        })
        return _obj


