// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package iceberg

import (
	
	
	
	
	

	
)

var (
	regexFromBrackets = regexp.MustCompile(`^\w+\[(\d+)\]$`)
	decimalRegex      = regexp.MustCompile(`decimal\(\s*(\d+)\s*,\s*(\d+)\s*\)`)
)

type Properties map[string]string

// Type is an interface representing any of the available iceberg types,
// such as primitives (int32/int64/etc.) or nested types (list/struct/map).
type Type interface {
	fmt.Stringer
	Type() string
	Equals(Type) bool
}

// NestedType is an interface that allows access to the child fields of
// a nested type such as a list/struct/map type.
type NestedType interface {
	Type
	Fields() []NestedField
}

type typeIFace struct {
	Type
}

func ( *typeIFace) () ([]byte, error) {
	if ,  := .Type.(NestedType);  {
		return json.Marshal()
	}
	return []byte(`"` + .Type.Type() + `"`), nil
}

func ( *typeIFace) ( []byte) error {
	var  string
	 := json.Unmarshal(, &)
	if  == nil {
		switch  {
		case "boolean":
			.Type = BooleanType{}
		case "int":
			.Type = Int32Type{}
		case "long":
			.Type = Int64Type{}
		case "float":
			.Type = Float32Type{}
		case "double":
			.Type = Float64Type{}
		case "date":
			.Type = DateType{}
		case "time":
			.Type = TimeType{}
		case "timestamp":
			.Type = TimestampType{}
		case "timestamptz":
			.Type = TimestampTzType{}
		case "string":
			.Type = StringType{}
		case "uuid":
			.Type = UUIDType{}
		case "binary":
			.Type = BinaryType{}
		default:
			switch {
			case strings.HasPrefix(, "fixed"):
				 := regexFromBrackets.FindStringSubmatch()
				if len() != 2 {
					return fmt.Errorf("%w: %s", ErrInvalidTypeString, )
				}

				,  := strconv.Atoi([1])
				.Type = FixedType{len: }
			case strings.HasPrefix(, "decimal"):
				 := decimalRegex.FindStringSubmatch()
				if len() != 3 {
					return fmt.Errorf("%w: %s", ErrInvalidTypeString, )
				}

				,  := strconv.Atoi([1])
				,  := strconv.Atoi([2])
				.Type = DecimalType{precision: , scale: }
			default:
				return fmt.Errorf("%w: unrecognized field type", ErrInvalidSchema)
			}
		}
		return nil
	}

	 := struct {
		 string `json:"type"`
	}{}
	if  = json.Unmarshal(, &);  != nil {
		return 
	}

	switch . {
	case "list":
		.Type = &ListType{}
	case "map":
		.Type = &MapType{}
	case "struct":
		.Type = &StructType{}
	default:
		return fmt.Errorf("%w: %s", ErrInvalidTypeString, .)
	}

	return json.Unmarshal(, .Type)
}

type NestedField struct {
	Type `json:"-"`

	ID             int    `json:"id"`
	Name           string `json:"name"`
	Required       bool   `json:"required"`
	Doc            string `json:"doc,omitempty"`
	InitialDefault any    `json:"initial-default,omitempty"`
	WriteDefault   any    `json:"write-default,omitempty"`
}

func optOrReq( bool) string {
	if  {
		return "required"
	}
	return "optional"
}

func ( NestedField) () string {
	 := .Doc
	if  != "" {
		 = " (" +  + ")"
	}

	return fmt.Sprintf("%d: %s: %s %s%s",
		.ID, .Name, optOrReq(.Required), .Type, )
}

func ( *NestedField) ( NestedField) bool {
	return .ID == .ID &&
		.Name == .Name &&
		.Required == .Required &&
		.Doc == .Doc &&
		.InitialDefault == .InitialDefault &&
		.WriteDefault == .WriteDefault &&
		.Type.Equals(.Type)
}

func ( NestedField) () ([]byte, error) {
	type  NestedField
	return json.Marshal(struct {
		 *typeIFace `json:"type"`
		*
	}{: &typeIFace{.Type}, : (*)(&)})
}

func ( *NestedField) ( []byte) error {
	type  NestedField
	 := struct {
		 typeIFace `json:"type"`
		*
	}{
		: (*)(),
	}

	if  := json.Unmarshal(, &);  != nil {
		return 
	}

	.Type = ..Type

	return nil
}

type StructType struct {
	FieldList []NestedField `json:"fields"`
}

func ( *StructType) ( Type) bool {
	,  := .(*StructType)
	if ! {
		return false
	}

	return slices.EqualFunc(.FieldList, .FieldList, func(,  NestedField) bool {
		return .Equals()
	})
}

func ( *StructType) () []NestedField { return .FieldList }

func ( *StructType) () ([]byte, error) {
	type  StructType
	return json.Marshal(struct {
		 string `json:"type"`
		*
	}{: .Type(), : (*)()})
}

func (*StructType) () string { return "struct" }
func ( *StructType) () string {
	var  strings.Builder
	.WriteString("struct<")
	for ,  := range .FieldList {
		if  != 0 {
			.WriteString(", ")
		}
		fmt.Fprintf(&, "%d: %s: ",
			.ID, .Name)
		if .Required {
			.WriteString("required ")
		}
		.WriteString(.Type.String())
		if .Doc != "" {
			.WriteString(" (")
			.WriteString(.Doc)
			.WriteByte(')')
		}
	}
	.WriteString(">")

	return .String()
}

type ListType struct {
	ElementID       int  `json:"element-id"`
	Element         Type `json:"-"`
	ElementRequired bool `json:"element-required"`
}

func ( *ListType) () ([]byte, error) {
	type  ListType
	return json.Marshal(struct {
		 string `json:"type"`
		*
		 *typeIFace `json:"element"`
	}{: .Type(), : (*)(), : &typeIFace{.Element}})
}

func ( *ListType) ( Type) bool {
	,  := .(*ListType)
	if ! {
		return false
	}

	return .ElementID == .ElementID &&
		.Element.Equals(.Element) &&
		.ElementRequired == .ElementRequired
}

func ( *ListType) () []NestedField {
	return []NestedField{.ElementField()}
}

func ( *ListType) () NestedField {
	return NestedField{
		ID:       .ElementID,
		Name:     "element",
		Type:     .Element,
		Required: .ElementRequired,
	}
}

func (*ListType) () string     { return "list" }
func ( *ListType) () string { return fmt.Sprintf("list<%s>", .Element) }

func ( *ListType) ( []byte) error {
	 := struct {
		   int       `json:"element-id"`
		 typeIFace `json:"element"`
		  bool      `json:"element-required"`
	}{}
	if  := json.Unmarshal(, &);  != nil {
		return 
	}

	.ElementID = .
	.Element = ..Type
	.ElementRequired = .
	return nil
}

type MapType struct {
	KeyID         int  `json:"key-id"`
	KeyType       Type `json:"-"`
	ValueID       int  `json:"value-id"`
	ValueType     Type `json:"-"`
	ValueRequired bool `json:"value-required"`
}

func ( *MapType) () ([]byte, error) {
	type  MapType
	return json.Marshal(struct {
		 string `json:"type"`
		*
		   *typeIFace `json:"key"`
		 *typeIFace `json:"value"`
	}{: .Type(), : (*)(),
		:   &typeIFace{.KeyType},
		: &typeIFace{.ValueType}})
}

func ( *MapType) ( Type) bool {
	,  := .(*MapType)
	if ! {
		return false
	}

	return .KeyID == .KeyID &&
		.KeyType.Equals(.KeyType) &&
		.ValueID == .ValueID &&
		.ValueType.Equals(.ValueType) &&
		.ValueRequired == .ValueRequired
}

func ( *MapType) () []NestedField {
	return []NestedField{.KeyField(), .ValueField()}
}

func ( *MapType) () NestedField {
	return NestedField{
		Name:     "key",
		ID:       .KeyID,
		Type:     .KeyType,
		Required: true,
	}
}

func ( *MapType) () NestedField {
	return NestedField{
		Name:     "value",
		ID:       .ValueID,
		Type:     .ValueType,
		Required: .ValueRequired,
	}
}

func (*MapType) () string { return "map" }
func ( *MapType) () string {
	return fmt.Sprintf("map<%s, %s>", .KeyType, .ValueType)
}

func ( *MapType) ( []byte) error {
	 := struct {
		    int       `json:"key-id"`
		      typeIFace `json:"key"`
		  int       `json:"value-id"`
		    typeIFace `json:"value"`
		 *bool     `json:"value-required"`
	}{}
	if  := json.Unmarshal(, &);  != nil {
		return 
	}

	.KeyID, .KeyType = ., ..Type
	.ValueID, .ValueType = ., ..Type
	if . == nil {
		.ValueRequired = true
	} else {
		.ValueRequired = *.
	}
	return nil
}

func ( int) FixedType { return FixedType{len: } }

type FixedType struct {
	len int
}

func ( FixedType) ( Type) bool {
	,  := .(FixedType)
	if ! {
		return false
	}

	return .len == .len
}
func ( FixedType) () int       { return .len }
func ( FixedType) () string   { return fmt.Sprintf("fixed[%d]", .len) }
func ( FixedType) () string { return fmt.Sprintf("fixed[%d]", .len) }

func (,  int) DecimalType {
	return DecimalType{precision: , scale: }
}

type DecimalType struct {
	precision, scale int
}

func ( DecimalType) ( Type) bool {
	,  := .(DecimalType)
	if ! {
		return false
	}

	return .precision == .precision &&
		.scale == .scale
}

func ( DecimalType) () string   { return fmt.Sprintf("decimal(%d, %d)", .precision, .scale) }
func ( DecimalType) () string { return fmt.Sprintf("decimal(%d, %d)", .precision, .scale) }
func ( DecimalType) () int { return .precision }
func ( DecimalType) () int     { return .scale }

type PrimitiveType interface {
	Type
	primitive()
}

type BooleanType struct{}

func (BooleanType) ( Type) bool {
	,  := .(BooleanType)
	return 
}

func (BooleanType) ()     {}
func (BooleanType) () string   { return "boolean" }
func (BooleanType) () string { return "boolean" }

// Int32Type is the "int"/"integer" type of the iceberg spec.
type Int32Type struct{}

func (Int32Type) ( Type) bool {
	,  := .(Int32Type)
	return 
}

func (Int32Type) ()     {}
func (Int32Type) () string   { return "int" }
func (Int32Type) () string { return "int" }

// Int64Type is the "long" type of the iceberg spec.
type Int64Type struct{}

func (Int64Type) ( Type) bool {
	,  := .(Int64Type)
	return 
}

func (Int64Type) ()     {}
func (Int64Type) () string   { return "long" }
func (Int64Type) () string { return "long" }

// Float32Type is the "float" type in the iceberg spec.
type Float32Type struct{}

func (Float32Type) ( Type) bool {
	,  := .(Float32Type)
	return 
}

func (Float32Type) ()     {}
func (Float32Type) () string   { return "float" }
func (Float32Type) () string { return "float" }

// Float64Type represents the "double" type of the iceberg spec.
type Float64Type struct{}

func (Float64Type) ( Type) bool {
	,  := .(Float64Type)
	return 
}

func (Float64Type) ()     {}
func (Float64Type) () string   { return "double" }
func (Float64Type) () string { return "double" }

type Date int32

// DateType represents a calendar date without a timezone or time,
// represented as a 32-bit integer denoting the number of days since
// the unix epoch.
type DateType struct{}

func (DateType) ( Type) bool {
	,  := .(DateType)
	return 
}

func (DateType) ()     {}
func (DateType) () string   { return "date" }
func (DateType) () string { return "date" }

type Time int64

// TimeType represents a number of microseconds since midnight.
type TimeType struct{}

func (TimeType) ( Type) bool {
	,  := .(TimeType)
	return 
}

func (TimeType) ()     {}
func (TimeType) () string   { return "time" }
func (TimeType) () string { return "time" }

type Timestamp int64

// TimestampType represents a number of microseconds since the unix epoch
// without regard for timezone.
type TimestampType struct{}

func (TimestampType) ( Type) bool {
	,  := .(TimestampType)
	return 
}

func (TimestampType) ()     {}
func (TimestampType) () string   { return "timestamp" }
func (TimestampType) () string { return "timestamp" }

// TimestampTzType represents a timestamp stored as UTC representing the
// number of microseconds since the unix epoch.
type TimestampTzType struct{}

func (TimestampTzType) ( Type) bool {
	,  := .(TimestampTzType)
	return 
}

func (TimestampTzType) ()     {}
func (TimestampTzType) () string   { return "timestamptz" }
func (TimestampTzType) () string { return "timestamptz" }

type StringType struct{}

func (StringType) ( Type) bool {
	,  := .(StringType)
	return 
}

func (StringType) ()     {}
func (StringType) () string   { return "string" }
func (StringType) () string { return "string" }

type UUIDType struct{}

func (UUIDType) ( Type) bool {
	,  := .(UUIDType)
	return 
}

func (UUIDType) ()     {}
func (UUIDType) () string   { return "uuid" }
func (UUIDType) () string { return "uuid" }

type BinaryType struct{}

func (BinaryType) ( Type) bool {
	,  := .(BinaryType)
	return 
}

func (BinaryType) ()     {}
func (BinaryType) () string   { return "binary" }
func (BinaryType) () string { return "binary" }

var PrimitiveTypes = struct {
	Bool        PrimitiveType
	Int32       PrimitiveType
	Int64       PrimitiveType
	Float32     PrimitiveType
	Float64     PrimitiveType
	Date        PrimitiveType
	Time        PrimitiveType
	Timestamp   PrimitiveType
	TimestampTz PrimitiveType
	String      PrimitiveType
	Binary      PrimitiveType
	UUID        PrimitiveType
}{
	Bool:        BooleanType{},
	Int32:       Int32Type{},
	Int64:       Int64Type{},
	Float32:     Float32Type{},
	Float64:     Float64Type{},
	Date:        DateType{},
	Time:        TimeType{},
	Timestamp:   TimestampType{},
	TimestampTz: TimestampTzType{},
	String:      StringType{},
	Binary:      BinaryType{},
	UUID:        UUIDType{},
}