// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package golang

import (
	"go/ast"
	"go/token"
	"strconv"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/gopls/internal/cache/parsego"
	"golang.org/x/tools/gopls/internal/file"
	"golang.org/x/tools/gopls/internal/protocol"
	"golang.org/x/tools/gopls/internal/util/bug"
	"golang.org/x/tools/gopls/internal/util/safetoken"
	"golang.org/x/tools/internal/diff"
)

// ConvertStringLiteral reports whether we can convert between raw and interpreted
// string literals in the [start, end), along with a CodeAction containing the edits.
//
// Only the following conditions are true, the action in result is valid
//   - [start, end) is enclosed by a string literal
//   - if the string is interpreted string, need check whether the convert is allowed
func ConvertStringLiteral(pgf *parsego.File, fh file.Handle, rng protocol.Range) (protocol.CodeAction, bool) {
	startPos, endPos, err := pgf.RangePos(rng)
	if err != nil {
		return protocol.CodeAction{}, false // e.g. invalid range
	}
	path, _ := astutil.PathEnclosingInterval(pgf.File, startPos, endPos)
	lit, ok := path[0].(*ast.BasicLit)
	if !ok || lit.Kind != token.STRING {
		return protocol.CodeAction{}, false
	}

	str, err := strconv.Unquote(lit.Value)
	if err != nil {
		return protocol.CodeAction{}, false
	}

	interpreted := lit.Value[0] == '"'
	// Not all "..." strings can be represented as `...` strings.
	if interpreted && !strconv.CanBackquote(strings.ReplaceAll(str, "\n", "")) {
		return protocol.CodeAction{}, false
	}

	var (
		title   string
		newText string
	)
	if interpreted {
		title = "Convert to raw string literal"
		newText = "`" + str + "`"
	} else {
		title = "Convert to interpreted string literal"
		newText = strconv.Quote(str)
	}

	start, end, err := safetoken.Offsets(pgf.Tok, lit.Pos(), lit.End())
	if err != nil {
		bug.Reportf("failed to get string literal offset by token.Pos:%v", err)
		return protocol.CodeAction{}, false
	}
	edits := []diff.Edit{{
		Start: start,
		End:   end,
		New:   newText,
	}}
	pedits, err := protocol.EditsFromDiffEdits(pgf.Mapper, edits)
	if err != nil {
		bug.Reportf("failed to convert diff.Edit to protocol.TextEdit:%v", err)
		return protocol.CodeAction{}, false
	}

	return protocol.CodeAction{
		Title: title,
		Kind:  protocol.RefactorRewrite,
		Edit: &protocol.WorkspaceEdit{
			DocumentChanges: documentChanges(fh, pedits),
		},
	}, true
}
