"""Adjudication schemas for Intelligence Pipeline v3.

Defines Pydantic models for the adjudication layer:
- AdjudicationCandidate: a proposed entity/fact/event requiring adjudication
- ConflictDescription: describes a conflict between candidates
- AdjudicationQuestion: a specific question the adjudicator must resolve
- EvidencePacket: evidence spans provided to the adjudicator
- AdjudicationDecision: the adjudicator's resolution (excludes confidence,
  novelty, impact, and horizon — those come from calibrated pipelines)

Every decision requires evidence_ids linking back to packet evidence.
"""

from __future__ import annotations

from enum import Enum
from typing import Any

from pydantic import BaseModel, Field


class CandidateType(str, Enum):
    """Type of candidate being adjudicated."""

    ENTITY = "entity"
    EVENT = "event"
    FACT = "fact"
    RELATION = "relation"
    SENTIMENT = "sentiment"


class AdjudicationCandidate(BaseModel):
    """A proposed extraction candidate that requires adjudication.

    Represents an entity, event, fact, relation, or sentiment that the
    fast-path could not resolve with sufficient confidence.
    """

    candidate_id: str = Field(description="Unique identifier for this candidate")
    candidate_type: CandidateType = Field(description="Type of candidate")
    label: str = Field(description="Human-readable label or description")
    source_chunk_ids: list[str] = Field(
        default_factory=list,
        description="Chunk IDs where this candidate was found",
    )
    evidence_ids: list[str] = Field(
        default_factory=list,
        description="Evidence span IDs supporting this candidate",
    )
    metadata: dict[str, Any] = Field(
        default_factory=dict,
        description="Additional type-specific metadata",
    )
    score: float = Field(
        default=0.0,
        ge=0.0,
        le=1.0,
        description="Specialist extraction score (0.0-1.0)",
    )


class ConflictType(str, Enum):
    """Type of conflict between candidates."""

    CONTRADICTORY_VALUES = "contradictory_values"
    AMBIGUOUS_IDENTITY = "ambiguous_identity"
    OPPOSING_SENTIMENT = "opposing_sentiment"
    OVERLAPPING_EVENTS = "overlapping_events"
    CAUSAL_AMBIGUITY = "causal_ambiguity"


class ConflictDescription(BaseModel):
    """Describes a conflict between two or more candidates.

    Used to inform the adjudicator about what needs resolution.
    """

    conflict_id: str = Field(description="Unique identifier for this conflict")
    conflict_type: ConflictType = Field(description="Type of conflict")
    candidate_ids: list[str] = Field(
        min_length=2,
        description="IDs of conflicting candidates",
    )
    description: str = Field(description="Human-readable conflict description")
    evidence_ids: list[str] = Field(
        default_factory=list,
        description="Evidence IDs relevant to this conflict",
    )


class QuestionCode(str, Enum):
    """Codes representing specific adjudication questions."""

    RESOLVE_ENTITY_IDENTITY = "RESOLVE_ENTITY_IDENTITY"
    RESOLVE_EVENT_TYPE = "RESOLVE_EVENT_TYPE"
    RESOLVE_CAUSAL_DIRECTION = "RESOLVE_CAUSAL_DIRECTION"
    RESOLVE_NUMERIC_CONFLICT = "RESOLVE_NUMERIC_CONFLICT"
    RESOLVE_SENTIMENT_DIRECTION = "RESOLVE_SENTIMENT_DIRECTION"
    RESOLVE_TEMPORAL_ORDERING = "RESOLVE_TEMPORAL_ORDERING"
    RESOLVE_COMPANY_ATTRIBUTION = "RESOLVE_COMPANY_ATTRIBUTION"
    CONFIRM_CROSS_CHUNK_RELATION = "CONFIRM_CROSS_CHUNK_RELATION"


class AdjudicationQuestion(BaseModel):
    """A specific question the adjudicator must answer.

    Each question references candidates and conflicts that need resolution.
    """

    question_code: QuestionCode = Field(description="Structured question code")
    description: str = Field(description="Natural language question for the adjudicator")
    candidate_ids: list[str] = Field(
        default_factory=list,
        description="Candidate IDs this question applies to",
    )
    conflict_ids: list[str] = Field(
        default_factory=list,
        description="Conflict IDs this question resolves",
    )


class EvidencePacket(BaseModel):
    """Evidence spans provided to the adjudicator.

    Contains the exact text and location of evidence the adjudicator
    can reference in its decisions.
    """

    evidence_id: str = Field(description="Unique identifier for this evidence span")
    chunk_id: str = Field(description="Source chunk identifier")
    start_char: int = Field(ge=0, description="Start character offset within chunk")
    end_char: int = Field(gt=0, description="End character offset within chunk")
    text: str = Field(min_length=1, description="Evidence text content")
    source_document_id: str = Field(description="Parent document identifier")


class DecisionVerdict(str, Enum):
    """Possible verdicts for an adjudication decision."""

    ACCEPT = "accept"
    REJECT = "reject"
    MERGE = "merge"
    SPLIT = "split"
    REATTRIBUTE = "reattribute"


class AdjudicationDecision(BaseModel):
    """The adjudicator's resolution for one or more candidates.

    IMPORTANT: This model intentionally EXCLUDES:
    - authoritative confidence (comes from calibration pipeline)
    - novelty (comes from retrieval-based novelty stage)
    - impact (comes from stock-specific impact model)
    - horizon (comes from impact model)

    The adjudicator resolves candidate identity, relationships, event
    interpretation, and supported qualitative direction only. Every
    decision MUST reference evidence_ids from the provided packet.
    """

    decision_id: str = Field(description="Unique identifier for this decision")
    question_code: QuestionCode = Field(description="Which question this resolves")
    verdict: DecisionVerdict = Field(description="The adjudication verdict")
    candidate_ids: list[str] = Field(
        min_length=1,
        description="Candidate IDs this decision applies to",
    )
    evidence_ids: list[str] = Field(
        min_length=1,
        description="Evidence IDs supporting this decision (required, non-empty)",
    )
    reasoning: str = Field(
        description="Brief reasoning for the decision",
    )
    resolved_value: dict[str, Any] = Field(
        default_factory=dict,
        description="The resolved value(s) if applicable",
    )
