# ---------------------------------------------------------------------------
#  Copyright (c) DevSecCode. All rights reserved.
# ---------------------------------------------------------------------------
"""Data-transfer objects for the code intelligence engine."""

from __future__ import annotations

import enum
from typing import Optional

from pydantic import BaseModel, Field


# ---------------------------------------------------------------------------
# Identity types
# ---------------------------------------------------------------------------

class SymbolLocator(BaseModel):
    """Entry-point locator — what callers pass in before resolution."""
    file_path: str
    line: int
    column: int = 0


class SymbolId(BaseModel):
    """Opaque stable identifier issued by the engine after resolution."""
    id: int


class GraphNodeId(BaseModel):
    """Opaque node id used in graph responses (symbol, file, community, process)."""
    id: int
    kind: str = "symbol"  # "symbol" | "file" | "community" | "process"


# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------

class EdgeKind(str, enum.Enum):
    """Edge kinds for v1 cutover."""
    IMPORTS = "IMPORTS"
    CALLS = "CALLS"
    EXTENDS = "EXTENDS"
    IMPLEMENTS = "IMPLEMENTS"
    OVERRIDES = "OVERRIDES"
    CONTAINS = "CONTAINS"
    MEMBER_OF = "MEMBER_OF"
    # Post-cutover (phase 5)
    STEP_IN_PROCESS = "STEP_IN_PROCESS"


class SymbolKind(str, enum.Enum):
    """Kinds of symbols extracted from source."""
    MODULE = "module"
    CLASS = "class"
    INTERFACE = "interface"
    ENUM = "enum"
    FUNCTION = "function"
    METHOD = "method"
    CONSTRUCTOR = "constructor"
    PROPERTY = "property"
    VARIABLE = "variable"
    EXPORT = "export"
    NAMESPACE = "namespace"


class GraphProjection(str, enum.Enum):
    """Graph view projection modes."""
    SYMBOL = "symbol"
    FILE = "file"
    # Post-cutover (phase 5)
    COMMUNITY = "community"
    PROCESS = "process"


class ResolutionConfidence(float, enum.Enum):
    """Well-known confidence levels."""
    EXACT = 1.0
    HIGH = 0.9
    MEDIUM = 0.7
    LOW = 0.4
    GLOBAL_FALLBACK = 0.2


# ---------------------------------------------------------------------------
# Raw extraction records (per-file, cheap to rewrite)
# ---------------------------------------------------------------------------

class RawImport(BaseModel):
    file_path: str
    imported_path: str
    imported_names: list[str] = Field(default_factory=list)
    is_default: bool = False
    is_namespace: bool = False
    alias: Optional[str] = None
    line: int = 0


class RawCall(BaseModel):
    file_path: str
    caller_symbol: Optional[str] = None
    callee_name: str
    receiver: Optional[str] = None
    line: int = 0
    column: int = 0


class RawHeritage(BaseModel):
    """extends / implements relationships."""
    file_path: str
    symbol_name: str
    parent_name: str
    kind: str = "extends"  # "extends" | "implements"
    line: int = 0


class RawVariableDef(BaseModel):
    """Variable definition with data-flow source information."""
    file_path: str
    scope_symbol: Optional[str] = None  # qualified name of enclosing scope
    variable_name: str
    kind: str = "assignment"  # 'assignment' | 'parameter' | 'destructuring'
    source_type: str = "unknown"  # 'call_result' | 'identifier' | 'property_access' | 'literal' | 'parameter' | 'unknown'
    source_call: Optional[RawCall] = None  # populated when source_type == 'call_result'
    source_variable: Optional[str] = None  # populated when source_type == 'identifier'
    source_property: Optional[str] = None  # populated when source_type == 'property_access' or kind == 'destructuring'
    line: int = 0
    col: int = 0


class RawCallArgument(BaseModel):
    """Argument passed to a call expression."""
    file_path: str
    call_index: int = 0  # index into result.calls to locate the parent call
    argument_index: int = 0
    kind: str = "identifier"  # 'identifier' | 'property_access' | 'literal' | 'call_result' | 'expression'
    variable_name: Optional[str] = None
    literal_value: Optional[str] = None
    property_chain: Optional[str] = None
    nested_call: Optional[RawCall] = None
    line: int = 0


class RawReturnExpression(BaseModel):
    """Return expression inside a function/method body."""
    file_path: str
    scope_symbol: Optional[str] = None  # qualified name of enclosing function
    kind: str = "identifier"  # 'identifier' | 'property_access' | 'call_result' | 'literal' | 'expression'
    variable_name: Optional[str] = None
    property_chain: Optional[str] = None
    call: Optional[RawCall] = None  # populated when kind == 'call_result'
    line: int = 0


# ---------------------------------------------------------------------------
# Resolved records
# ---------------------------------------------------------------------------

class ResolvedEdge(BaseModel):
    source_symbol_id: int
    target_symbol_id: int
    kind: EdgeKind
    confidence: float = 1.0
    metadata: dict = Field(default_factory=dict)


# ---------------------------------------------------------------------------
# Persisted entity records
# ---------------------------------------------------------------------------

class FileRecord(BaseModel):
    id: int = 0
    path: str
    language: str = ""
    content_hash: str = ""
    last_indexed: float = 0.0
    size: int = 0


class SymbolRecord(BaseModel):
    id: int = 0
    name: str
    qualified_name: str = ""
    kind: SymbolKind = SymbolKind.FUNCTION
    file_id: int = 0
    file_path: str = ""
    start_line: int = 0
    end_line: int = 0
    start_column: int = 0
    end_column: int = 0
    exported: bool = False
    is_default_export: bool = False
    signature: str = ""
    parent_symbol_id: Optional[int] = None


class ChangeMark(BaseModel):
    file_id: int
    reason: str = ""
    created_at: float = 0.0


# ---------------------------------------------------------------------------
# Stats
# ---------------------------------------------------------------------------

class CodeIntelStats(BaseModel):
    status: str = "idle"
    file_count: int = 0
    symbol_count: int = 0
    raw_import_count: int = 0
    raw_call_count: int = 0
    raw_heritage_count: int = 0
    variable_def_count: int = 0
    call_argument_count: int = 0
    return_expression_count: int = 0
    resolved_edge_count: int = 0
    stale_file_count: int = 0
    community_count: int = 0
    process_count: int = 0
    last_rebuild_at: Optional[float] = None
    last_rebuild_duration_ms: Optional[float] = None
    extraction_time_ms: Optional[float] = None
    resolution_time_ms: Optional[float] = None
    workers_used: int = 1
    languages: dict[str, int] = Field(default_factory=dict)


# ---------------------------------------------------------------------------
# Graph query / response
# ---------------------------------------------------------------------------

class GraphViewRequest(BaseModel):
    projection: GraphProjection = GraphProjection.SYMBOL
    focus_symbol: Optional[SymbolLocator] = None
    focus_file: Optional[str] = None
    query: Optional[str] = None
    depth: int = 2
    max_nodes: int = 200


class GraphNode(BaseModel):
    id: int
    kind: str  # "symbol" | "file" | "community" | "process"
    label: str
    file_path: Optional[str] = None
    line: Optional[int] = None
    symbol_kind: Optional[SymbolKind] = None
    fan_in: int = 0
    fan_out: int = 0
    metadata: dict = Field(default_factory=dict)


class GraphEdge(BaseModel):
    source: int
    target: int
    kind: EdgeKind
    confidence: float = 1.0
    label: Optional[str] = None


class GraphSnapshot(BaseModel):
    nodes: list[GraphNode] = Field(default_factory=list)
    edges: list[GraphEdge] = Field(default_factory=list)
    stale: bool = False
    projection: GraphProjection = GraphProjection.SYMBOL
    total_symbols: int = 0
    total_files: int = 0


# ---------------------------------------------------------------------------
# Context assembly
# ---------------------------------------------------------------------------

class ContextSnippet(BaseModel):
    file_path: str
    start_line: int
    end_line: int
    content: str
    symbol_name: Optional[str] = None
    symbol_kind: Optional[SymbolKind] = None
    score: float = 0.0


class ContextResult(BaseModel):
    snippets: list[ContextSnippet] = Field(default_factory=list)
    total_tokens: int = 0
    mentions_resolved: int = 0
    stale: bool = False


# ---------------------------------------------------------------------------
# Impact analysis
# ---------------------------------------------------------------------------

class ImpactResult(BaseModel):
    """Symbols and files impacted by a set of changes."""
    impacted_symbols: list[SymbolRecord] = Field(default_factory=list)
    impacted_files: list[str] = Field(default_factory=list)
    depth_reached: int = 0
    stale: bool = False


# ---------------------------------------------------------------------------
# Phase 5: Community detection
# ---------------------------------------------------------------------------

class CommunityRecord(BaseModel):
    """A detected community of co-calling symbols."""
    id: int = 0
    label: str = ""
    size: int = 0
    cohesion: float = 0.0
    created_at: float = 0.0


# ---------------------------------------------------------------------------
# Phase 5: Process tracing
# ---------------------------------------------------------------------------

class ProcessLifecycle(str, enum.Enum):
    """Lifecycle types for traced processes."""
    REQUEST = "request"
    BACKGROUND = "background"
    MESSAGE = "message"
    LIFECYCLE = "lifecycle"


class ProcessRecord(BaseModel):
    """A traced end-to-end process (call chain from an entrypoint)."""
    id: int = 0
    label: str = ""
    lifecycle_type: ProcessLifecycle = ProcessLifecycle.REQUEST
    entrypoint_id: int = 0
    depth: int = 0
    step_count: int = 0
    community_id: Optional[int] = None
    created_at: float = 0.0


# ---------------------------------------------------------------------------
# Phase 5: Rename / refactor
# ---------------------------------------------------------------------------

class ReferenceLocation(BaseModel):
    """A single reference to a symbol in the workspace."""
    file_path: str
    line: int
    column: int
    end_column: int
    kind: str = "definition"  # definition | import | call | heritage | override
    text: str = ""


class FileEdit(BaseModel):
    """A text edit to apply during a rename operation."""
    file_path: str
    line: int
    column: int
    end_column: int
    old_text: str
    new_text: str
    kind: str = ""
