"""
Defines wrapper objects around the types returned by LSP to ensure decoupling between LSP versions and SolidLSP
"""

from __future__ import annotations

from enum import Enum, IntEnum
from typing import TYPE_CHECKING, NotRequired, Union

from sensai.util.helper import mark_used
from typing_extensions import TypedDict

from solidlsp.lsp_protocol_handler.lsp_types import DiagnosticSeverity, SymbolKind

if TYPE_CHECKING:
    from .ls import SymbolBody

# a lot of code relied on a previously duplicated SymbolKind definition here.
# This line is kept to avoid breaking downstream imports
mark_used(SymbolKind)


URI = str
DocumentUri = str
Uint = int
RegExp = str


class Position(TypedDict):
    r"""Position in a text document expressed as zero-based line and character
    offset. Prior to 3.17 the offsets were always based on a UTF-16 string
    representation. So a string of the form `a𐐀b` the character offset of the
    character `a` is 0, the character offset of `𐐀` is 1 and the character
    offset of b is 3 since `𐐀` is represented using two code units in UTF-16.
    Since 3.17 clients and servers can agree on a different string encoding
    representation (e.g. UTF-8). The client announces it's supported encoding
    via the client capability [`general.positionEncodings`](#clientCapabilities).
    The value is an array of position encodings the client supports, with
    decreasing preference (e.g. the encoding at index `0` is the most preferred
    one). To stay backwards compatible the only mandatory encoding is UTF-16
    represented via the string `utf-16`. The server can pick one of the
    encodings offered by the client and signals that encoding back to the
    client via the initialize result's property
    [`capabilities.positionEncoding`](#serverCapabilities). If the string value
    `utf-16` is missing from the client's capability `general.positionEncodings`
    servers can safely assume that the client supports UTF-16. If the server
    omits the position encoding in its initialize result the encoding defaults
    to the string value `utf-16`. Implementation considerations: since the
    conversion from one encoding into another requires the content of the
    file / line the conversion is best done where the file is read which is
    usually on the server side.

    Positions are line end character agnostic. So you can not specify a position
    that denotes `\r|\n` or `\n|` where `|` represents the character offset.

    @since 3.17.0 - support for negotiated position encoding.
    """

    line: Uint
    """ Line position in a document (zero-based).

    If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document.
    If a line number is negative, it defaults to 0. """
    character: Uint
    """ Character offset on a line in a document (zero-based).

    The meaning of this offset is determined by the negotiated
    `PositionEncodingKind`.

    If the character value is greater than the line length it defaults back to the
    line length. """


class Range(TypedDict):
    """A range in a text document expressed as (zero-based) start and end positions.

    If you want to specify a range that contains a line including the line ending
    character(s) then use an end position denoting the start of the next line.
    For example:
    ```ts
    {
        start: { line: 5, character: 23 }
        end : { line 6, character : 0 }
    }
    ```
    """

    start: Position
    """ The range's start position. """
    end: Position
    """ The range's end position. """


class Location(TypedDict):
    """Represents a location inside a resource, such as a line
    inside a text file.
    """

    uri: DocumentUri
    range: Range
    absolutePath: str
    relativePath: str | None


class CompletionItemKind(IntEnum):
    """The kind of a completion entry."""

    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
    EnumMember = 20
    Constant = 21
    Struct = 22
    Event = 23
    Operator = 24
    TypeParameter = 25


class CompletionItem(TypedDict):
    """A completion item represents a text snippet that is
    proposed to complete text that is being typed.
    """

    completionText: str
    """ The completionText of this completion item.

    The completionText property is also by default the text that
    is inserted when selecting this completion."""

    kind: CompletionItemKind
    """ The kind of this completion item. Based of the kind
    an icon is chosen by the editor. """

    detail: NotRequired[str]
    """ A human-readable string with additional information
    about this item, like type or symbol information. """


class SymbolTag(IntEnum):
    """Symbol tags are extra annotations that tweak the rendering of a symbol.

    @since 3.16
    """

    Deprecated = 1
    """ Render a symbol as obsolete, usually using a strike-out. """


class UnifiedSymbolInformation(TypedDict):
    """
    Represents information about programming constructs like variables, classes,
    interfaces etc.

    This is a unifying extension of `lsp_types.SymbolInformation` and `lsp_types.DocumentSymbol`,
    with added fields for SolidLSP/Serena use.
    """

    deprecated: NotRequired[bool]
    """ Indicates if this symbol is deprecated.

    @deprecated Use tags instead """
    location: NotRequired[Location]
    """ The location of this symbol. The location's range is used by a tool
    to reveal the location in the editor. If the symbol is selected in the
    tool the range's start information is used to position the cursor. So
    the range usually spans more than the actual symbol's name and does
    normally include things like visibility modifiers.

    The range doesn't have to denote a node range in the sense of an abstract
    syntax tree. It can therefore not be used to re-construct a hierarchy of
    the symbols. """
    name: str
    """ The name of this symbol. """
    kind: SymbolKind
    """ The kind of this symbol. """
    tags: NotRequired[list[SymbolTag]]
    """ Tags for this symbol.

    @since 3.16.0 """
    containerName: NotRequired[str]
    """ The name of the symbol containing this symbol. This information is for
    user interface purposes (e.g. to render a qualifier in the user interface
    if necessary). It can't be used to re-infer a hierarchy for the document
    symbols. 
    
    Note: within Serena, the parent attribute was added and should be used instead. 
    Most LS don't provide containerName.
    """

    detail: NotRequired[str]
    """ More detail for this symbol, e.g the signature of a function. """

    range: NotRequired[Range]
    """ The range enclosing this symbol not including leading/trailing whitespace but everything else
    like comments. This information is typically used to determine if the clients cursor is
    inside the symbol to reveal in the symbol in the UI. """
    selectionRange: NotRequired[Range]
    """ The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
    Must be contained by the `range`. """

    body: NotRequired["SymbolBody"]
    """ The body of the symbol. """

    children: list[UnifiedSymbolInformation]
    """ The children of the symbol. 
    Added to be compatible with `lsp_types.DocumentSymbol`, 
    since it is sometimes useful to have the children of the symbol as a user-facing feature."""

    parent: NotRequired[UnifiedSymbolInformation | None]
    """The parent of the symbol, if there is any. Added with Serena, not part of the LSP.
    All symbols except the root packages will have a parent.
    """

    overload_idx: NotRequired[int]
    """
    The overload index of the symbol, if applicable. If a symbol does not have overloads, this field is omitted.
    If the symbol is an overloaded function or method (same symbol name with the same parent), 
    this index indicates which overload it is. The index is 0-based.
    Added for Serena, not part of the LSP.
    """


class MarkupKind(Enum):
    """Describes the content type that a client supports in various
    result literals like `Hover`, `ParameterInfo` or `CompletionItem`.

    Please note that `MarkupKinds` must not start with a `$`. This kinds
    are reserved for internal usage.
    """

    PlainText = "plaintext"
    """ Plain text is supported as a content format """
    Markdown = "markdown"
    """ Markdown is supported as a content format """


class __MarkedString_Type_1(TypedDict):
    language: str
    value: str


MarkedString = Union[str, "__MarkedString_Type_1"]
""" MarkedString can be used to render human readable text. It is either a markdown string
or a code-block that provides a language and a code snippet. The language identifier
is semantically equal to the optional language identifier in fenced code blocks in GitHub
issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting

The pair of a language and a value is an equivalent to markdown:
```${language}
${value}
```

Note that markdown strings will be sanitized - that means html will be escaped.
@deprecated use MarkupContent instead. """


class MarkupContent(TypedDict):
    r"""A `MarkupContent` literal represents a string value which content is interpreted base on its
    kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds.

    If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues.
    See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting

    Here is an example how such a string can be constructed using JavaScript / TypeScript:
    ```ts
    let markdown: MarkdownContent = {
     kind: MarkupKind.Markdown,
     value: [
       '# Header',
       'Some text',
       '```typescript',
       'someCode();',
       '```'
     ].join('\n')
    };
    ```

    *Please Note* that clients might sanitize the return markdown. A client could decide to
    remove HTML from the markdown to avoid script execution.
    """

    kind: MarkupKind
    """ The type of the Markup """
    value: str
    """ The content itself """


class Hover(TypedDict):
    """The result of a hover request."""

    contents: MarkupContent | MarkedString | list[MarkedString]
    """ The hover's content """
    range: NotRequired[Range]
    """ An optional range inside the text document that is used to
    visualize the hover, e.g. by changing the background color. """


class TextDocumentIdentifier(TypedDict):
    """A literal to identify a text document in the client."""

    uri: DocumentUri
    """ The text document's uri. """


class TextEdit(TypedDict):
    """A textual edit applicable to a text document."""

    range: Range
    """ The range of the text document to be manipulated. """
    newText: str
    """ The string to be inserted. For delete operations use an empty string. """


class WorkspaceEdit(TypedDict):
    """A workspace edit represents changes to many resources managed in the workspace."""

    changes: NotRequired[dict[DocumentUri, list[TextEdit]]]
    """ Holds changes to existing resources. """
    documentChanges: NotRequired[list]
    """ Document changes array for versioned edits. """


class Diagnostic(TypedDict):
    """Diagnostic information for a text document."""

    uri: DocumentUri
    """ The URI of the text document to which the diagnostics apply. """
    range: Range
    """ The range of the text document to which the diagnostics apply. """
    severity: NotRequired[DiagnosticSeverity]
    """ The severity of the diagnostic. """
    message: str
    """ The diagnostic message. """
    code: NotRequired[str | int]
    """ The code of the diagnostic. """
    source: NotRequired[str]
    """ The source of the diagnostic, e.g. the name of the tool that produced it. """


class SignatureHelp(TypedDict):
    """
    Signature help represents the signature of something
    callable. There can be multiple signature but only one
    active and only one active parameter.

    See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#signatureHelp
    """

    signatures: list[SignatureInformation]
    """ One or more signatures. """
    activeSignature: NotRequired[int]
    """ The active signature. If omitted or the value lies outside the
    range of `signatures` the value defaults to zero or is ignored if
    the `SignatureHelp` has no signatures.

    Whenever possible implementers should make an active decision about
    the active signature and shouldn't rely on a default value.

    In future version of the protocol this property might become
    mandatory to better express this. """
    activeParameter: NotRequired[int]
    """ The active parameter of the active signature. If omitted or the value
    lies outside the range of `signatures[activeSignature].parameters`
    defaults to 0 if the active signature has parameters. If
    the active signature has no parameters it is ignored.
    In future version of the protocol this property might become
    mandatory to better express the active parameter if the
    active signature does have any. """


class SignatureInformation(TypedDict):
    """Represents the signature of something callable. A signature
    can have a label, like a function-name, a doc-comment, and
    a set of parameters.
    """

    label: str
    """ The label of this signature. Will be shown in
    the UI. """
    documentation: NotRequired[MarkupContent | str]
    """ The human-readable doc-comment of this signature. Will be shown
    in the UI but can be omitted. """
    parameters: NotRequired[list[ParameterInformation]]
    """ The parameters of this signature. """
    activeParameter: NotRequired[int]
    """ The index of the active parameter.

    If provided, this is used in place of `SignatureHelp.activeParameter`.

    @since 3.16.0 """


class ParameterInformation(TypedDict):
    """Represents a parameter of a callable-signature. A parameter can
    have a label and a doc-comment.
    """

    label: str | list[int]
    """ The label of this parameter information.

    Either a string or an inclusive start and exclusive end offsets within its containing
    signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
    string representation as `Position` and `Range` does.

    *Note*: a label of type string should be a substring of its containing signature label.
    Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. """
    documentation: NotRequired[MarkupContent | str]
    """ The human-readable doc-comment of this parameter. Will be shown
    in the UI but can be omitted. """
