// SPDX-FileCopyrightText: 2019 The Go Language Server Authors
// SPDX-License-Identifier: BSD-3-Clause

package protocol

import (
	"github.com/a-h/templ/lsp/uri"
)

// DocumentURI represents the URI of a document.
//
// Many of the interfaces contain fields that correspond to the URI of a document.
// For clarity, the type of such a field is declared as a DocumentURI.
// Over the wire, it will still be transferred as a string, but this guarantees
// that the contents of that string can be parsed as a valid URI.
type DocumentURI = uri.URI

// URI a tagging interface for normal non document URIs.
//
// @since 3.16.0.
type URI = uri.URI

// EOL denotes represents the character offset.
var EOL = []string{"\n", "\r\n", "\r"}

// Position represents a text document expressed as zero-based line and zero-based character offset.
//
// The offsets are 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.
//
// Positions are line end character agnostic. So you can not specify a position that
// denotes "\r|\n" or "\n|" where "|" represents the character offset.
//
// Position is between two characters like an "insert" cursor in a editor.
// Special values like for example "-1" to denote the end of a line are not supported.
type Position struct {
	// 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.
	Line uint32 `json:"line"`

	// Character offset on a line in a document (zero-based).
	//
	// Assuming that the line is represented as a string, the Character value represents the gap between the
	// "character" and "character + 1".
	//
	// If the character value is greater than the line length it defaults back to the line length.
	// If a line number is negative, it defaults to 0.
	Character uint32 `json:"character"`
}

// Range represents a text document expressed as (zero-based) start and end positions.
//
// A range is comparable to a selection in an editor. Therefore the end position is exclusive.
// 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.
type Range struct {
	// Start is the range's start position.
	Start Position `json:"start"`

	// End is the range's end position.
	End Position `json:"end"`
}

// Location represents a location inside a resource, such as a line inside a text file.
type Location struct {
	URI   DocumentURI `json:"uri"`
	Range Range       `json:"range"`
}

// LocationLink represents a link between a source and a target location.
type LocationLink struct {
	// OriginSelectionRange span of the origin of this link.
	//
	// Used as the underlined span for mouse interaction. Defaults to the word range at the mouse position.
	OriginSelectionRange *Range `json:"originSelectionRange,omitempty"`

	// TargetURI is the target resource identifier of this link.
	TargetURI DocumentURI `json:"targetUri"`

	// TargetRange is the full target range of this link.
	//
	// If the target for example is a symbol then target range is the range enclosing this symbol not including
	// leading/trailing whitespace but everything else like comments.
	//
	// This information is typically used to highlight the range in the editor.
	TargetRange Range `json:"targetRange"`

	// TargetSelectionRange is the range that should be selected and revealed when this link is being followed,
	// e.g the name of a function.
	//
	// Must be contained by the the TargetRange. See also DocumentSymbol#range
	TargetSelectionRange Range `json:"targetSelectionRange"`
}

// Command represents a reference to a command. Provides a title which will be used to represent a command in the UI.
//
// Commands are identified by a string identifier.
// The recommended way to handle commands is to implement their execution on the server side if the client and
// server provides the corresponding capabilities.
//
// Alternatively the tool extension code could handle the command. The protocol currently doesn't specify
// a set of well-known commands.
type Command struct {
	// Title of the command, like `save`.
	Title string `json:"title"`

	// Command is the identifier of the actual command handler.
	Command string `json:"command"`

	// Arguments that the command handler should be invoked with.
	Arguments []any `json:"arguments,omitempty"`
}

// TextEdit is a textual edit applicable to a text document.
type TextEdit struct {
	// Range is the range of the text document to be manipulated.
	//
	// To insert text into a document create a range where start == end.
	Range Range `json:"range"`

	// NewText is the string to be inserted. For delete operations use an
	// empty string.
	NewText string `json:"newText"`
}

// ChangeAnnotation is the additional information that describes document changes.
//
// @since 3.16.0.
type ChangeAnnotation struct {
	// Label a human-readable string describing the actual change.
	// The string is rendered prominent in the user interface.
	Label string `json:"label"`

	// NeedsConfirmation is a flag which indicates that user confirmation is needed
	// before applying the change.
	NeedsConfirmation bool `json:"needsConfirmation,omitempty"`

	// Description is a human-readable string which is rendered less prominent in
	// the user interface.
	Description string `json:"description,omitempty"`
}

// ChangeAnnotationIdentifier an identifier referring to a change annotation managed by a workspace
// edit.
//
// @since 3.16.0.
type ChangeAnnotationIdentifier string

// AnnotatedTextEdit is a special text edit with an additional change annotation.
//
// @since 3.16.0.
type AnnotatedTextEdit struct {
	TextEdit

	// AnnotationID is the actual annotation identifier.
	AnnotationID ChangeAnnotationIdentifier `json:"annotationId"`
}

// TextDocumentEdit describes textual changes on a single text document.
//
// The TextDocument is referred to as a OptionalVersionedTextDocumentIdentifier to allow clients to check the
// text document version before an edit is applied.
//
// TextDocumentEdit describes all changes on a version "Si" and after they are applied move the document to
// version "Si+1".
// So the creator of a TextDocumentEdit doesn't need to sort the array or do any kind of ordering. However the
// edits must be non overlapping.
type TextDocumentEdit struct {
	// TextDocument is the text document to change.
	TextDocument OptionalVersionedTextDocumentIdentifier `json:"textDocument"`

	// Edits is the edits to be applied.
	//
	// @since 3.16.0 - support for AnnotatedTextEdit.
	// This is guarded by the client capability Workspace.WorkspaceEdit.ChangeAnnotationSupport.
	Edits []TextEdit `json:"edits"` // []TextEdit | []AnnotatedTextEdit
}

// ResourceOperationKind is the file event type.
type ResourceOperationKind string

const (
	// CreateResourceOperation supports creating new files and folders.
	CreateResourceOperation ResourceOperationKind = "create"

	// RenameResourceOperation supports renaming existing files and folders.
	RenameResourceOperation ResourceOperationKind = "rename"

	// DeleteResourceOperation supports deleting existing files and folders.
	DeleteResourceOperation ResourceOperationKind = "delete"
)

// CreateFileOptions represents an options to create a file.
type CreateFileOptions struct {
	// Overwrite existing file. Overwrite wins over `ignoreIfExists`.
	Overwrite bool `json:"overwrite,omitempty"`

	// IgnoreIfExists ignore if exists.
	IgnoreIfExists bool `json:"ignoreIfExists,omitempty"`
}

// CreateFile represents a create file operation.
type CreateFile struct {
	// Kind a create.
	Kind ResourceOperationKind `json:"kind"` // should be `create`

	// URI is the resource to create.
	URI DocumentURI `json:"uri"`

	// Options additional options.
	Options *CreateFileOptions `json:"options,omitempty"`

	// AnnotationID an optional annotation identifier describing the operation.
	//
	// @since 3.16.0.
	AnnotationID ChangeAnnotationIdentifier `json:"annotationId,omitempty"`
}

// RenameFileOptions represents a rename file options.
type RenameFileOptions struct {
	// Overwrite target if existing. Overwrite wins over `ignoreIfExists`.
	Overwrite bool `json:"overwrite,omitempty"`

	// IgnoreIfExists ignores if target exists.
	IgnoreIfExists bool `json:"ignoreIfExists,omitempty"`
}

// RenameFile represents a rename file operation.
type RenameFile struct {
	// Kind a rename.
	Kind ResourceOperationKind `json:"kind"` // should be `rename`

	// OldURI is the old (existing) location.
	OldURI DocumentURI `json:"oldUri"`

	// NewURI is the new location.
	NewURI DocumentURI `json:"newUri"`

	// Options rename options.
	Options *RenameFileOptions `json:"options,omitempty"`

	// AnnotationID an optional annotation identifier describing the operation.
	//
	// @since 3.16.0.
	AnnotationID ChangeAnnotationIdentifier `json:"annotationId,omitempty"`
}

// DeleteFileOptions represents a delete file options.
type DeleteFileOptions struct {
	// Recursive delete the content recursively if a folder is denoted.
	Recursive bool `json:"recursive,omitempty"`

	// IgnoreIfNotExists ignore the operation if the file doesn't exist.
	IgnoreIfNotExists bool `json:"ignoreIfNotExists,omitempty"`
}

// DeleteFile represents a delete file operation.
type DeleteFile struct {
	// Kind is a delete.
	Kind ResourceOperationKind `json:"kind"` // should be `delete`

	// URI is the file to delete.
	URI DocumentURI `json:"uri"`

	// Options delete options.
	Options *DeleteFileOptions `json:"options,omitempty"`

	// AnnotationID an optional annotation identifier describing the operation.
	//
	// @since 3.16.0.
	AnnotationID ChangeAnnotationIdentifier `json:"annotationId,omitempty"`
}

// WorkspaceEdit represent a changes to many resources managed in the workspace.
//
// The edit should either provide changes or documentChanges.
// If the client can handle versioned document edits and if documentChanges are present, the latter are preferred over
// changes.
type WorkspaceEdit struct {
	// Changes holds changes to existing resources.
	Changes map[DocumentURI][]TextEdit `json:"changes,omitempty"`

	// DocumentChanges depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes
	// are either an array of `TextDocumentEdit`s to express changes to n different text documents
	// where each text document edit addresses a specific version of a text document. Or it can contain
	// above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.
	//
	// Whether a client supports versioned document edits is expressed via
	// `workspace.workspaceEdit.documentChanges` client capability.
	//
	// If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then
	// only plain `TextEdit`s using the `changes` property are supported.
	DocumentChanges []TextDocumentEdit `json:"documentChanges,omitempty"`

	// ChangeAnnotations is a map of change annotations that can be referenced in
	// "AnnotatedTextEdit"s or create, rename and delete file / folder
	// operations.
	//
	// Whether clients honor this property depends on the client capability
	// "workspace.changeAnnotationSupport".
	//
	// @since 3.16.0.
	ChangeAnnotations map[ChangeAnnotationIdentifier]ChangeAnnotation `json:"changeAnnotations,omitempty"`
}

// TextDocumentIdentifier indicates the using a URI. On the protocol level, URIs are passed as strings.
type TextDocumentIdentifier struct {
	// URI is the text document's URI.
	URI DocumentURI `json:"uri"`
}

// TextDocumentItem represent an item to transfer a text document from the client to the server.
type TextDocumentItem struct {
	// URI is the text document's URI.
	URI DocumentURI `json:"uri"`

	// LanguageID is the text document's language identifier.
	LanguageID LanguageIdentifier `json:"languageId"`

	// Version is the version number of this document (it will increase after each
	// change, including undo/redo).
	Version int32 `json:"version"`

	// Text is the content of the opened text document.
	Text string `json:"text"`
}

// LanguageIdentifier represent a text document's language identifier.
type LanguageIdentifier string

const (
	// ABAPLanguage ABAP Language.
	ABAPLanguage LanguageIdentifier = "abap"

	// BatLanguage Windows Bat Language.
	BatLanguage LanguageIdentifier = "bat"

	// BibtexLanguage BibTeX Language.
	BibtexLanguage LanguageIdentifier = "bibtex"

	// ClojureLanguage Clojure Language.
	ClojureLanguage LanguageIdentifier = "clojure"

	// CoffeescriptLanguage CoffeeScript Language.
	CoffeeScriptLanguage LanguageIdentifier = "coffeescript"

	// CLanguage C Language.
	CLanguage LanguageIdentifier = "c"

	// CppLanguage C++ Language.
	CppLanguage LanguageIdentifier = "cpp"

	// CsharpLanguage C# Language.
	CsharpLanguage LanguageIdentifier = "csharp"

	// CSSLanguage CSS Language.
	CSSLanguage LanguageIdentifier = "css"

	// DiffLanguage Diff Language.
	DiffLanguage LanguageIdentifier = "diff"

	// DartLanguage Dart Language.
	DartLanguage LanguageIdentifier = "dart"

	// DockerfileLanguage Dockerfile Language.
	DockerfileLanguage LanguageIdentifier = "dockerfile"

	// ElixirLanguage Elixir Language.
	ElixirLanguage LanguageIdentifier = "elixir"

	// ErlangLanguage Erlang Language.
	ErlangLanguage LanguageIdentifier = "erlang"

	// FsharpLanguage F# Language.
	FsharpLanguage LanguageIdentifier = "fsharp"

	// GitCommitLanguage Git Language.
	GitCommitLanguage LanguageIdentifier = "git-commit"

	// GitRebaseLanguage Git Language.
	GitRebaseLanguage LanguageIdentifier = "git-rebase"

	// GoLanguage Go Language.
	GoLanguage LanguageIdentifier = "go"

	// GroovyLanguage Groovy Language.
	GroovyLanguage LanguageIdentifier = "groovy"

	// HandlebarsLanguage Handlebars Language.
	HandlebarsLanguage LanguageIdentifier = "handlebars"

	// HTMLLanguage HTML Language.
	HTMLLanguage LanguageIdentifier = "html"

	// IniLanguage Ini Language.
	IniLanguage LanguageIdentifier = "ini"

	// JavaLanguage Java Language.
	JavaLanguage LanguageIdentifier = "java"

	// JavaScriptLanguage JavaScript Language.
	JavaScriptLanguage LanguageIdentifier = "javascript"

	// JavaScriptReactLanguage JavaScript React Language.
	JavaScriptReactLanguage LanguageIdentifier = "javascriptreact"

	// JSONLanguage JSON Language.
	JSONLanguage LanguageIdentifier = "json"

	// LatexLanguage LaTeX Language.
	LatexLanguage LanguageIdentifier = "latex"

	// LessLanguage Less Language.
	LessLanguage LanguageIdentifier = "less"

	// LuaLanguage Lua Language.
	LuaLanguage LanguageIdentifier = "lua"

	// MakefileLanguage Makefile Language.
	MakefileLanguage LanguageIdentifier = "makefile"

	// MarkdownLanguage Markdown Language.
	MarkdownLanguage LanguageIdentifier = "markdown"

	// ObjectiveCLanguage Objective-C Language.
	ObjectiveCLanguage LanguageIdentifier = "objective-c"

	// ObjectiveCppLanguage Objective-C++ Language.
	ObjectiveCppLanguage LanguageIdentifier = "objective-cpp"

	// PerlLanguage Perl Language.
	PerlLanguage LanguageIdentifier = "perl"

	// Perl6Language Perl Language.
	Perl6Language LanguageIdentifier = "perl6"

	// PHPLanguage PHP Language.
	PHPLanguage LanguageIdentifier = "php"

	// PowershellLanguage Powershell Language.
	PowershellLanguage LanguageIdentifier = "powershell"

	// JadeLanguage Pug Language.
	JadeLanguage LanguageIdentifier = "jade"

	// PythonLanguage Python Language.
	PythonLanguage LanguageIdentifier = "python"

	// RLanguage R Language.
	RLanguage LanguageIdentifier = "r"

	// RazorLanguage Razor(cshtml) Language.
	RazorLanguage LanguageIdentifier = "razor"

	// RubyLanguage Ruby Language.
	RubyLanguage LanguageIdentifier = "ruby"

	// RustLanguage Rust Language.
	RustLanguage LanguageIdentifier = "rust"

	// SCSSLanguage SCSS Languages syntax using curly brackets.
	SCSSLanguage LanguageIdentifier = "scss"

	// SASSLanguage SCSS Languages indented syntax.
	SASSLanguage LanguageIdentifier = "sass"

	// ScalaLanguage Scala Language.
	ScalaLanguage LanguageIdentifier = "scala"

	// ShaderlabLanguage ShaderLab Language.
	ShaderlabLanguage LanguageIdentifier = "shaderlab"

	// ShellscriptLanguage Shell Script (Bash) Language.
	ShellscriptLanguage LanguageIdentifier = "shellscript"

	// SQLLanguage SQL Language.
	SQLLanguage LanguageIdentifier = "sql"

	// SwiftLanguage Swift Language.
	SwiftLanguage LanguageIdentifier = "swift"

	// TypeScriptLanguage TypeScript Language.
	TypeScriptLanguage LanguageIdentifier = "typescript"

	// TypeScriptReactLanguage TypeScript React Language.
	TypeScriptReactLanguage LanguageIdentifier = "typescriptreact"

	// TeXLanguage TeX Language.
	TeXLanguage LanguageIdentifier = "tex"

	// VBLanguage Visual Basic Language.
	VBLanguage LanguageIdentifier = "vb"

	// XMLLanguage XML Language.
	XMLLanguage LanguageIdentifier = "xml"

	// XslLanguage XSL Language.
	XslLanguage LanguageIdentifier = "xsl"

	// YamlLanguage YAML Language.
	YamlLanguage LanguageIdentifier = "yaml"
)

// languageIdentifierMap map of LanguageIdentifiers.
var languageIdentifierMap = map[string]LanguageIdentifier{
	"abap":            ABAPLanguage,
	"bat":             BatLanguage,
	"bibtex":          BibtexLanguage,
	"clojure":         ClojureLanguage,
	"coffeescript":    CoffeeScriptLanguage,
	"c":               CLanguage,
	"cpp":             CppLanguage,
	"csharp":          CsharpLanguage,
	"css":             CSSLanguage,
	"diff":            DiffLanguage,
	"dart":            DartLanguage,
	"dockerfile":      DockerfileLanguage,
	"elixir":          ElixirLanguage,
	"erlang":          ErlangLanguage,
	"fsharp":          FsharpLanguage,
	"git-commit":      GitCommitLanguage,
	"git-rebase":      GitRebaseLanguage,
	"go":              GoLanguage,
	"groovy":          GroovyLanguage,
	"handlebars":      HandlebarsLanguage,
	"html":            HTMLLanguage,
	"ini":             IniLanguage,
	"java":            JavaLanguage,
	"javascript":      JavaScriptLanguage,
	"javascriptreact": JavaScriptReactLanguage,
	"json":            JSONLanguage,
	"latex":           LatexLanguage,
	"less":            LessLanguage,
	"lua":             LuaLanguage,
	"makefile":        MakefileLanguage,
	"markdown":        MarkdownLanguage,
	"objective-c":     ObjectiveCLanguage,
	"objective-cpp":   ObjectiveCppLanguage,
	"perl":            PerlLanguage,
	"perl6":           Perl6Language,
	"php":             PHPLanguage,
	"powershell":      PowershellLanguage,
	"jade":            JadeLanguage,
	"python":          PythonLanguage,
	"r":               RLanguage,
	"razor":           RazorLanguage,
	"ruby":            RubyLanguage,
	"rust":            RustLanguage,
	"scss":            SCSSLanguage,
	"sass":            SASSLanguage,
	"scala":           ScalaLanguage,
	"shaderlab":       ShaderlabLanguage,
	"shellscript":     ShellscriptLanguage,
	"sql":             SQLLanguage,
	"swift":           SwiftLanguage,
	"typescript":      TypeScriptLanguage,
	"typescriptreact": TypeScriptReactLanguage,
	"tex":             TeXLanguage,
	"vb":              VBLanguage,
	"xml":             XMLLanguage,
	"xsl":             XslLanguage,
	"yaml":            YamlLanguage,
}

// ToLanguageIdentifier converts ft to LanguageIdentifier.
func ToLanguageIdentifier(ft string) LanguageIdentifier {
	langID, ok := languageIdentifierMap[ft]
	if ok {
		return langID
	}

	return LanguageIdentifier(ft)
}

// VersionedTextDocumentIdentifier represents an identifier to denote a specific version of a text document.
//
// This information usually flows from the client to the server.
type VersionedTextDocumentIdentifier struct {
	TextDocumentIdentifier

	// Version is the version number of this document.
	//
	// The version number of a document will increase after each change, including
	// undo/redo. The number doesn't need to be consecutive.
	Version int32 `json:"version"`
}

// OptionalVersionedTextDocumentIdentifier represents an identifier which optionally denotes a specific version of
// a text document.
//
// This information usually flows from the server to the client.
//
// @since 3.16.0.
type OptionalVersionedTextDocumentIdentifier struct {
	TextDocumentIdentifier

	// Version is the version number of this document. If an optional versioned text document
	// identifier is sent from the server to the client and the file is not
	// open in the editor (the server has not received an open notification
	// before) the server can send `null` to indicate that the version is
	// known and the content on disk is the master (as specified with document
	// content ownership).
	//
	// The version number of a document will increase after each change,
	// including undo/redo. The number doesn't need to be consecutive.
	Version *int32 `json:"version"` // int32 | null
}

// TextDocumentPositionParams is a parameter literal used in requests to pass a text document and a position
// inside that document.
//
// It is up to the client to decide how a selection is converted into a position when issuing a request for a text
// document.
//
// The client can for example honor or ignore the selection direction to make LSP request consistent with features
// implemented internally.
type TextDocumentPositionParams struct {
	// TextDocument is the text document.
	TextDocument TextDocumentIdentifier `json:"textDocument"`

	// Position is the position inside the text document.
	Position Position `json:"position"`
}

// DocumentFilter is a document filter denotes a document through properties like language, scheme or pattern.
//
// An example is a filter that applies to TypeScript files on disk.
type DocumentFilter struct {
	// Language a language id, like `typescript`.
	Language string `json:"language,omitempty"`

	// Scheme a URI scheme, like `file` or `untitled`.
	Scheme string `json:"scheme,omitempty"`

	// Pattern a glob pattern, like `*.{ts,js}`.
	//
	// Glob patterns can have the following syntax:
	//  "*"
	// "*" to match one or more characters in a path segment
	//  "?"
	// "?" to match on one character in a path segment
	//  "**"
	// "**" to match any number of path segments, including none
	//  "{}"
	// "{}" to group conditions (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files)
	//  "[]"
	// "[]" to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
	//  "[!...]"
	// "[!...]" to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
	Pattern string `json:"pattern,omitempty"`
}

// DocumentSelector is a document selector is the combination of one or more document filters.
type DocumentSelector []*DocumentFilter

// MarkupKind 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.
type MarkupKind string

const (
	// PlainText is supported as a content format.
	PlainText MarkupKind = "plaintext"

	// Markdown is supported as a content format.
	Markdown MarkupKind = "markdown"
)

// MarkupContent 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:
//
//	let markdown: MarkdownContent = {
//	 kind: MarkupKind.Markdown,
//	  value: [
//	  	'# Header',
//	  	'Some text',
//	  	'```typescript',
//	  'someCode();',
//	  '```'
//	  ].join('\n')
//	};
//
// NOTE: clients might sanitize the return markdown. A client could decide to
// remove HTML from the markdown to avoid script execution.
type MarkupContent struct {
	// Kind is the type of the Markup
	Kind MarkupKind `json:"kind"`

	// Value is the content itself
	Value string `json:"value"`
}
