"""LSP Protocol data structures and message handling."""

from __future__ import annotations

import json
from dataclasses import dataclass, field, asdict
from typing import Any
from enum import IntEnum


class MessageType(IntEnum):
    """LSP message types."""

    ERROR = 1
    WARNING = 2
    INFO = 3
    LOG = 4


class CompletionItemKind(IntEnum):
    """LSP completion item kinds."""

    TEXT = 1
    METHOD = 2
    FUNCTION = 3
    CONSTRUCTOR = 4
    FIELD = 5
    VARIABLE = 6
    CLASS = 7
    INTERFACE = 8
    MODULE = 9
    PROPERTY = 10
    UNIT = 11
    VALUE = 12
    ENUM = 13
    KEYWORD = 14
    SNIPPET = 15
    COLOR = 16
    FILE = 17
    REFERENCE = 18
    FOLDER = 19
    ENUM_MEMBER = 20
    CONSTANT = 21
    STRUCT = 22
    EVENT = 23
    OPERATOR = 24
    TYPE_PARAMETER = 25


class SymbolKind(IntEnum):
    """LSP symbol kinds."""

    FILE = 1
    MODULE = 2
    NAMESPACE = 3
    PACKAGE = 4
    CLASS = 5
    METHOD = 6
    PROPERTY = 7
    FIELD = 8
    CONSTRUCTOR = 9
    ENUM = 10
    INTERFACE = 11
    FUNCTION = 12
    VARIABLE = 13
    CONSTANT = 14
    STRING = 15
    NUMBER = 16
    BOOLEAN = 17
    ARRAY = 18
    OBJECT = 19
    KEY = 20
    NULL = 21
    ENUM_MEMBER = 22
    STRUCT = 23
    EVENT = 24
    OPERATOR = 25
    TYPE_PARAMETER = 26


@dataclass
class Position:
    """Position in a text document (0-indexed)."""

    line: int
    character: int

    def to_dict(self) -> dict[str, int]:
        """Convert to LSP dict format."""
        return {"line": self.line, "character": self.character}

    @classmethod
    def from_dict(cls, data: dict) -> Position:
        """Create from LSP dict format."""
        return cls(line=data["line"], character=data["character"])


@dataclass
class Range:
    """Range in a text document."""

    start: Position
    end: Position

    def to_dict(self) -> dict:
        """Convert to LSP dict format."""
        return {"start": self.start.to_dict(), "end": self.end.to_dict()}

    @classmethod
    def from_dict(cls, data: dict) -> Range:
        """Create from LSP dict format."""
        return cls(
            start=Position.from_dict(data["start"]),
            end=Position.from_dict(data["end"]),
        )


@dataclass
class Location:
    """Location in a text document."""

    uri: str
    range: Range

    def to_dict(self) -> dict:
        """Convert to LSP dict format."""
        return {"uri": self.uri, "range": self.range.to_dict()}

    @classmethod
    def from_dict(cls, data: dict) -> Location:
        """Create from LSP dict format."""
        return cls(
            uri=data["uri"],
            range=Range.from_dict(data["range"]),
        )

    @property
    def file_path(self) -> str:
        """Get file path from URI."""
        uri = self.uri
        if uri.startswith("file:///"):
            # Windows: file:///C:/path -> C:/path
            path = uri[8:]
            if len(path) > 2 and path[1] == ":":
                return path
            return "/" + path
        elif uri.startswith("file://"):
            return uri[7:]
        return uri


@dataclass
class TextDocumentIdentifier:
    """Identifier for a text document."""

    uri: str

    def to_dict(self) -> dict[str, str]:
        """Convert to LSP dict format."""
        return {"uri": self.uri}

    @classmethod
    def from_file_path(cls, file_path: str) -> TextDocumentIdentifier:
        """Create from file path."""
        from pathlib import Path

        path = Path(file_path).resolve()
        # Convert to file:// URI
        if path.drive:
            # Windows path
            uri = f"file:///{path.as_posix()}"
        else:
            uri = f"file://{path}"
        return cls(uri=uri)


@dataclass
class TextDocumentPositionParams:
    """Parameters for text document position requests."""

    text_document: TextDocumentIdentifier
    position: Position

    def to_dict(self) -> dict:
        """Convert to LSP dict format."""
        return {
            "textDocument": self.text_document.to_dict(),
            "position": self.position.to_dict(),
        }


@dataclass
class DocumentSymbol:
    """Symbol information in a document."""

    name: str
    kind: SymbolKind
    range: Range
    selection_range: Range
    detail: str | None = None
    children: list[DocumentSymbol] = field(default_factory=list)

    @classmethod
    def from_dict(cls, data: dict) -> DocumentSymbol:
        """Create from LSP dict format."""
        return cls(
            name=data["name"],
            kind=SymbolKind(data["kind"]),
            range=Range.from_dict(data["range"]),
            selection_range=Range.from_dict(data["selectionRange"]),
            detail=data.get("detail"),
            children=[cls.from_dict(c) for c in data.get("children", [])],
        )


@dataclass
class SymbolInformation:
    """Symbol information (flat structure)."""

    name: str
    kind: SymbolKind
    location: Location
    container_name: str | None = None

    @classmethod
    def from_dict(cls, data: dict) -> SymbolInformation:
        """Create from LSP dict format."""
        return cls(
            name=data["name"],
            kind=SymbolKind(data["kind"]),
            location=Location.from_dict(data["location"]),
            container_name=data.get("containerName"),
        )


@dataclass
class Hover:
    """Hover information."""

    contents: str | list[str]
    range: Range | None = None

    @classmethod
    def from_dict(cls, data: dict) -> Hover:
        """Create from LSP dict format."""
        contents = data.get("contents", "")
        if isinstance(contents, dict):
            contents = contents.get("value", str(contents))
        elif isinstance(contents, list):
            contents = [c.get("value", str(c)) if isinstance(c, dict) else str(c) for c in contents]

        return cls(
            contents=contents,
            range=Range.from_dict(data["range"]) if "range" in data else None,
        )


@dataclass
class CompletionItem:
    """Completion item."""

    label: str
    kind: CompletionItemKind | None = None
    detail: str | None = None
    documentation: str | None = None
    insert_text: str | None = None

    @classmethod
    def from_dict(cls, data: dict) -> CompletionItem:
        """Create from LSP dict format."""
        kind = None
        if "kind" in data:
            try:
                kind = CompletionItemKind(data["kind"])
            except ValueError:
                pass

        doc = data.get("documentation")
        if isinstance(doc, dict):
            doc = doc.get("value", str(doc))

        return cls(
            label=data["label"],
            kind=kind,
            detail=data.get("detail"),
            documentation=doc,
            insert_text=data.get("insertText"),
        )


@dataclass
class Diagnostic:
    """Diagnostic information (errors, warnings)."""

    range: Range
    message: str
    severity: int | None = None
    code: str | int | None = None
    source: str | None = None

    @classmethod
    def from_dict(cls, data: dict) -> Diagnostic:
        """Create from LSP dict format."""
        return cls(
            range=Range.from_dict(data["range"]),
            message=data["message"],
            severity=data.get("severity"),
            code=data.get("code"),
            source=data.get("source"),
        )


class LSPMessage:
    """LSP JSON-RPC message builder."""

    _id_counter = 0

    @classmethod
    def _next_id(cls) -> int:
        """Get next message ID."""
        cls._id_counter += 1
        return cls._id_counter

    @classmethod
    def request(cls, method: str, params: dict | None = None) -> tuple[int, str]:
        """Create a request message."""
        msg_id = cls._next_id()
        message = {
            "jsonrpc": "2.0",
            "id": msg_id,
            "method": method,
        }
        if params:
            message["params"] = params

        content = json.dumps(message)
        return msg_id, f"Content-Length: {len(content)}\r\n\r\n{content}"

    @classmethod
    def notification(cls, method: str, params: dict | None = None) -> str:
        """Create a notification message (no response expected)."""
        message = {
            "jsonrpc": "2.0",
            "method": method,
        }
        if params:
            message["params"] = params

        content = json.dumps(message)
        return f"Content-Length: {len(content)}\r\n\r\n{content}"

    @classmethod
    def response(cls, msg_id: int, result: Any = None, error: dict | None = None) -> str:
        """Create a response message."""
        message: dict[str, Any] = {
            "jsonrpc": "2.0",
            "id": msg_id,
        }
        if error:
            message["error"] = error
        else:
            message["result"] = result

        content = json.dumps(message)
        return f"Content-Length: {len(content)}\r\n\r\n{content}"

    @classmethod
    def parse(cls, data: str) -> dict:
        """Parse an LSP message."""
        # Find Content-Length header
        lines = data.split("\r\n")
        content_length = 0
        header_end = 0

        for i, line in enumerate(lines):
            if line.startswith("Content-Length:"):
                content_length = int(line.split(":")[1].strip())
            elif line == "":
                header_end = i + 1
                break

        # Extract JSON content
        content = "\r\n".join(lines[header_end:])
        if content:
            return json.loads(content)
        return {}
