// Copyright 2016-2020, Pulumi Corporation.
//
// 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
//
//     http://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 docs

import (
	"fmt"
	"strings"

	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)

const (
	beginCodeBlock = "<!--Start PulumiCodeChooser -->"
	endCodeBlock   = "<!--End PulumiCodeChooser -->"
)

type codeLocation struct {
	open  int
	close int
}

func getCodeSection(doc string) []codeLocation {
	var fences []codeLocation

	startIndex := 0
	for {
		open := strings.Index(doc[startIndex:], beginCodeBlock)
		if open == -1 {
			break
		}
		var fence codeLocation
		fence.open = startIndex + open
		startIndex += open + len(beginCodeBlock)
		closing := strings.Index(doc[startIndex:], endCodeBlock)

		contract.Assertf(closing != -1, "this should never happen: "+
			"there should be equal amounts of opening and closing code block markers")

		fence.close = startIndex + closing

		startIndex += closing + len(endCodeBlock)
		fences = append(fences, fence)
	}
	return fences
}

func markupBlock(block, supportedSnippetLanguages string) string {
	languages := []struct{ tag, choosable string }{
		{"typescript", "<div>\n<pulumi-choosable type=\"language\" values=\"javascript,typescript\">\n\n"},
		{"python", "<div>\n<pulumi-choosable type=\"language\" values=\"python\">\n\n"},
		{"go", "<div>\n<pulumi-choosable type=\"language\" values=\"go\">\n\n"},
		{"csharp", "<div>\n<pulumi-choosable type=\"language\" values=\"csharp\">\n\n"},
		{"java", "<div>\n<pulumi-choosable type=\"language\" values=\"java\">\n\n"},
		{"yaml", "<div>\n<pulumi-choosable type=\"language\" values=\"yaml\">\n\n"},
	}
	const (
		//nolint:lll
		chooserStartFmt = "<div>\n<pulumi-chooser type=\"language\" options=\"%s\"></pulumi-chooser>\n</div>\n"
		choosableEnd    = "</pulumi-choosable>\n</div>\n"
	)

	var markedUpBlock strings.Builder
	// first, append the start chooser
	markedUpBlock.WriteString(fmt.Sprintf(chooserStartFmt, supportedSnippetLanguages))

	for _, lang := range languages {
		// Add language specific open choosable
		markedUpBlock.WriteString(lang.choosable)
		// find our language - because we have no guarantee of order from our input, we need to find
		// both code fences and then append the content in the order that docsgen expects.
		start := strings.Index(block, "```"+lang.tag)
		if start == -1 {
			markedUpBlock.WriteString("```\n")
			markedUpBlock.WriteString(defaultMissingExampleSnippetPlaceholder)
			markedUpBlock.WriteString("\n```\n")
		} else {
			// find end index - this is the next code fence.
			endLangBlock := start + len("```"+lang.tag) + strings.Index(block[start+len("```"+lang.tag):], "```")
			// append code to block, and include code fences
			markedUpBlock.WriteString(block[start : endLangBlock+len("```")])
			markedUpBlock.WriteRune('\n')
		}
		// add closing choosable
		markedUpBlock.WriteString(choosableEnd)
	}
	return markedUpBlock.String()
}

func (dctx *docGenContext) processDescription(description, supportedSnippetLanguages string) docInfo {
	importDetails := ""
	parts := strings.Split(description, "\n\n## Import")
	if len(parts) > 1 {
		importDetails = parts[1]
		description = parts[0]
	}

	codeBlocks := getCodeSection(description)

	startIndex := 0
	var markedUpDescription string
	for _, block := range codeBlocks {
		// append text
		markedUpDescription += description[startIndex:block.open]
		codeBlock := description[block.open:block.close]
		// append marked up block
		markedUpDescription += markupBlock(codeBlock, supportedSnippetLanguages)
		startIndex = block.close + len(endCodeBlock)
	}
	// append remainder of description, if any
	markedUpDescription += description[startIndex:]

	return docInfo{
		description:   markedUpDescription,
		importDetails: importDetails,
	}
}