// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

import (
	"encoding/binary"
	"fmt"

	"github.com/luxfi/crypto/bls"
	"github.com/luxfi/crypto/hash"
	"github.com/luxfi/ids"
	"github.com/luxfi/vm/types"
	"github.com/luxfi/zap"
)

type ChainToL1ConversionValidatorData struct {
	NodeID       types.JSONByteSlice    `json:"nodeID"`
	BLSPublicKey [bls.PublicKeyLen]byte `json:"blsPublicKey"`
	Weight       uint64                 `json:"weight"`
}

type ChainToL1ConversionData struct {
	ChainID        ids.ID                             `json:"chainID"`
	ManagerChainID ids.ID                             `json:"managerChainID"`
	ManagerAddress types.JSONByteSlice                `json:"managerAddress"`
	Validators     []ChainToL1ConversionValidatorData `json:"validators"`
}

// ChainToL1ConversionData wire (standalone hash-preimage buffer, no kind byte):
// ChainID 32B @0, ManagerChainID 32B @32, ManagerAddress bytes @64, Validators
// list @72 (stride 64: nodeIDStart u32, nodeIDLen u32, BLSPublicKey 48B,
// Weight u64), NodeID byte pool @80.
const (
	cdOffChainID     = 0
	cdOffManagerID   = 32
	cdOffManagerAddr = 64
	cdOffValidators  = 72
	cdOffNodeIDPool  = 80
	cdSize           = 88

	cvNodeIDStart = 0
	cvNodeIDLen   = 4
	cvBLSKey      = 8
	cvWeight      = 56
	cvStride      = 64
)

// Marshal is the ONE canonical byte encoding of the conversion data — the
// hash preimage of ChainToL1ConversionID. ChainToL1ConversionID calls this
// same encoder, so the ID and the bytes can never diverge.
func (d *ChainToL1ConversionData) Marshal() ([]byte, error) {
	b := zap.NewBuilder(zap.HeaderSize + cdSize + len(d.Validators)*cvStride + len(d.ManagerAddress) + 256)

	var nodeIDPool []byte
	vdrOff, vdrCount := 0, 0
	if len(d.Validators) > 0 {
		lb := b.StartList(cvStride)
		for _, v := range d.Validators {
			var e [cvStride]byte
			binary.LittleEndian.PutUint32(e[cvNodeIDStart:], uint32(len(nodeIDPool)))
			binary.LittleEndian.PutUint32(e[cvNodeIDLen:], uint32(len(v.NodeID)))
			nodeIDPool = append(nodeIDPool, v.NodeID...)
			copy(e[cvBLSKey:], v.BLSPublicKey[:])
			binary.LittleEndian.PutUint64(e[cvWeight:], v.Weight)
			lb.AddBytes(e[:])
		}
		vdrOff, _ = lb.Finish()
		vdrCount = len(d.Validators) // AddBytes counts bytes, not elements
	}

	ob := b.StartObject(cdSize)
	ob.SetBytesFixed(cdOffChainID, d.ChainID[:])
	ob.SetBytesFixed(cdOffManagerID, d.ManagerChainID[:])
	ob.SetBytes(cdOffManagerAddr, d.ManagerAddress)
	ob.SetList(cdOffValidators, vdrOff, vdrCount)
	ob.SetBytes(cdOffNodeIDPool, nodeIDPool)
	ob.FinishAsRoot()
	return b.Finish(), nil
}

// ChainToL1ConversionID creates a chain conversion ID from the provided
// chain conversion data. The preimage is exactly ChainToL1ConversionData.Marshal.
func ChainToL1ConversionID(data ChainToL1ConversionData) (ids.ID, error) {
	bytes, err := data.Marshal()
	if err != nil {
		return ids.Empty, err
	}
	return hash.ComputeHash256Array(bytes), nil
}

// ChainToL1Conversion reports the summary of the chain conversion that
// occurred on the P-chain.
//
// Wire: mkind u8 @0, ID 32B @1 = 33 bytes.
type ChainToL1Conversion struct {
	payload

	// ID of the chain conversion. It is typically generated by calling
	// ChainToL1ConversionID.
	ID ids.ID `json:"id"`
}

const (
	convOffID = 1
	convSize  = 33
)

// NewChainToL1Conversion creates a new initialized ChainToL1Conversion.
func NewChainToL1Conversion(id ids.ID) (*ChainToL1Conversion, error) {
	msg := &ChainToL1Conversion{
		ID: id,
	}
	b := zap.NewBuilder(zap.HeaderSize + convSize)
	ob := b.StartObject(convSize)
	ob.SetUint8(offMKind, uint8(mkindChainToL1Conversion))
	ob.SetBytesFixed(convOffID, id[:])
	ob.FinishAsRoot()
	msg.initialize(b.Finish())
	return msg, nil
}

func parseChainToL1Conversion(root zap.Object) *ChainToL1Conversion {
	return &ChainToL1Conversion{
		ID: readID(root, convOffID),
	}
}

// ParseChainToL1Conversion parses bytes into an initialized
// ChainToL1Conversion.
func ParseChainToL1Conversion(b []byte) (*ChainToL1Conversion, error) {
	payloadIntf, err := Parse(b)
	if err != nil {
		return nil, err
	}
	payload, ok := payloadIntf.(*ChainToL1Conversion)
	if !ok {
		return nil, fmt.Errorf("%w: %T", ErrWrongType, payloadIntf)
	}
	return payload, nil
}
