package plan

import (
	"fmt"
	"strings"
)

func countDocumentSchemaFields(document *Document, stopAt int) int {
	if document == nil || stopAt <= 0 {
		return 0
	}
	queue := make([]Schema, 0)
	appendMap := func(values map[string]Schema) {
		for _, value := range values {
			queue = append(queue, value)
		}
	}
	appendMap(document.Input)
	appendMap(document.Output)
	appendMap(document.State)
	for index := range document.Graph.Nodes {
		node := &document.Graph.Nodes[index]
		appendMap(node.Input)
		if node.SendMessage != nil {
			appendMap(node.SendMessage.CallbackOutput)
		}
		if node.Join != nil {
			appendMap(node.Join.Result)
		}
		if node.Loop != nil {
			appendMap(node.Loop.State)
		}
	}

	count := 0
	for len(queue) != 0 {
		last := len(queue) - 1
		schema := queue[last]
		queue = queue[:last]
		count++
		if count >= stopAt {
			return count
		}
		if schema.Items != nil {
			queue = append(queue, *schema.Items)
		}
	}
	return count
}

func expressionBudgetError(source string) string {
	if len(source) > MaxExpressionBytes {
		return fmt.Sprintf("expression must not exceed %d bytes", MaxExpressionBytes)
	}
	if expressionNesting(source) > MaxExpressionNesting {
		return fmt.Sprintf("expression nesting must not exceed %d", MaxExpressionNesting)
	}
	return ""
}

// expressionNesting bounds recursive-descent parser depth. Besides brackets
// and parentheses, chains of unary ! operators count as nesting because they
// recurse through parseNot even without delimiters.
func expressionNesting(source string) int {
	depth, unaryDepth, maximum := 0, 0, 0
	quote := byte(0)
	escaped := false
	for index := 0; index < len(source); index++ {
		character := source[index]
		if quote != 0 {
			if escaped {
				escaped = false
				continue
			}
			if character == '\\' {
				escaped = true
				continue
			}
			if character == quote {
				quote = 0
			}
			continue
		}
		switch character {
		case '\'', '"':
			quote = character
			unaryDepth = 0
		case '(', '[':
			depth++
			unaryDepth = 0
			maximum = max(maximum, depth)
		case ')', ']':
			if depth > 0 {
				depth--
			}
			unaryDepth = 0
		case '!':
			if index+1 < len(source) && source[index+1] == '=' {
				unaryDepth = 0
				continue
			}
			unaryDepth++
			maximum = max(maximum, depth+unaryDepth)
		default:
			if !strings.ContainsRune(" \t\r\n", rune(character)) {
				unaryDepth = 0
			}
		}
	}
	return maximum
}
