package layoutauthoringv1

import (
	"bytes"
	"crypto/sha256"
	"encoding/binary"
	"encoding/hex"
	"errors"
	"io"
	"reflect"
	"strings"
)

const requestDigestDomain = "nexuscrm-layout-authoring-v1"

// RequestIdentity is the immutable command content. Command and idempotency
// identifiers name durable acceptance slots, while WaitMode controls only the
// current transport call; none of those three values changes the effect.
type RequestIdentity struct {
	BaseCatalogRevision uint64           `json:"base_catalog_revision"`
	Operation           CommandOperation `json:"operation"`
}

func CanonicalRequestIdentity(request SubmitRequest) RequestIdentity {
	return RequestIdentity{
		BaseCatalogRevision: request.BaseCatalogRevision,
		Operation:           request.Operation,
	}
}

// CanonicalRequestBytes returns the v1 cross-process command-content grammar.
// It starts with a length-prefixed ASCII domain and then recursively encodes
// the fixed RequestIdentity structure. Each value has a type/presence tag;
// struct fields include their JSON semantic name in declaration order; strings
// and collection counts use unsigned varints; signed integers use zig-zag;
// slices retain authored order and distinguish nil from authored-empty.
// Map-derived facts are represented by validated sorted typed slices, so maps
// and implementation-specific serialization never enter the grammar.
func CanonicalRequestBytes(request SubmitRequest) ([]byte, error) {
	var buffer bytes.Buffer
	encoder := semanticEncoder{writer: &buffer}
	if err := encoder.writeString(requestDigestDomain); err != nil {
		return nil, err
	}
	if err := encoder.writeValue(reflect.ValueOf(CanonicalRequestIdentity(request)), 0); err != nil {
		return nil, err
	}
	return buffer.Bytes(), nil
}

func RequestDigest(request SubmitRequest) (string, error) {
	raw, err := CanonicalRequestBytes(request)
	if err != nil {
		return "", err
	}
	digest := sha256.Sum256(raw)
	return "sha256:" + hex.EncodeToString(digest[:]), nil
}

type semanticEncoder struct {
	writer io.Writer
}

func (encoder semanticEncoder) writeValue(value reflect.Value, depth int) error {
	if depth > 64 {
		return errors.New("layout authoring: semantic value nesting exceeds encoder bound")
	}
	if !value.IsValid() {
		return encoder.writeByte('0')
	}
	switch value.Kind() {
	case reflect.Pointer:
		if value.IsNil() {
			return encoder.writeByte('0')
		}
		if err := encoder.writeByte('1'); err != nil {
			return err
		}
		return encoder.writeValue(value.Elem(), depth+1)
	case reflect.Struct:
		if err := encoder.writeByte('S'); err != nil {
			return err
		}
		typeOfValue := value.Type()
		fieldIndexes := make([]int, 0, value.NumField())
		for index := 0; index < value.NumField(); index++ {
			field := typeOfValue.Field(index)
			if field.PkgPath == "" && field.Tag.Get("json") != "-" {
				fieldIndexes = append(fieldIndexes, index)
			}
		}
		if err := encoder.writeUint(uint64(len(fieldIndexes))); err != nil {
			return err
		}
		for _, index := range fieldIndexes {
			field := typeOfValue.Field(index)
			name := strings.Split(field.Tag.Get("json"), ",")[0]
			if name == "" {
				name = field.Name
			}
			if err := encoder.writeString(name); err != nil {
				return err
			}
			if err := encoder.writeValue(value.Field(index), depth+1); err != nil {
				return err
			}
		}
		return nil
	case reflect.Slice:
		if value.IsNil() {
			return encoder.writeByte('N')
		}
		if err := encoder.writeByte('L'); err != nil {
			return err
		}
		if err := encoder.writeUint(uint64(value.Len())); err != nil {
			return err
		}
		for index := 0; index < value.Len(); index++ {
			if err := encoder.writeValue(value.Index(index), depth+1); err != nil {
				return err
			}
		}
		return nil
	case reflect.Array:
		if err := encoder.writeByte('A'); err != nil {
			return err
		}
		if err := encoder.writeUint(uint64(value.Len())); err != nil {
			return err
		}
		for index := 0; index < value.Len(); index++ {
			if err := encoder.writeValue(value.Index(index), depth+1); err != nil {
				return err
			}
		}
		return nil
	case reflect.String:
		if err := encoder.writeByte('T'); err != nil {
			return err
		}
		return encoder.writeString(value.String())
	case reflect.Bool:
		if value.Bool() {
			return encoder.writeByte('Y')
		}
		return encoder.writeByte('F')
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if err := encoder.writeByte('U'); err != nil {
			return err
		}
		return encoder.writeUint(value.Uint())
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if err := encoder.writeByte('I'); err != nil {
			return err
		}
		signed := value.Int()
		return encoder.writeUint(uint64(signed<<1) ^ uint64(signed>>63))
	default:
		return errors.New("layout authoring: unsupported semantic value kind " + value.Kind().String())
	}
}

func (encoder semanticEncoder) writeString(value string) error {
	if err := encoder.writeUint(uint64(len(value))); err != nil {
		return err
	}
	_, err := io.WriteString(encoder.writer, value)
	return err
}

func (encoder semanticEncoder) writeUint(value uint64) error {
	var encoded [binary.MaxVarintLen64]byte
	count := binary.PutUvarint(encoded[:], value)
	_, err := encoder.writer.Write(encoded[:count])
	return err
}

func (encoder semanticEncoder) writeByte(value byte) error {
	_, err := encoder.writer.Write([]byte{value})
	return err
}
