// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package statekeys

import (
	"fmt"
)

// Parse attempts to parse the given string as a state key, and returns the
// result if successful.
//
// A returned error means that the given string is syntactically invalid,
// which could mean either that it doesn't meet the basic requirements for
// any state key, or that it has a recognized key type but the remainder is
// not valid for that type.
//
// Parse DOES NOT return an error for a syntactically-valid key of an
// unrecognized type. Instead, it returns an [UnrecognizedKey] value which
// callers can detect using [RecognizedType], which will return false for
// a key of an unrecognized type.
func Parse(raw string) (Key, error) {
	if len(raw) < 4 {
		// All state keys must have at least four characters, since that's
		// how long a key prefix is.
		return nil, fmt.Errorf("too short to be a valid state key")
	}
	keyType := KeyType(raw[:4])
	remain := raw[4:]
	parser := keyParsers[keyType]
	if parser == nil {
		if !isPlausibleRawKeyType(string(keyType)) {
			return nil, fmt.Errorf("invalid key type prefix %q", keyType)
		}
		return Unrecognized{
			ApparentKeyType: keyType,
			remainder:       remain,
		}, nil
	}
	return parser(remain)
}

var keyParsers = map[KeyType]func(string) (Key, error){
	ResourceInstanceObjectType: parseResourceInstanceObject,
	ComponentInstanceType:      parseComponentInstance,
	OutputType:                 parseOutput,
	VariableType:               parseVariable,
}

// cutKeyField is a key parsing helper for key types that consist of
// multiple fields concatenated together.
//
// cutKeyField returns the raw string content of the next field, and
// also returns any remaining text after the field delimeter which
// could therefore be used in a subsequent call to cutKeyField.
//
// The field delimiter is a comma, but the parser ignores any comma
// that appears to be inside a pair of double-quote characters (")
// so that it's safe to include an address with a string-based instance key
// (which could potentially contain a literal comma) and get back that same
// address as a single field.
//
// If the given string does not contain any delimiters, the result is the
// same string verbatim and an empty "remain" result.
func cutKeyField(raw string) (field, remain string) {
	i := keyDelimiterIdx(raw)
	if i == -1 {
		return raw, ""
	}
	return raw[:i], raw[i+1:]
}

// finalKeyField returns the given string and true if it doesn't contain a key
// field delimiter, or "", false if the string does have a delimiter.
func finalKeyField(raw string) (string, bool) {
	i := keyDelimiterIdx(raw)
	if i != -1 {
		return "", false
	}
	return raw, true
}

// keyDelimiterIdx finds the index of the first delimiter in the given
// string, or returns -1 if there is no delimiter in the string.
func keyDelimiterIdx(raw string) int {
	inQuotes := false
	escape := false
	for i, c := range raw {
		if c == ',' && !inQuotes {
			return i
		}
		if c == '\\' {
			escape = true
			continue
		}
		if c == '"' && !escape {
			inQuotes = !inQuotes
		}
		escape = false
	}
	// If we fall out here then the entire string seems to be
	// a single field, with no delimiters.
	return -1
}
