// 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"

	coreerr "dappco.re/go/core/log"
)

// 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.
//
//	base := types.AssetDescriptorBase{Ticker: "LTHN", FullName: "Lethean", TotalMaxSupply: 1_000_000, OwnerKey: ownerPub}
//	if err := base.Validate(); err != nil { ... }
func (base AssetDescriptorBase) Validate() error {
	tickerLen := utf8.RuneCountInString(base.Ticker)
	fullNameLen := utf8.RuneCountInString(base.FullName)

	if base.TotalMaxSupply == 0 {
		return coreerr.E("AssetDescriptorBase.Validate", "total max supply must be non-zero", nil)
	}
	if base.CurrentSupply > base.TotalMaxSupply {
		return coreerr.E("AssetDescriptorBase.Validate", fmt.Sprintf("current supply %d exceeds max supply %d", base.CurrentSupply, base.TotalMaxSupply), nil)
	}
	if tickerLen == 0 || tickerLen > 6 {
		return coreerr.E("AssetDescriptorBase.Validate", fmt.Sprintf("ticker length %d out of range [1,6]", tickerLen), nil)
	}
	if fullNameLen == 0 || fullNameLen > 64 {
		return coreerr.E("AssetDescriptorBase.Validate", fmt.Sprintf("full name length %d out of range [1,64]", fullNameLen), nil)
	}
	if base.OwnerKey.IsZero() {
		return coreerr.E("AssetDescriptorBase.Validate", "owner key must be non-zero", nil)
	}

	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.
//
//	op := types.AssetDescriptorOperation{Version: 1, OperationType: types.AssetOpRegister, Descriptor: &base}
//	if err := op.Validate(); err != nil { ... }
func (op AssetDescriptorOperation) Validate() error {
	switch op.Version {
	case 0, 1:
	default:
		return coreerr.E("AssetDescriptorOperation.Validate", fmt.Sprintf("unsupported version %d", op.Version), nil)
	}

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

	return nil
}
