# 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, StrictBool, StrictStr
from typing import Any, ClassVar, Dict, List, Optional
from typing_extensions import Annotated
from mixpeek.models.logical_operator import LogicalOperator
from mixpeek.models.stage_cache_behavior import StageCacheBehavior
from typing import Optional, Set
from typing_extensions import Self

class StageParamsDocumentEnrich(BaseModel):
    """
    Configuration for enriching documents with data from another collection.  **Stage Category**: APPLY (1-1 Inner Join/Enrichment)  **Transformation**: N documents → N documents (same count, expanded schema)  **Purpose**: Applies each input document to a lookup operation in another collection, merging matching data back. This performs JOIN-like operations similar to SQL INNER JOIN. Each input document produces exactly one output document with added fields.  **When to Use**:     - After FILTER/SORT to add related reference data     - To combine data from multiple collections (e.g., products + catalog info)     - When documents need contextual information from other sources     - For denormalizing data at query time instead of storage time     - To attach user profiles, metadata, or related entities  **When NOT to Use**:     - For initial document retrieval (use FILTER stages: hybrid_search)     - For removing documents (use FILTER stages)     - For reordering results (use SORT stages)     - When the target collection is very large (performance impact)     - For 1-N joins that expand document count (use taxonomy with multi-match)  **Operational Behavior**:     - Applies each input document to a collection lookup (1-1 operation)     - Performs database lookups for each document (MongoDB queries)     - Maintains document count: N in → N out     - Expands schema: adds fields from target collection     - Moderate performance (depends on target collection size and indexes)     - Left join semantics: missing matches result in null/absent fields  **Common Pipeline Position**: FILTER → SORT → APPLY (this stage)  **Join Operation**: This is a LEFT JOIN - all source documents are kept, enrichment fields are added when matches are found. Missing matches result in null/absent fields rather than document removal.  Requirements:     - target_collection_id: REQUIRED, collection to join with     - source_field: REQUIRED, field in current documents to match     - target_field: REQUIRED, field in target collection to match against     - fields_to_merge: OPTIONAL, specific fields to merge (or entire document)     - output_field: OPTIONAL, where to place enrichment (root or nested path)  Use Cases:     - Enrich product search results with full catalog data     - Add user profile information to activity logs     - Join cluster assignments with detailed metadata     - Attach reference data (categories, taxonomies) to documents     - Combine fragmented data across collections  Examples:     Basic field-based join:         ```json         {             \"target_collection_id\": \"col_products\",             \"source_field\": \"metadata.product_id\",             \"target_field\": \"product_id\",             \"fields_to_merge\": [\"name\", \"price\", \"category\"]         }         ```      Nested field join with custom output:         ```json         {             \"target_collection_id\": \"col_users\",             \"source_field\": \"lineage.source_object_id\",             \"target_field\": \"user_id\",             \"output_field\": \"enrichments.user_profile\",             \"fields_to_merge\": [\"name\", \"email\", \"role\"]         }         ```      Conditional enrichment (only for specific categories):         ```json         {             \"target_collection_id\": \"col_catalog\",             \"source_field\": \"metadata.sku\",             \"target_field\": \"sku\",             \"fields_to_merge\": [\"description\", \"specs\"],             \"when\": {                 \"field\": \"metadata.category\",                 \"operator\": \"eq\",                 \"value\": \"electronics\"             }         }         ```
    """ # noqa: E501
    cache_behavior: Optional[StageCacheBehavior] = Field(default=None, description="Controls internal caching behavior for this stage. OPTIONAL - defaults to 'auto' for transparent performance.   'auto' (default): Automatic caching for deterministic operations. Stage intelligently caches results based on inputs and parameters. Use for transformations, parsing, formatting, stable API calls. Cache invalidates automatically when parameters change. Recommended for 95% of use cases.   'disabled': Skip all internal caching. Every execution runs fresh without cache lookup. Use for templates with now(), random(), or external APIs that must be called every time (real-time data). No performance benefit but guarantees fresh execution.   'aggressive': Cache even non-deterministic operations. Use ONLY when you fully understand caching implications. May cache time-sensitive or random data. Generally not recommended - prefer 'auto' or 'disabled'.   Note: This controls internal stage caching. Retriever-level caching (cache_config.cache_stage_names) is separate and caches complete stage outputs.")
    cache_ttl_seconds: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(default=None, description="Time-to-live for cache entries in seconds. OPTIONAL - defaults to None (LRU eviction only).   When None (default, recommended): Cache uses Redis LRU eviction policy. Most frequently used items stay cached automatically. No manual TTL management needed. Memory bounded by Redis maxmemory setting.   When specified: Cache entries expire after this duration regardless of usage. Useful for data that becomes stale after specific time periods. Lower values for frequently changing external data. Higher values for stable transformations.   Examples: - None: LRU-based eviction (recommended for most cases) - 300: 5 minutes (for semi-static external data) - 3600: 1 hour (for stable transformations) - 86400: 24 hours (for rarely changing operations)   Performance Note: TTL adds minimal overhead (<1ms) but forces eviction even for frequently accessed items. Use None unless you have specific staleness requirements.")
    retriever_id: Optional[StrictStr] = Field(default='null', description="ID of an existing retriever to use for finding enrichment data. When provided, uses the full retriever pipeline (semantic search, filters, etc.) instead of simple field matching. Mutually exclusive with retriever_config.")
    retriever_config: Optional[Dict[str, Any]] = Field(default=None, description="Anonymous retriever definition for finding enrichment data. Allows defining a custom retriever inline without creating it separately. Mutually exclusive with retriever_id. Must have 'stages' array with at least one stage.")
    retriever_inputs: Optional[Dict[str, Any]] = Field(default=None, description="Template mapping from source document fields to retriever inputs. Supports template syntax: {{DOC.field_name}} to reference source document fields. Used when retriever_id or retriever_config is specified.")
    target_collection_id: Optional[StrictStr] = Field(default='{{COLLECTION_ID}}', description="Collection ID to fetch enrichment data from. REQUIRED for direct joins (when retriever_id/retriever_config not provided). Also used to scope retriever queries when retriever-based join is used. NOTE: You must replace the default placeholder with your actual collection ID.")
    source_field: Optional[StrictStr] = Field(default='source_object_id', description="Dot-path to field in current document to match on. REQUIRED for direct joins (when retriever_id/retriever_config not provided). For retriever-based joins, use retriever_inputs instead.")
    target_field: Optional[StrictStr] = Field(default='document_id', description="Field in target collection to match against. REQUIRED for direct joins (when retriever_id/retriever_config not provided). Ignored for retriever-based joins.")
    fields_to_merge: Optional[List[StrictStr]] = Field(default=None, description="Specific fields from target document to merge. If None, merges entire document. Supports dot-notation for nested fields.")
    output_field: Optional[StrictStr] = Field(default='null', description="Dot-path where enrichment data should be placed. If None, merges directly into document root. Use 'enrichments.{name}' to namespace enrichments.")
    strategy: Optional[StrictStr] = Field(default='enrich', description="How to handle the merge: 'enrich' = add fields to existing document, 'replace' = replace document with enriched version, 'append' = add as array item")
    when: Optional[LogicalOperator] = Field(default=None, description="Conditional filter to determine which documents should be enriched. Documents not matching the condition pass through unchanged.")
    allow_missing: Optional[StrictBool] = Field(default=True, description="If True, documents without matching enrichment data pass through unchanged. If False, documents without matches are filtered out.")
    __properties: ClassVar[List[str]] = ["cache_behavior", "cache_ttl_seconds", "retriever_id", "retriever_config", "retriever_inputs", "target_collection_id", "source_field", "target_field", "fields_to_merge", "output_field", "strategy", "when", "allow_missing"]

    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 StageParamsDocumentEnrich 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 when
        if self.when:
            _dict['when'] = self.when.to_dict()
        return _dict

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

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

        _obj = cls.model_validate({
            "cache_behavior": obj.get("cache_behavior"),
            "cache_ttl_seconds": obj.get("cache_ttl_seconds"),
            "retriever_id": obj.get("retriever_id") if obj.get("retriever_id") is not None else 'null',
            "retriever_config": obj.get("retriever_config"),
            "retriever_inputs": obj.get("retriever_inputs"),
            "target_collection_id": obj.get("target_collection_id") if obj.get("target_collection_id") is not None else '{{COLLECTION_ID}}',
            "source_field": obj.get("source_field") if obj.get("source_field") is not None else 'source_object_id',
            "target_field": obj.get("target_field") if obj.get("target_field") is not None else 'document_id',
            "fields_to_merge": obj.get("fields_to_merge"),
            "output_field": obj.get("output_field") if obj.get("output_field") is not None else 'null',
            "strategy": obj.get("strategy") if obj.get("strategy") is not None else 'enrich',
            "when": LogicalOperator.from_dict(obj["when"]) if obj.get("when") is not None else None,
            "allow_missing": obj.get("allow_missing") if obj.get("allow_missing") is not None else True
        })
        return _obj


