// Copyright (c) 2017-2026 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// SPDX-License-Identifier: EUPL-1.2

package types

import (
	"fmt"
	"unicode/utf8"
)

// AssetDescriptorOperationTag is the wire tag for asset_descriptor_operation
// extra variants.
const AssetDescriptorOperationTag uint8 = 40

// Asset operation types used by the HF5 asset_descriptor_operation variant.
const (
	AssetOpRegister   uint8 = 0 // deploy new asset
	AssetOpEmit       uint8 = 1 // emit additional supply
	AssetOpUpdate     uint8 = 2 // update asset metadata
	AssetOpBurn       uint8 = 3 // burn supply with proof
	AssetOpPublicBurn uint8 = 4 // burn supply publicly
)

// AssetDescriptorBase holds the core asset metadata referenced by
// asset_descriptor_operation extra variants.
type AssetDescriptorBase struct {
	Ticker         string
	FullName       string
	TotalMaxSupply uint64
	CurrentSupply  uint64
	DecimalPoint   uint8
	MetaInfo       string
	OwnerKey       PublicKey
	Etc            []byte
}

// Validate checks that the base asset metadata is structurally valid.
func (base AssetDescriptorBase) Validate() error {
	tickerLen := utf8.RuneCountInString(base.Ticker)
	fullNameLen := utf8.RuneCountInString(base.FullName)

	if base.TotalMaxSupply == 0 {
		return fmt.Errorf("types: total max supply must be non-zero")
	}
	if base.CurrentSupply > base.TotalMaxSupply {
		return fmt.Errorf("types: current supply %d exceeds max supply %d", base.CurrentSupply, base.TotalMaxSupply)
	}
	if tickerLen == 0 || tickerLen > 6 {
		return fmt.Errorf("types: ticker length %d out of range", tickerLen)
	}
	if fullNameLen == 0 || fullNameLen > 64 {
		return fmt.Errorf("types: full name length %d out of range", fullNameLen)
	}
	if base.OwnerKey.IsZero() {
		return fmt.Errorf("types: owner key must be non-zero")
	}

	return nil
}

// AssetDescriptorOperation represents a deploy/emit/update/burn operation.
// The wire format is parsed in wire/ as an opaque blob for round-tripping.
type AssetDescriptorOperation struct {
	Version       uint8
	OperationType uint8
	Descriptor    *AssetDescriptorBase
	AssetID       Hash
	AmountToEmit  uint64
	AmountToBurn  uint64
	Etc           []byte
}

// Validate checks that the operation is structurally valid for HF5 parsing.
func (op AssetDescriptorOperation) Validate() error {
	switch op.Version {
	case 0, 1:
	default:
		return fmt.Errorf("types: unsupported version %d", op.Version)
	}

	switch op.OperationType {
	case AssetOpRegister:
		if !op.AssetID.IsZero() {
			return fmt.Errorf("types: register operation must not carry asset id")
		}
		if op.Descriptor == nil {
			return fmt.Errorf("types: register operation missing descriptor")
		}
		if err := op.Descriptor.Validate(); err != nil {
			return err
		}
		if op.AmountToEmit != 0 || op.AmountToBurn != 0 {
			return fmt.Errorf("types: register operation must not include emission or burn amounts")
		}
	case AssetOpEmit:
		if op.AssetID.IsZero() {
			return fmt.Errorf("types: emit operation must carry asset id")
		}
		if op.AmountToEmit == 0 {
			return fmt.Errorf("types: emit operation has zero amount")
		}
		if op.Descriptor != nil {
			return fmt.Errorf("types: emit operation must not carry descriptor")
		}
	case AssetOpUpdate:
		if op.AssetID.IsZero() {
			return fmt.Errorf("types: update operation must carry asset id")
		}
		if op.Descriptor == nil {
			return fmt.Errorf("types: update operation missing descriptor")
		}
		if err := op.Descriptor.Validate(); err != nil {
			return err
		}
		if op.AmountToEmit != 0 || op.AmountToBurn != 0 {
			return fmt.Errorf("types: update operation must not include emission or burn amounts")
		}
	case AssetOpBurn, AssetOpPublicBurn:
		if op.AssetID.IsZero() {
			return fmt.Errorf("types: burn operation must carry asset id")
		}
		if op.AmountToBurn == 0 {
			return fmt.Errorf("types: burn operation has zero amount")
		}
		if op.Descriptor != nil {
			return fmt.Errorf("types: burn operation must not carry descriptor")
		}
	default:
		return fmt.Errorf("types: unsupported operation type %d", op.OperationType)
	}

	return nil
}
