# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""
Workflow execution models for action definitions, AI responses, and workflow-level structures.
"""

from typing import Dict, Any, List, Optional, TYPE_CHECKING
from pydantic import BaseModel, Field
from modules.shared.attributeUtils import registerModelLabels
from modules.shared.jsonUtils import extractJsonString, tryParseJson, repairBrokenJson

# Import DocumentReferenceList at runtime (needed for ActionDefinition)
from modules.datamodels.datamodelDocref import DocumentReferenceList

class ActionDefinition(BaseModel):
    """Action definition with selection and parameters from planning phase"""
    
    # Core action selection (Stage 1)
    action: str = Field(description="Compound action name (method.action)")
    actionObjective: str = Field(description="Objective for this action")
    userMessage: Optional[str] = Field(
        None,
        description="User-friendly message in user's language explaining what this action will do (generated by AI in prompts)"
    )
    parametersContext: Optional[str] = Field(
        None,
        description="Context for parameter generation"
    )
    learnings: List[str] = Field(
        default_factory=list,
        description="Learnings from previous actions"
    )
    
    # Resources (ALWAYS defined in Stage 1 if action needs them)
    documentList: Optional[DocumentReferenceList] = Field(
        None,
        description="Document references (ALWAYS defined in Stage 1 if action needs documents)"
    )
    connectionReference: Optional[str] = Field(
        None,
        description="Connection reference (ALWAYS defined in Stage 1 if action needs connection)"
    )
    
    # Parameters (may be defined in Stage 1 OR Stage 2, depending on action and actionObjective)
    parameters: Optional[Dict[str, Any]] = Field(
        None,
        description="Action-specific parameters (generated in Stage 2 for complex actions, or inferred from actionObjective for simple actions)"
    )
    
    def hasParameters(self) -> bool:
        """Check if parameters have been generated (Stage 2 complete or inferred)"""
        return self.parameters is not None
    
    def needsStage2(self) -> bool:
        """Determine if Stage 2 parameter generation is needed (generic, deterministic check)
        
        Generic logic (works for any action, dynamically added or removed):
        - If parameters are already set → Stage 2 not needed
        - If parameters are None → Stage 2 needed (to generate parameters from actionObjective and context)
        
        Note: Stage 1 always defines documentList and connectionReference if the action needs them.
        Stage 2 only generates the action-specific parameters dictionary.
        """
        # Generic check: if parameters are not set, Stage 2 is needed
        return self.parameters is None
    
    def updateFromStage1StringReferences(self, stringRefs: Optional[List[str]], connectionRef: Optional[str]):
        """Update documentList and connectionReference from Stage 1 string references
        
        Called when Stage 1 AI returns string references that need to be converted to typed models.
        """
        if stringRefs:
            self.documentList = DocumentReferenceList.from_string_list(stringRefs)
        if connectionRef:
            self.connectionReference = connectionRef


class AiResponseMetadata(BaseModel):
    """Metadata for AI response (varies by operation type)."""
    
    # Document Generation Metadata
    title: Optional[str] = Field(None, description="Document title")
    filename: Optional[str] = Field(None, description="Document filename")
    
    # Operation-Specific Metadata
    operationType: Optional[str] = Field(None, description="Type of operation performed")
    schemaVersion: Optional[str] = Field(None, description="Schema version (e.g., 'parameters_v1')", alias="schema")
    extractionMethod: Optional[str] = Field(None, description="Method used for extraction")
    sourceDocuments: Optional[List[str]] = Field(None, description="Source document references")
    
    # Additional metadata (for extensibility)
    additionalData: Optional[Dict[str, Any]] = Field(None, description="Additional operation-specific metadata")


class DocumentData(BaseModel):
    """Single document in response"""
    documentName: str = Field(description="Document name")
    documentData: Any = Field(description="Document data (can be str, bytes, dict, etc.)")
    mimeType: str = Field(description="MIME type of the document")
    sourceJson: Optional[Dict[str, Any]] = Field(
        None,
        description="Source JSON structure (preserved when rendering to xlsx/docx/pdf)"
    )


class ExtractContentParameters(BaseModel):
    """Parameters for extraction action.
    
    This model is defined together with the `methodAi.extractContent()` action function.
    All action parameter models follow this pattern: defined in the same module as the action.
    However, since this is a workflow-level model used across the system, it's defined here.
    """
    documentList: DocumentReferenceList = Field(description="Document references to extract content from")
    extractionOptions: Optional[Any] = Field(  # ExtractionOptions - forward reference
        None,
        description="Extraction options (determined dynamically based on task and document characteristics)"
    )


class AiResponse(BaseModel):
    """Unified response from all AI calls (planning, text, documents)"""
    
    content: str = Field(description="Response content (JSON string for planning, text for analysis, unified JSON for documents)")
    metadata: Optional[AiResponseMetadata] = Field(
        None,
        description="Response metadata (varies by operation type)"
    )
    documents: Optional[List[DocumentData]] = Field(
        None,
        description="Generated documents (only for document generation operations)"
    )
    
    def toJson(self) -> Dict[str, Any]:
        """
        Convert AI response content to JSON using enhanced stabilizing failsafe conversion methods.
        Centralizes AI result to JSON conversion in one place.
        
        Uses methods from jsonUtils:
        - tryParseJson() - Safe parsing with error handling
        - repairBrokenJson() - Repairs broken/incomplete JSON
        - extractJsonString() - Extracts JSON from text with code fences
        
        Returns:
            Dict containing the parsed JSON content, or a safe fallback structure if parsing fails.
            - If content is valid JSON dict: returns the dict directly
            - If content is valid JSON list: wraps in {"data": [...]}
            - If content is broken JSON: attempts repair using repairBrokenJson()
            - If all parsing fails: returns {"content": "...", "parseError": True}
        """
        # If content is already a dict, return it directly
        if isinstance(self.content, dict):
            return self.content
        
        # If content is already a list, wrap it
        if isinstance(self.content, list):
            return {"data": self.content}
        
        # Convert to string if needed
        contentStr = str(self.content) if not isinstance(self.content, str) else self.content
        
        # First, try to extract JSON from text (handles code fences, etc.)
        extractedJson = extractJsonString(contentStr)
        
        # Try to parse as JSON (returns tuple: obj, error, cleaned_str)
        parsedJson, parseError, _ = tryParseJson(extractedJson)
        
        if parsedJson is not None and parseError is None:
            # If it's a dict, return directly
            if isinstance(parsedJson, dict):
                return parsedJson
            # If it's a list, wrap in dict
            elif isinstance(parsedJson, list):
                return {"data": parsedJson}
        
        # Try to repair broken JSON
        repairedJson = repairBrokenJson(contentStr)
        if repairedJson:
            # repairBrokenJson returns Optional[Dict[str, Any]] - always a dict or None
            if isinstance(repairedJson, dict):
                return repairedJson
        
        # All parsing failed - return safe fallback
        contentStr = str(self.content) if not isinstance(self.content, str) else self.content
        return {"content": contentStr, "parseError": True}


# Workflow-level models

class RequestContext(BaseModel):
    """Normalized request context from user input"""
    
    originalPrompt: str = Field(description="Original user prompt")
    documents: List[Any] = Field(  # ChatDocument - forward reference
        default_factory=list,
        description="Documents provided by user"
    )
    userLanguage: str = Field(description="User's language")
    detectedComplexity: str = Field(
        description="Complexity level: simple, moderate, complex"
    )
    requiresDocuments: bool = Field(default=False, description="Whether request requires documents")
    requiresWebResearch: bool = Field(default=False, description="Whether request requires web research")
    requiresAnalysis: bool = Field(default=False, description="Whether request requires analysis")
    expectedOutputFormat: Optional[str] = Field(None, description="Expected output format")
    expectedOutputType: Optional[str] = Field(None, description="Expected output type: answer, document, analysis")


class UnderstandingResult(BaseModel):
    """Result from initial understanding phase (combined AI call)"""
    
    parameters: Dict[str, Any] = Field(
        default_factory=dict,
        description="Basic parameters (language, format, detail level)"
    )
    intention: Dict[str, Any] = Field(
        default_factory=dict,
        description="User intention (primaryGoal, secondaryGoals, intentionType)"
    )
    context: Dict[str, Any] = Field(
        default_factory=dict,
        description="Extracted context (topics, requirements, constraints)"
    )
    documentReferences: List[Dict[str, Any]] = Field(
        default_factory=list,
        description="Document references with purpose and relevance"
    )
    tasks: List["TaskDefinition"] = Field(  # Forward reference
        default_factory=list,
        description="Task definitions with deliverables"
    )


class TaskDefinition(BaseModel):
    """Task definition from understanding phase"""
    
    id: str = Field(description="Task identifier")
    objective: str = Field(description="Task objective")
    deliverable: Dict[str, Any] = Field(
        description="Deliverable specification (type, format, style, detailLevel)"
    )
    requiresWebResearch: bool = Field(default=False, description="Whether task requires web research")
    requiresDocumentAnalysis: bool = Field(default=False, description="Whether task requires document analysis")
    requiresContentGeneration: bool = Field(default=True, description="Whether task requires content generation")
    requiredDocuments: List[str] = Field(
        default_factory=list,
        description="Document references needed for this task"
    )
    extractionOptions: Optional[Any] = Field(  # ExtractionOptions - forward reference
        None,
        description="Extraction options for document processing (determined dynamically based on task and document characteristics)"
    )


class TaskResult(BaseModel):
    """Result from task execution"""
    
    taskId: str = Field(description="Task identifier")
    actionResult: Any = Field(description="ActionResult from task execution")  # ActionResult - forward reference


# Register model labels for UI
registerModelLabels(
    "RequestContext",
    {"en": "Request Context", "fr": "Contexte de la demande"},
    {
        "originalPrompt": {"en": "Original Prompt", "fr": "Invite originale"},
        "documents": {"en": "Documents", "fr": "Documents"},
        "userLanguage": {"en": "User Language", "fr": "Langue de l'utilisateur"},
        "detectedComplexity": {"en": "Detected Complexity", "fr": "Complexité détectée"},
        "requiresDocuments": {"en": "Requires Documents", "fr": "Nécessite des documents"},
        "requiresWebResearch": {"en": "Requires Web Research", "fr": "Nécessite une recherche web"},
        "requiresAnalysis": {"en": "Requires Analysis", "fr": "Nécessite une analyse"},
        "expectedOutputFormat": {"en": "Expected Output Format", "fr": "Format de sortie attendu"},
        "expectedOutputType": {"en": "Expected Output Type", "fr": "Type de sortie attendu"},
    },
)

registerModelLabels(
    "UnderstandingResult",
    {"en": "Understanding Result", "fr": "Résultat de compréhension"},
    {
        "parameters": {"en": "Parameters", "fr": "Paramètres"},
        "intention": {"en": "Intention", "fr": "Intention"},
        "context": {"en": "Context", "fr": "Contexte"},
        "documentReferences": {"en": "Document References", "fr": "Références de documents"},
        "tasks": {"en": "Tasks", "fr": "Tâches"},
    },
)

registerModelLabels(
    "TaskDefinition",
    {"en": "Task Definition", "fr": "Définition de tâche"},
    {
        "id": {"en": "Task ID", "fr": "ID de la tâche"},
        "objective": {"en": "Objective", "fr": "Objectif"},
        "deliverable": {"en": "Deliverable", "fr": "Livrable"},
        "requiresWebResearch": {"en": "Requires Web Research", "fr": "Nécessite une recherche web"},
        "requiresDocumentAnalysis": {"en": "Requires Document Analysis", "fr": "Nécessite une analyse de documents"},
        "requiresContentGeneration": {"en": "Requires Content Generation", "fr": "Nécessite une génération de contenu"},
        "requiredDocuments": {"en": "Required Documents", "fr": "Documents requis"},
        "extractionOptions": {"en": "Extraction Options", "fr": "Options d'extraction"},
    },
)

registerModelLabels(
    "TaskResult",
    {"en": "Task Result", "fr": "Résultat de tâche"},
    {
        "taskId": {"en": "Task ID", "fr": "ID de la tâche"},
        "actionResult": {"en": "Action Result", "fr": "Résultat de l'action"},
    },
)

registerModelLabels(
    "RequestContext",
    {"en": "Request Context", "fr": "Contexte de la demande"},
    {
        "originalPrompt": {"en": "Original Prompt", "fr": "Invite originale"},
        "documents": {"en": "Documents", "fr": "Documents"},
        "userLanguage": {"en": "User Language", "fr": "Langue de l'utilisateur"},
        "detectedComplexity": {"en": "Detected Complexity", "fr": "Complexité détectée"},
        "requiresDocuments": {"en": "Requires Documents", "fr": "Nécessite des documents"},
        "requiresWebResearch": {"en": "Requires Web Research", "fr": "Nécessite une recherche web"},
        "requiresAnalysis": {"en": "Requires Analysis", "fr": "Nécessite une analyse"},
        "expectedOutputFormat": {"en": "Expected Output Format", "fr": "Format de sortie attendu"},
        "expectedOutputType": {"en": "Expected Output Type", "fr": "Type de sortie attendu"},
    },
)

registerModelLabels(
    "UnderstandingResult",
    {"en": "Understanding Result", "fr": "Résultat de compréhension"},
    {
        "parameters": {"en": "Parameters", "fr": "Paramètres"},
        "intention": {"en": "Intention", "fr": "Intention"},
        "context": {"en": "Context", "fr": "Contexte"},
        "documentReferences": {"en": "Document References", "fr": "Références de documents"},
        "tasks": {"en": "Tasks", "fr": "Tâches"},
    },
)

registerModelLabels(
    "TaskDefinition",
    {"en": "Task Definition", "fr": "Définition de tâche"},
    {
        "id": {"en": "Task ID", "fr": "ID de la tâche"},
        "objective": {"en": "Objective", "fr": "Objectif"},
        "deliverable": {"en": "Deliverable", "fr": "Livrable"},
        "requiresWebResearch": {"en": "Requires Web Research", "fr": "Nécessite une recherche web"},
        "requiresDocumentAnalysis": {"en": "Requires Document Analysis", "fr": "Nécessite une analyse de documents"},
        "requiresContentGeneration": {"en": "Requires Content Generation", "fr": "Nécessite une génération de contenu"},
        "requiredDocuments": {"en": "Required Documents", "fr": "Documents requis"},
        "extractionOptions": {"en": "Extraction Options", "fr": "Options d'extraction"},
    },
)

registerModelLabels(
    "TaskResult",
    {"en": "Task Result", "fr": "Résultat de tâche"},
    {
        "taskId": {"en": "Task ID", "fr": "ID de la tâche"},
        "actionResult": {"en": "Action Result", "fr": "Résultat de l'action"},
    },
)

# Register model labels for UI
registerModelLabels(
    "ActionDefinition",
    {"en": "Action Definition", "fr": "Définition d'action"},
    {
        "action": {"en": "Action", "fr": "Action"},
        "actionObjective": {"en": "Action Objective", "fr": "Objectif de l'action"},
        "parametersContext": {"en": "Parameters Context", "fr": "Contexte des paramètres"},
        "learnings": {"en": "Learnings", "fr": "Apprentissages"},
        "documentList": {"en": "Document List", "fr": "Liste de documents"},
        "connectionReference": {"en": "Connection Reference", "fr": "Référence de connexion"},
        "parameters": {"en": "Parameters", "fr": "Paramètres"},
    },
)

registerModelLabels(
    "AiResponse",
    {"en": "AI Response", "fr": "Réponse IA"},
    {
        "content": {"en": "Content", "fr": "Contenu"},
        "metadata": {"en": "Metadata", "fr": "Métadonnées"},
        "documents": {"en": "Documents", "fr": "Documents"},
    },
)

registerModelLabels(
    "AiResponseMetadata",
    {"en": "AI Response Metadata", "fr": "Métadonnées de réponse IA"},
    {
        "title": {"en": "Title", "fr": "Titre"},
        "filename": {"en": "Filename", "fr": "Nom de fichier"},
        "operationType": {"en": "Operation Type", "fr": "Type d'opération"},
        "schemaVersion": {"en": "Schema Version", "fr": "Version du schéma"},
        "extractionMethod": {"en": "Extraction Method", "fr": "Méthode d'extraction"},
        "sourceDocuments": {"en": "Source Documents", "fr": "Documents sources"},
    },
)

registerModelLabels(
    "DocumentData",
    {"en": "Document Data", "fr": "Données de document"},
    {
        "documentName": {"en": "Document Name", "fr": "Nom du document"},
        "documentData": {"en": "Document Data", "fr": "Données du document"},
        "mimeType": {"en": "MIME Type", "fr": "Type MIME"},
    },
)

registerModelLabels(
    "RequestContext",
    {"en": "Request Context", "fr": "Contexte de requête"},
    {
        "originalPrompt": {"en": "Original Prompt", "fr": "Invite originale"},
        "documents": {"en": "Documents", "fr": "Documents"},
        "userLanguage": {"en": "User Language", "fr": "Langue de l'utilisateur"},
        "detectedComplexity": {"en": "Detected Complexity", "fr": "Complexité détectée"},
        "requiresDocuments": {"en": "Requires Documents", "fr": "Nécessite des documents"},
        "requiresWebResearch": {"en": "Requires Web Research", "fr": "Nécessite une recherche web"},
        "requiresAnalysis": {"en": "Requires Analysis", "fr": "Nécessite une analyse"},
    },
)

registerModelLabels(
    "UnderstandingResult",
    {"en": "Understanding Result", "fr": "Résultat de compréhension"},
    {
        "parameters": {"en": "Parameters", "fr": "Paramètres"},
        "intention": {"en": "Intention", "fr": "Intention"},
        "context": {"en": "Context", "fr": "Contexte"},
        "documentReferences": {"en": "Document References", "fr": "Références de documents"},
        "tasks": {"en": "Tasks", "fr": "Tâches"},
    },
)

registerModelLabels(
    "TaskDefinition",
    {"en": "Task Definition", "fr": "Définition de tâche"},
    {
        "id": {"en": "ID", "fr": "ID"},
        "objective": {"en": "Objective", "fr": "Objectif"},
        "deliverable": {"en": "Deliverable", "fr": "Livrable"},
        "requiresWebResearch": {"en": "Requires Web Research", "fr": "Nécessite une recherche web"},
        "requiresDocumentAnalysis": {"en": "Requires Document Analysis", "fr": "Nécessite une analyse de document"},
        "requiresContentGeneration": {"en": "Requires Content Generation", "fr": "Nécessite une génération de contenu"},
        "requiredDocuments": {"en": "Required Documents", "fr": "Documents requis"},
        "extractionOptions": {"en": "Extraction Options", "fr": "Options d'extraction"},
    },
)

registerModelLabels(
    "TaskResult",
    {"en": "Task Result", "fr": "Résultat de tâche"},
    {
        "taskId": {"en": "Task ID", "fr": "ID de tâche"},
        "actionResult": {"en": "Action Result", "fr": "Résultat d'action"},
    },
)

