// Copyright 2025 CloudWeGo Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lsp

import (
	"context"
	"encoding/json"
	"fmt"
	"path/filepath"
	"strings"

	sitter "github.com/smacker/go-tree-sitter"

	"github.com/sourcegraph/go-lsp"
)

// The SymbolKind values are defined at https://microsoft.github.io/language-server-protocol/specification.
const (
	SKUnknown       SymbolKind = 1
	SKFile          SymbolKind = 1
	SKModule        SymbolKind = 2
	SKNamespace     SymbolKind = 3
	SKPackage       SymbolKind = 4
	SKClass         SymbolKind = 5
	SKMethod        SymbolKind = 6
	SKProperty      SymbolKind = 7
	SKField         SymbolKind = 8
	SKConstructor   SymbolKind = 9
	SKEnum          SymbolKind = 10
	SKInterface     SymbolKind = 11
	SKFunction      SymbolKind = 12
	SKVariable      SymbolKind = 13
	SKConstant      SymbolKind = 14
	SKString        SymbolKind = 15
	SKNumber        SymbolKind = 16
	SKBoolean       SymbolKind = 17
	SKArray         SymbolKind = 18
	SKObject        SymbolKind = 19
	SKKey           SymbolKind = 20
	SKNull          SymbolKind = 21
	SKEnumMember    SymbolKind = 22
	SKStruct        SymbolKind = 23
	SKEvent         SymbolKind = 24
	SKOperator      SymbolKind = 25
	SKTypeParameter SymbolKind = 26
)

type SymbolKind = lsp.SymbolKind

type SymbolRole int

const (
	DEFINITION SymbolRole = 1
	REFERENCE  SymbolRole = 2
)

type Position lsp.Position

func (r Position) Less(s Position) bool {
	if r.Line != s.Line {
		return r.Line < s.Line
	}
	return r.Character < s.Character
}

func (r Position) String() string {
	return fmt.Sprintf("%d:%d", r.Line, r.Character)
}

type Range struct {
	Start Position `json:"start"`
	End   Position `json:"end"`
}

func (r Range) String() string {
	return fmt.Sprintf("%s-%s", r.Start, r.End)
}

func (r Range) MarshalText() ([]byte, error) {
	return []byte(r.String()), nil
}

type _Range Range

func (r Range) MarshalJSON() ([]byte, error) {
	return json.Marshal(_Range(r))
}

func isPositionInRange(pos Position, r Range, close bool) bool {
	if pos.Line < r.Start.Line || pos.Line > r.End.Line {
		return false
	}
	if pos.Line == r.Start.Line && pos.Character < r.Start.Character {
		return false
	}
	if pos.Line == r.End.Line {
		if close {
			return pos.Character <= r.End.Character
		} else {
			return pos.Character < r.End.Character
		}
	}
	return true
}

func (a Range) Include(b Range) bool {
	return isPositionInRange(b.Start, a, false) && isPositionInRange(b.End, a, true)
}

type Location struct {
	URI   DocumentURI `json:"uri"`
	Range Range       `json:"range"`
}

func (l Location) String() string {
	return fmt.Sprintf("%s:%d:%d-%d:%d", l.URI, l.Range.Start.Line, l.Range.Start.Character, l.Range.End.Line, l.Range.End.Character)
}

var locationMarshalJSONInline = true

func SetLocationMarshalJSONInline(inline bool) {
	locationMarshalJSONInline = inline
}

type _Location Location

func (l Location) MarshalJSON() ([]byte, error) {
	if locationMarshalJSONInline {
		return []byte(fmt.Sprintf("%q", l.String())), nil
	}
	return json.Marshal(_Location(l))
}

func (l Location) MarshalText() ([]byte, error) {
	return []byte(l.String()), nil
}

func (a Location) Include(b Location) bool {
	if a == b {
		return true
	}
	if a.URI != b.URI {
		return false
	}
	return isPositionInRange(b.Range.Start, a.Range, false) && isPositionInRange(b.Range.End, a.Range, true)
}

type DocumentURI lsp.DocumentURI

func (l DocumentURI) File() string {
	return strings.TrimPrefix(string(l), "file://")
}

func NewURI(file string) DocumentURI {
	if !filepath.IsAbs(file) {
		file, _ = filepath.Abs(file)
	}
	return DocumentURI("file://" + file)
}

type TextDocumentItem struct {
	URI         DocumentURI               `json:"uri"`
	LanguageID  string                    `json:"languageId"`
	Version     int                       `json:"version"`
	Text        string                    `json:"text"`
	LineCounts  []int                     `json:"-"`
	Symbols     map[Range]*DocumentSymbol `json:"-"`
	Definitions map[Position][]Location   `json:"-"`
}

type DocumentSymbol struct {
	Name     string            `json:"name"`
	Kind     SymbolKind        `json:"kind"`
	Tags     []json.RawMessage `json:"tags"`
	Location Location          `json:"location"`
	Children []*DocumentSymbol `json:"children"`
	Text     string            `json:"text"`
	Tokens   []Token           `json:"tokens"`
	Node     *sitter.Node      `json:"-"`
	Role     SymbolRole        `json:"-"`
}

type TextDocumentPositionParams struct {
	/**
	 * The text document.
	 */
	TextDocument TextDocumentIdentifier `json:"textDocument"`

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

type TextDocumentIdentifier struct {
	/**
	 * The text document's URI.
	 */
	URI DocumentURI `json:"uri"`
}

type Hover struct {
	Contents []MarkedString `json:"contents"`
	Range    *Range         `json:"range,omitempty"`
}

type MarkedString markedString

type markedString struct {
	Language string `json:"language"`
	Value    string `json:"value"`

	isRawString bool
}

type WorkspaceSymbolParams struct {
	Query string `json:"query"`
	Limit int    `json:"limit"`
}

type SymbolInformation struct {
	Name          string     `json:"name"`
	Kind          SymbolKind `json:"kind"`
	Location      Location   `json:"location"`
	ContainerName string     `json:"containerName,omitempty"`
}

// TypeHierarchyItem represents a node in the type hierarchy tree.
//
// @since 3.17.0
type TypeHierarchyItem struct {
	Name           string      `json:"name"`
	Kind           SymbolKind  `json:"kind"`
	Detail         string      `json:"detail,omitempty"`
	URI            DocumentURI `json:"uri"`
	Range          Range       `json:"range"`
	SelectionRange Range       `json:"selectionRange"`
	Data           interface{} `json:"data,omitempty"`
}

func (cli *LSPClient) WorkspaceSymbols(ctx context.Context, query string) ([]DocumentSymbol, error) {
	req := WorkspaceSymbolParams{
		Query: query,
	}
	var resp []DocumentSymbol
	if err := cli.Call(ctx, "workspace/symbol", req, &resp); err != nil {
		return nil, err
	}
	return resp, nil
}
func (s *DocumentSymbol) MarshalJSON() ([]byte, error) {
	if s == nil {
		return []byte("null"), nil
	}
	r := *s
	if js, err := json.Marshal(r); err != nil {
		return nil, err
	} else {
		return js, nil
	}
}

func (s *DocumentSymbol) MarshalText() ([]byte, error) {
	return []byte(s.String()), nil
}

func (s *DocumentSymbol) String() string {
	if s == nil {
		return "null"
	}
	return fmt.Sprintf("%s %s %s", s.Name, s.Kind, s.Location)
}

type SemanticTokens struct {
	ResultID string   `json:"resultId"`
	Data     []uint32 `json:"data"`
}

type Token struct {
	Location  Location `json:"location"`
	Type      string   `json:"type"`
	Modifiers []string `json:"modifiers"`
	Text      string   `json:"text"`
}

func (t *Token) String() string {
	return fmt.Sprintf("%s %s %v %s", t.Text, t.Type, t.Modifiers, t.Location)
}

func (cli *LSPClient) Hover(ctx context.Context, uri DocumentURI, line, character int) (*Hover, error) {
	if cli.provider != nil {
		// The type assertion is safe because the provider is for the specific language.
		return cli.provider.Hover(ctx, cli, uri, line, character)
	}
	// Default hover implementation (or return an error if not supported)
	// Default implementation (or return an error if not supported)
	return nil, fmt.Errorf("Hover not supported for this language")
}

func (cli *LSPClient) Implementation(ctx context.Context, uri DocumentURI, pos Position) ([]Location, error) {
	if cli.provider != nil {
		return cli.provider.Implementation(ctx, cli, uri, pos)
	}
	// Default implementation (or return an error if not supported)
	return nil, fmt.Errorf("implementation not supported for this language")
}

func (cli *LSPClient) WorkspaceSearchSymbols(ctx context.Context, query string) ([]SymbolInformation, error) {
	if cli.provider != nil {
		return cli.provider.WorkspaceSearchSymbols(ctx, cli, query)
	}
	// Default implementation (or return an error if not supported)
	return nil, fmt.Errorf("WorkspaceSearchSymbols not supported for this language")
}

func (cli *LSPClient) PrepareTypeHierarchy(ctx context.Context, uri DocumentURI, pos Position) ([]TypeHierarchyItem, error) {
	if cli.provider != nil {
		return cli.provider.PrepareTypeHierarchy(ctx, cli, uri, pos)
	}
	// Default implementation (or return an error if not supported)
	return nil, fmt.Errorf("PrepareTypeHierarchy not supported for this language")
}

func (cli *LSPClient) TypeHierarchySupertypes(ctx context.Context, item TypeHierarchyItem) ([]TypeHierarchyItem, error) {
	if cli.provider != nil {
		return cli.provider.TypeHierarchySupertypes(ctx, cli, item)
	}
	// Default implementation (or return an error if not supported)
	return nil, fmt.Errorf("TypeHierarchySupertypes not supported for this language")
}

func (cli *LSPClient) TypeHierarchySubtypes(ctx context.Context, item TypeHierarchyItem) ([]TypeHierarchyItem, error) {
	if cli.provider != nil {
		return cli.provider.TypeHierarchySubtypes(ctx, cli, item)
	}
	// Default implementation (or return an error if not supported)
	return nil, fmt.Errorf("TypeHierarchySubtypes not supported for this language")
}
