// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package jsonpath

import (
	"fmt"
	"strings"

	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

// EvaluateTruthyFromCompiled evaluates a compiled JSONPath expression and interprets the result
// using "truthy" semantics:
//
//   - empty result => false
//   - "false" (case-insensitive) => false
//   - "0" => false
//   - anything else non-empty => true
func EvaluateTruthyFromCompiled(u unstructured.Unstructured, compiled *CompiledJSONPath) (bool, error) {
	if compiled == nil {
		return false, fmt.Errorf("compiled jsonpath is nil")
	}

	value, err := compiled.Execute(u)
	if err != nil {
		return false, err
	}

	value = strings.TrimSpace(value)
	if value == "" {
		return false, nil
	}

	switch strings.ToLower(value) {
	case "false", "0":
		return false, nil
	default:
		return true, nil
	}
}

func SplitFieldSelectorEquals(raw string) (path string, value string, ok bool) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return "", "", false
	}

	idx, width := findTopLevelEquals(raw)
	if idx < 0 {
		return "", "", false
	}

	path = strings.TrimSpace(raw[:idx])
	value = strings.TrimSpace(raw[idx+width:])

	if path == "" || value == "" {
		return "", "", false
	}

	value = trimMatchingQuotes(value)

	return path, value, true
}

func findTopLevelEquals(raw string) (idx int, width int) {
	var (
		bracketDepth int
		braceDepth   int
		parenDepth   int
		quote        byte
		escaped      bool
	)

	for i := range len(raw) {
		ch := raw[i]

		if escaped {
			escaped = false

			continue
		}

		if quote != 0 {
			switch ch {
			case '\\':
				escaped = true
			case quote:
				quote = 0
			}

			continue
		}

		switch ch {
		case '\'', '"':
			quote = ch

		case '[':
			bracketDepth++
		case ']':
			if bracketDepth > 0 {
				bracketDepth--
			}

		case '{':
			braceDepth++
		case '}':
			if braceDepth > 0 {
				braceDepth--
			}

		case '(':
			parenDepth++
		case ')':
			if parenDepth > 0 {
				parenDepth--
			}

		case '=':
			if bracketDepth != 0 || braceDepth != 0 || parenDepth != 0 {
				continue
			}

			if i+1 < len(raw) && raw[i+1] == '=' {
				return i, 2
			}

			return i, 1
		}
	}

	return -1, 0
}

// SplitFieldSelectorNotEquals parses a raw field selector of the form "path!=value"
// and returns the path, value, and whether the not-equal operator was found at the top level.
func SplitFieldSelectorNotEquals(raw string) (path string, value string, ok bool) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return "", "", false
	}

	idx, width := findTopLevelNotEquals(raw)
	if idx < 0 {
		return "", "", false
	}

	path = strings.TrimSpace(raw[:idx])
	value = strings.TrimSpace(raw[idx+width:])

	if path == "" || value == "" {
		return "", "", false
	}

	value = trimMatchingQuotes(value)

	return path, value, true
}

// findTopLevelNotEquals finds the index and width of a top-level "!=" operator,
// skipping any operators nested inside brackets, braces, parentheses or quotes.
func findTopLevelNotEquals(raw string) (idx int, width int) {
	var (
		bracketDepth int
		braceDepth   int
		parenDepth   int
		quote        byte
		escaped      bool
	)

	for i := range len(raw) {
		ch := raw[i]

		if escaped {
			escaped = false

			continue
		}

		if quote != 0 {
			switch ch {
			case '\\':
				escaped = true
			case quote:
				quote = 0
			}

			continue
		}

		switch ch {
		case '\'', '"':
			quote = ch

		case '[':
			bracketDepth++
		case ']':
			if bracketDepth > 0 {
				bracketDepth--
			}

		case '{':
			braceDepth++
		case '}':
			if braceDepth > 0 {
				braceDepth--
			}

		case '(':
			parenDepth++
		case ')':
			if parenDepth > 0 {
				parenDepth--
			}

		case '!':
			if bracketDepth != 0 || braceDepth != 0 || parenDepth != 0 {
				continue
			}

			if i+1 < len(raw) && raw[i+1] == '=' {
				return i, 2
			}
		}
	}

	return -1, 0
}

func trimMatchingQuotes(value string) string {
	if len(value) < 2 {
		return value
	}

	first := value[0]
	last := value[len(value)-1]

	if first != last {
		return value
	}

	if first != '"' && first != '\'' {
		return value
	}

	return strings.TrimSpace(value[1 : len(value)-1])
}
