require "json"

module LSP
  alias DocumentUri = String
  alias ProgressToken = String | Int32

  # Position in a text document
  struct Position
    include JSON::Serializable
    include Comparable(Position)

    property line : Int32
    property character : Int32

    def initialize(@line : Int32, @character : Int32)
    end

    def <=>(other : Position)
      line_cmp = @line <=> other.line
      return line_cmp unless line_cmp == 0
      @character <=> other.character
    end

    def ==(other : Position) : Bool
      @line == other.line && @character == other.character
    end
  end

  # Range in a text document
  struct Range
    include JSON::Serializable

    property start : Position

    @[JSON::Field(key: "end")]
    property ending : Position

    def initialize(@start : Position, @ending : Position)
    end

    def contains?(position : Position)
      position >= @start && position <= @ending
    end

    def ==(other : Range) : Bool
      @start == other.start && @ending == other.ending
    end
  end

  # Location in a text document
  struct Location
    include JSON::Serializable

    property uri : DocumentUri
    property range : Range

    def initialize(@uri : DocumentUri, @range : Range)
    end
  end

  # Diagnostic severity
  enum DiagnosticSeverity
    Error       = 1
    Warning     = 2
    Information = 3
    Hint        = 4

    def to_json(json : JSON::Builder)
      json.number(value)
    end

    def self.new(pull : JSON::PullParser) : self
      case pull.kind
      when .int?
        from_value(pull.read_int.to_i)
      when .string?
        parse(pull.read_string)
      else
        pull.raise "Expected int or string for DiagnosticSeverity"
      end
    end
  end

  # Diagnostic message
  struct Diagnostic
    include JSON::Serializable

    property range : Range
    property severity : DiagnosticSeverity?
    property code : String | Int32?
    property source : String?
    property message : String
    property? tags : Array(Int32)?
    property? related_information : Array(DiagnosticRelatedInformation)?

    def initialize(
      @range : Range,
      @message : String,
      @severity : DiagnosticSeverity? = nil,
      @source : String? = nil,
    )
    end
  end

  struct DiagnosticRelatedInformation
    include JSON::Serializable

    property location : Location
    property message : String
  end

  # Text edit
  struct TextEdit
    include JSON::Serializable

    property range : Range
    @[JSON::Field(key: "newText")]
    property new_text : String

    def initialize(@range : Range, @new_text : String)
    end
  end

  # Workspace edit
  struct WorkspaceEdit
    include JSON::Serializable

    property? changes : Hash(DocumentUri, Array(TextEdit))?
    property? document_changes : Array(TextDocumentEdit)?

    def initialize(@changes : Hash(DocumentUri, Array(TextEdit))? = nil)
    end
  end

  # Text document identifier
  struct TextDocumentIdentifier
    include JSON::Serializable

    property uri : DocumentUri

    def initialize(@uri : DocumentUri)
    end
  end

  # Versioned text document identifier
  struct VersionedTextDocumentIdentifier
    include JSON::Serializable

    property uri : DocumentUri
    property version : Int32

    def initialize(@uri : DocumentUri, @version : Int32)
    end
  end

  # Text document edit
  struct TextDocumentEdit
    include JSON::Serializable

    property text_document : VersionedTextDocumentIdentifier
    property edits : Array(TextEdit)
  end

  # Text document item
  struct TextDocumentItem
    include JSON::Serializable

    property uri : DocumentUri
    @[JSON::Field(key: "languageId")]
    property language_id : String
    property version : Int32
    property text : String

    def initialize(
      @uri : DocumentUri,
      @language_id : String,
      @version : Int32,
      @text : String,
    )
    end
  end

  # Completion item kind
  enum CompletionItemKind
    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

    def to_json(json : JSON::Builder)
      json.number(value)
    end
  end

  # Insert text format for completion items
  enum InsertTextFormat
    PlainText = 1
    Snippet   = 2

    def to_json(json : JSON::Builder)
      json.number(value)
    end
  end

  # Completion item documentation — can be a plain string or a MarkupContent
  # object.  LSP clients that support markdown (Zed, Neovim, Helix) render the
  # latter with full syntax highlighting; plain-string clients fall back safely.
  struct CompletionDocumentation
    include JSON::Serializable

    property kind : String  # "plaintext" | "markdown"
    property value : String

    def initialize(@kind : String, @value : String)
    end

    # Convenience: build a markdown documentation block.
    def self.markdown(text : String) : self
      new("markdown", text)
    end

    # Convenience: build a plain-text documentation block.
    def self.plain(text : String) : self
      new("plaintext", text)
    end
  end

  # Completion item
  struct CompletionItem
    include JSON::Serializable

    property label : String
    property kind : CompletionItemKind?
    property detail : String?
    # documentation accepts either a plain String (for backwards compatibility
    # with providers that set it directly) or a CompletionDocumentation value
    # object (for Markdown-aware clients). The JSON::Serializable union handles
    # serialisation: a plain String serialises as a JSON string; a
    # CompletionDocumentation serialises as {"kind":...,"value":...}.
    property documentation : CompletionDocumentation | String | Nil
    property? deprecated : Bool?
    property? preselect : Bool?
    @[JSON::Field(key: "sortText")]
    property sort_text : String?
    @[JSON::Field(key: "filterText")]
    property filter_text : String?
    @[JSON::Field(key: "insertText")]
    property insert_text : String?
    @[JSON::Field(key: "insertTextFormat")]
    property? insert_text_format : InsertTextFormat?
    @[JSON::Field(key: "textEdit")]
    property? text_edit : TextEdit?
    @[JSON::Field(key: "additionalTextEdits")]
    property? additional_text_edits : Array(TextEdit)?
    @[JSON::Field(key: "commitCharacters")]
    property? commit_characters : Array(String)?
    property? data : JSON::Any?

    def initialize(
      @label : String,
      @kind : CompletionItemKind? = nil,
      @detail : String? = nil,
    )
    end

    # Set documentation from a plain string, wrapping it as MarkupContent so
    # the client always receives a structured object rather than a bare string.
    def doc=(text : String)
      @documentation = CompletionDocumentation.markdown(text)
    end

    # Set documentation from an already-built CompletionDocumentation object.
    def doc=(markup : CompletionDocumentation)
      @documentation = markup
    end
  end

  # Completion list
  struct CompletionList
    include JSON::Serializable

    property? is_incomplete : Bool
    property items : Array(CompletionItem)

    def initialize(
      @items : Array(CompletionItem),
      @is_incomplete : Bool = false,
    )
    end
  end

  # Symbol kind
  enum SymbolKind
    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
    EnumMember    = 22
    Struct        = 23
    Event         = 24
    Operator      = 25
    TypeParameter = 26

    def to_json(json : JSON::Builder)
      json.number(value)
    end

    def self.new(pull : JSON::PullParser) : self
      case pull.kind
      when .int?
        from_value(pull.read_int.to_i)
      when .string?
        parse(pull.read_string)
      else
        pull.raise "Expected int or string for SymbolKind"
      end
    end
  end

  # Document symbol
  struct DocumentSymbol
    include JSON::Serializable

    property name : String
    property detail : String?
    property kind : SymbolKind
    property? deprecated : Bool?
    property range : Range
    @[JSON::Field(key: "selectionRange")]
    property selection_range : Range
    property? children : Array(DocumentSymbol)?

    def initialize(
      @name : String,
      @kind : SymbolKind,
      @range : Range,
      @selection_range : Range,
    )
    end
  end

  # Symbol information
  struct SymbolInformation
    include JSON::Serializable

    property name : String
    property kind : SymbolKind
    property? deprecated : Bool?
    property location : Location
    @[JSON::Field(key: "containerName")]
    property? container_name : String?

    def initialize(@name : String, @kind : SymbolKind, @location : Location)
    end
  end

  # Markup content
  struct MarkupContent
    include JSON::Serializable

    property kind : String
    property value : String

    def initialize(@kind : String, @value : String)
    end
  end

  # Hover result
  struct Hover
    include JSON::Serializable

    property contents : MarkupContent | String
    property range : Range?

    def initialize(@contents : MarkupContent | String, @range : Range? = nil)
    end
  end

  # Signature information
  struct SignatureInformation
    include JSON::Serializable

    property label : String
    property documentation : String | MarkupContent?
    property? parameters : Array(ParameterInformation)?

    def initialize(
      @label : String,
      @documentation : String | MarkupContent? = nil,
      @parameters : Array(ParameterInformation)? = nil,
    )
    end
  end

  # Parameter information
  struct ParameterInformation
    include JSON::Serializable

    property label : String | Array(Int32)
    property documentation : String | MarkupContent?

    def initialize(@label : String | Array(Int32))
    end
  end

  # Signature help
  struct SignatureHelp
    include JSON::Serializable

    property signatures : Array(SignatureInformation)
    property? active_signature : Int32?
    property? active_parameter : Int32?

    def initialize(
      @signatures : Array(SignatureInformation),
      @active_signature : Int32? = nil,
      @active_parameter : Int32? = nil,
    )
    end
  end

  # Server capabilities
  struct ServerCapabilities
    include JSON::Serializable

    property? text_document_sync : Int32?
    property? hover_provider : Bool?
    property? completion_provider : CompletionOptions?
    property? signature_help_provider : SignatureHelpOptions?
    property? definition_provider : Bool?
    property? references_provider : Bool?
    property? document_highlight_provider : Bool?
    property? document_symbol_provider : Bool?
    property? workspace_symbol_provider : Bool?
    property? code_action_provider : Bool?
    property? code_lens_provider : CodeLensOptions?
    property? document_formatting_provider : Bool?
    property? document_range_formatting_provider : Bool?
    property? rename_provider : Bool | RenameOptions?
    property? document_link_provider : DocumentLinkOptions?
    property? execute_command_provider : ExecuteCommandOptions?
    property? workspace : WorkspaceServerCapabilities?

    def initialize
    end
  end

  struct CompletionOptions
    include JSON::Serializable

    property? resolve_provider : Bool?
    property? trigger_characters : Array(String)?

    def initialize(
      @trigger_characters : Array(String)? = nil,
      @resolve_provider : Bool? = nil,
    )
    end
  end

  struct SignatureHelpOptions
    include JSON::Serializable

    property? trigger_characters : Array(String)?
    property? retrigger_characters : Array(String)?

    def initialize(@trigger_characters : Array(String)? = nil)
    end
  end

  struct CodeLensOptions
    include JSON::Serializable

    property? resolve_provider : Bool?
  end

  struct RenameOptions
    include JSON::Serializable

    property? prepare_provider : Bool?

    def initialize(@prepare_provider : Bool? = nil)
    end
  end

  struct DocumentLinkOptions
    include JSON::Serializable

    property? resolve_provider : Bool?
  end

  struct ExecuteCommandOptions
    include JSON::Serializable

    property commands : Array(String)

    def initialize(@commands : Array(String))
    end
  end

  struct WorkspaceServerCapabilities
    include JSON::Serializable

    property? workspace_folders : WorkspaceFoldersServerCapabilities?
  end

  struct WorkspaceFoldersServerCapabilities
    include JSON::Serializable

    property? supported : Bool?
    property? change_notifications : Bool | String?
  end

  # Workspace folder
  struct WorkspaceFolder
    include JSON::Serializable

    property uri : DocumentUri
    property name : String

    def initialize(@uri : DocumentUri, @name : String)
    end
  end

  # workspace/didChangeWatchedFiles params
  struct DidChangeWatchedFilesParams
    include JSON::Serializable

    property changes : Array(FileEvent)
  end

  struct FileEvent
    include JSON::Serializable

    property uri : DocumentUri
    property type : Int32
  end

  module FileChangeType
    Created = 1
    Changed = 2
    Deleted = 3
  end

  # workspace/workspaceFolders result
  alias WorkspaceFoldersResult = Array(WorkspaceFolder)?

  # Progress support

  struct ProgressParams
    include JSON::Serializable

    property token : ProgressToken
    property value : JSON::Any

    def initialize(@token : ProgressToken, @value : JSON::Any)
    end
  end

  struct WorkDoneProgressBegin
    include JSON::Serializable

    property kind : String = "begin"
    property title : String
    property? cancellable : Bool?
    property? message : String?
    property? percentage : Int32?

    def initialize(
      @title : String,
      @cancellable : Bool? = nil,
      @message : String? = nil,
      @percentage : Int32? = nil,
    )
    end
  end

  struct WorkDoneProgressReport
    include JSON::Serializable

    property kind : String = "report"
    property? cancellable : Bool?
    property? message : String?
    property? percentage : Int32?

    def initialize(
      @message : String? = nil,
      @percentage : Int32? = nil,
      @cancellable : Bool? = nil,
    )
    end
  end

  struct WorkDoneProgressEnd
    include JSON::Serializable

    property kind : String = "end"
    property? message : String?

    def initialize(@message : String? = nil)
    end
  end

  # ---------------------------------------------------------------------------
  # Semantic tokens
  # ---------------------------------------------------------------------------

  struct SemanticTokensLegend
    include JSON::Serializable

    @[JSON::Field(key: "tokenTypes")]
    property token_types : Array(String)

    @[JSON::Field(key: "tokenModifiers")]
    property token_modifiers : Array(String)

    def initialize(@token_types : Array(String), @token_modifiers : Array(String))
    end
  end

  struct SemanticTokensOptions
    include JSON::Serializable

    property legend : SemanticTokensLegend
    property range : Bool
    property full : Bool

    def initialize(@legend : SemanticTokensLegend, @range : Bool = true, @full : Bool = true)
    end
  end

  struct SemanticTokens
    include JSON::Serializable

    @[JSON::Field(key: "resultId")]
    property? result_id : String?
    property data : Array(UInt32)

    def initialize(@data : Array(UInt32), @result_id : String? = nil)
    end
  end

  struct SemanticTokensParams
    include JSON::Serializable

    @[JSON::Field(key: "textDocument")]
    property text_document : TextDocumentIdentifier
  end

  struct SemanticTokensRangeParams
    include JSON::Serializable

    @[JSON::Field(key: "textDocument")]
    property text_document : TextDocumentIdentifier
    property range : Range
  end

  # ---------------------------------------------------------------------------
  # Inlay hints
  # ---------------------------------------------------------------------------

  enum InlayHintKind
    Type      = 1
    Parameter = 2

    def to_json(json : JSON::Builder)
      json.number(value)
    end
  end

  struct InlayHint
    include JSON::Serializable

    property position : Position
    property label : String
    property kind : InlayHintKind?
    property? tooltip : String?
    @[JSON::Field(key: "paddingLeft")]
    property? padding_left : Bool?
    @[JSON::Field(key: "paddingRight")]
    property? padding_right : Bool?

    def initialize(
      @position : Position,
      @label : String,
      @kind : InlayHintKind? = nil,
      @tooltip : String? = nil,
      @padding_left : Bool? = nil,
      @padding_right : Bool? = nil,
    )
    end
  end

  struct InlayHintOptions
    include JSON::Serializable

    @[JSON::Field(key: "resolveProvider")]
    property? resolve_provider : Bool?

    def initialize(@resolve_provider : Bool? = nil)
    end
  end

  struct InlayHintParams
    include JSON::Serializable

    @[JSON::Field(key: "textDocument")]
    property text_document : TextDocumentIdentifier
    property range : Range
  end

  # ---------------------------------------------------------------------------
  # Pull-model diagnostics (LSP §3.17)
  # ---------------------------------------------------------------------------

  # Trigger kind for textDocument/diagnostic requests.
  enum DiagnosticTriggerKind
    Invoked    = 1
    Automatic  = 2

    def to_json(json : JSON::Builder)
      json.number(value)
    end
  end

  # Parameters for a textDocument/diagnostic request.
  struct DocumentDiagnosticParams
    include JSON::Serializable

    @[JSON::Field(key: "textDocument")]
    property text_document : TextDocumentIdentifier
    @[JSON::Field(key: "identifier")]
    property? identifier : String?
    @[JSON::Field(key: "previousResultId")]
    property? previous_result_id : String?
  end

  # A full diagnostic report returned by textDocument/diagnostic.
  struct FullDocumentDiagnosticReport
    include JSON::Serializable

    property kind : String = "full"
    @[JSON::Field(key: "resultId")]
    property? result_id : String?
    property items : Array(Diagnostic)

    def initialize(@items : Array(Diagnostic), @result_id : String? = nil)
      @kind = "full"
    end
  end

  # An unchanged diagnostic report (client already has the current results).
  struct UnchangedDocumentDiagnosticReport
    include JSON::Serializable

    property kind : String = "unchanged"
    @[JSON::Field(key: "resultId")]
    property result_id : String

    def initialize(@result_id : String)
      @kind = "unchanged"
    end
  end

  # Parameters for a workspace/diagnostic request.
  struct WorkspaceDiagnosticParams
    include JSON::Serializable

    @[JSON::Field(key: "identifier")]
    property? identifier : String?
    @[JSON::Field(key: "previousResultIds")]
    property? previous_result_ids : Array(PreviousResultId)?
  end

  struct PreviousResultId
    include JSON::Serializable

    property uri : DocumentUri
    property value : String

    def initialize(@uri : DocumentUri, @value : String)
    end
  end

  # An item inside a workspace diagnostic report.
  struct WorkspaceFullDocumentDiagnosticReport
    include JSON::Serializable

    property kind : String = "full"
    property uri : DocumentUri
    property version : Int32?
    @[JSON::Field(key: "resultId")]
    property? result_id : String?
    property items : Array(Diagnostic)

    def initialize(
      @uri : DocumentUri,
      @items : Array(Diagnostic),
      @version : Int32? = nil,
      @result_id : String? = nil,
    )
      @kind = "full"
    end
  end

  # The full workspace diagnostic report.
  struct WorkspaceDiagnosticReport
    include JSON::Serializable

    property items : Array(WorkspaceFullDocumentDiagnosticReport)

    def initialize(@items : Array(WorkspaceFullDocumentDiagnosticReport))
    end
  end
end

# ---------------------------------------------------------------------------
# Color presentation (textDocument/colorPresentation)
# ---------------------------------------------------------------------------

module LSP
  struct Color
    include JSON::Serializable

    property red : Float64
    property green : Float64
    property blue : Float64
    property alpha : Float64

    def initialize(@red : Float64, @green : Float64, @blue : Float64, @alpha : Float64 = 1.0)
    end
  end

  struct ColorInformation
    include JSON::Serializable

    property range : Range
    property color : Color

    def initialize(@range : Range, @color : Color)
    end
  end

  struct ColorPresentationParams
    include JSON::Serializable

    @[JSON::Field(key: "textDocument")]
    property text_document : TextDocumentIdentifier
    property range : Range
    property color : Color
  end

  struct ColorPresentation
    include JSON::Serializable

    property label : String
    @[JSON::Field(key: "textEdit")]
    property? text_edit : TextEdit?
    @[JSON::Field(key: "additionalTextEdits")]
    property? additional_text_edits : Array(TextEdit)?

    def initialize(@label : String, @text_edit : TextEdit? = nil)
    end
  end
end
