package template

import (
	"strings"
)

// CompileTemplate scans once, emitting:
// - data blocks: inner content between a line that's exactly "{" and a line that's exactly "}"
// - matching blocks: gaps between data blocks (excluding the brace lines themselves)
func CompileTemplate(templateSource string) ([]Block, error) {
	templateSource = strings.TrimSuffix(templateSource, "\n")

	var out []Block
	var curlyIndex int

	const OPENING = '{'
	const CLOSING = '}'

	var start int
	var blockType BlockType
	template := NewSlice(templateSource)

	if template.Len() > 0 && template.At(0) == OPENING {
		blockType = DataBlock
	} else {
		blockType = MatchingBlock
	}

	for i, r := range template.Chars() {

		nextCurlyIndex := curlyIndex

		switch r {
		case OPENING:
			nextCurlyIndex++
		case CLOSING:
			nextCurlyIndex--
		}

		if curlyIndex == 0 && nextCurlyIndex == 1 {
			if i > start {
				block, err := ParseTemplateBlock(template.Slice(start, i), blockType)
				if err != nil {
					return nil, NewErrorf("cannot parse block @pos -> %w", err).WithPosition(start, i)
				}
				out = append(out, block)
			}
			start = i
			blockType = DataBlock
		} else if curlyIndex == 1 && nextCurlyIndex == 0 {
			if i > start {
				block, err := ParseTemplateBlock(template.Slice(start, i+1), blockType)
				if err != nil {
					return nil, NewErrorf("cannot parse block @pos -> %w", err).WithPosition(start, i+1)
				}
				out = append(out, block)
			}

			nextChar := ' '
			if i+1 < template.Len() {
				nextChar = rune(template.At(i + 1))
			}

			if nextChar == OPENING {
				start = i + 1
				blockType = DataBlock
			} else {
				start = i + 1
				blockType = MatchingBlock
			}
		}

		curlyIndex = nextCurlyIndex
	}

	if curlyIndex != 0 {
		return nil, NewErrorf("unclosed block").WithPosition(start, template.Len())
	}

	if start < template.Len() {
		block, err := ParseTemplateBlock(template.Slice(start, template.Len()), blockType)
		if err != nil {
			return nil, NewErrorf("cannot parse final block @pos -> %w", err).WithPosition(start, template.Len())
		}
		out = append(out, block)
	}

	return out, nil
}
