# generated by datamodel-codegen:
#   filename:  core/optimization_goal.json
#   timestamp: 2026-02-28T17:39:50+00:00

from __future__ import annotations

from enum import Enum
from typing import Annotated, Literal

from adcp.types.base import AdCPBaseModel
from pydantic import ConfigDict, Field, RootModel

from ..enums import event_type as event_type_1
from ..enums import reach_unit as reach_unit_1
from . import duration


class Metric(Enum):
    clicks = 'clicks'
    views = 'views'
    completed_views = 'completed_views'
    viewed_seconds = 'viewed_seconds'
    attention_seconds = 'attention_seconds'
    attention_score = 'attention_score'
    engagements = 'engagements'
    follows = 'follows'
    saves = 'saves'
    profile_visits = 'profile_visits'
    reach = 'reach'


class Target(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    kind: Literal['cost_per']
    value: Annotated[
        float, Field(description='Target cost per metric unit in the buy currency', gt=0.0)
    ]


class Target1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    kind: Literal['threshold_rate']
    value: Annotated[
        float,
        Field(
            description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views), seconds (viewed_seconds, attention_seconds), or score (attention_score).',
            gt=0.0,
        ),
    ]


class TargetFrequency(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    max: Annotated[
        int | None,
        Field(
            description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.',
            ge=1,
        ),
    ] = None
    min: Annotated[
        int,
        Field(
            description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).',
            ge=1,
        ),
    ]
    window: Annotated[
        duration.Duration,
        Field(
            description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.'
        ),
    ]


class TargetFrequency1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    max: Annotated[
        int,
        Field(
            description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.',
            ge=1,
        ),
    ]
    min: Annotated[
        int | None,
        Field(
            description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).',
            ge=1,
        ),
    ] = None
    window: Annotated[
        duration.Duration,
        Field(
            description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.'
        ),
    ]


class AttributionWindow(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    post_click: Annotated[
        duration.Duration,
        Field(
            description='Post-click attribution window. Conversions within this duration after a click are attributed to the ad (e.g. {"interval": 7, "unit": "days"}).'
        ),
    ]
    post_view: Annotated[
        duration.Duration | None,
        Field(
            description='Post-view attribution window. Conversions within this duration after an ad impression (without click) are attributed to the ad (e.g. {"interval": 1, "unit": "days"}).'
        ),
    ] = None


class EventSource(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    custom_event_name: Annotated[
        str | None,
        Field(
            description="Required when event_type is 'custom'. Platform-specific name for the custom event."
        ),
    ] = None
    event_source_id: Annotated[
        str,
        Field(
            description='Event source to include (must be configured on this account via sync_event_sources)',
            min_length=1,
        ),
    ]
    event_type: Annotated[
        event_type_1.EventType,
        Field(
            description='Event type to include from this source (e.g., purchase, lead, app_install, refund)'
        ),
    ]
    value_factor: Annotated[
        float | None,
        Field(
            description="Multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics."
        ),
    ] = 1
    value_field: Annotated[
        str | None,
        Field(
            description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value'. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism."
        ),
    ] = None


class Target2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    kind: Literal['cost_per']
    value: Annotated[float, Field(description='Target cost per event in the buy currency', gt=0.0)]


class Target3(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    kind: Literal['per_ad_spend']
    value: Annotated[
        float,
        Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0),
    ]


class Target4(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    kind: Literal['maximize_value']


class OptimizationGoal2(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    attribution_window: Annotated[
        AttributionWindow | None,
        Field(
            description="Attribution window for this optimization goal. Values must match an option declared in the seller's conversion_tracking.attribution_windows capability. Sellers must reject windows not in their declared capabilities. When omitted, the seller uses their default window."
        ),
    ] = None
    event_sources: Annotated[
        list[EventSource],
        Field(
            description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.',
            min_length=1,
        ),
    ]
    kind: Literal['event']
    priority: Annotated[
        int | None,
        Field(
            description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.',
            ge=1,
        ),
    ] = None
    target: Annotated[
        Target2 | Target3 | Target4 | None,
        Field(
            description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversions within budget.'
        ),
    ] = None


class OptimizationGoal1(AdCPBaseModel):
    model_config = ConfigDict(
        extra='allow',
    )
    kind: Literal['metric']
    metric: Annotated[
        Metric,
        Field(
            description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression), attention_seconds (attention time per impression), attention_score (vendor-specific attention score). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel subscribes), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks')."
        ),
    ]
    priority: Annotated[
        int | None,
        Field(
            description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.',
            ge=1,
        ),
    ] = None
    reach_unit: Annotated[
        reach_unit_1.ReachUnit | None,
        Field(
            description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units."
        ),
    ] = None
    target: Annotated[
        Target | Target1 | None,
        Field(
            description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.'
        ),
    ] = None
    target_frequency: Annotated[
        TargetFrequency | TargetFrequency1 | None,
        Field(
            description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed."
        ),
    ] = None
    view_duration_seconds: Annotated[
        float | None,
        Field(
            description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.",
            gt=0.0,
        ),
    ] = None


class OptimizationGoal(RootModel[OptimizationGoal1 | OptimizationGoal2]):
    root: Annotated[
        OptimizationGoal1 | OptimizationGoal2,
        Field(
            description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. Duplicate priority values result in undefined seller behavior.',
            title='Optimization Goal',
        ),
    ]
