package gff

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
)

type jsonDocument struct {
	FileType    string     `json:"file_type"`
	FileVersion string     `json:"file_version"`
	Root        jsonStruct `json:"root"`
}

type jsonStruct struct {
	StructType uint32      `json:"struct_type"`
	Fields     []jsonField `json:"fields"`
}

type jsonField struct {
	Label string          `json:"label"`
	Type  string          `json:"type"`
	Value json.RawMessage `json:"value"`
}

type jsonLocString struct {
	StringRef uint32           `json:"string_ref"`
	Entries   []LocStringEntry `json:"entries"`
}

func (s Struct) MarshalJSON() ([]byte, error) {
	payload, err := marshalStruct(s)
	if err != nil {
		return nil, err
	}
	return json.Marshal(payload)
}

func (s *Struct) UnmarshalJSON(data []byte) error {
	var payload jsonStruct
	if err := json.Unmarshal(data, &payload); err != nil {
		return err
	}
	decoded, err := unmarshalStruct(payload)
	if err != nil {
		return err
	}
	*s = decoded
	return nil
}

func (d Document) MarshalJSON() ([]byte, error) {
	root, err := marshalStruct(d.Root)
	if err != nil {
		return nil, err
	}

	return json.Marshal(jsonDocument{
		FileType:    d.FileType,
		FileVersion: d.FileVersion,
		Root:        root,
	})
}

func (d *Document) UnmarshalJSON(data []byte) error {
	var payload jsonDocument
	if err := json.Unmarshal(data, &payload); err != nil {
		return err
	}

	root, err := unmarshalStruct(payload.Root)
	if err != nil {
		return err
	}

	d.FileType = payload.FileType
	d.FileVersion = payload.FileVersion
	d.Root = root
	return nil
}

func marshalStruct(in Struct) (jsonStruct, error) {
	fields := make([]jsonField, 0, len(in.Fields))
	for _, field := range in.Fields {
		payload, err := marshalValue(field.Value)
		if err != nil {
			return jsonStruct{}, fmt.Errorf("marshal field %q: %w", field.Label, err)
		}
		fields = append(fields, jsonField{
			Label: field.Label,
			Type:  field.Type.String(),
			Value: payload,
		})
	}

	return jsonStruct{
		StructType: in.Type,
		Fields:     fields,
	}, nil
}

func unmarshalStruct(in jsonStruct) (Struct, error) {
	fields := make([]Field, 0, len(in.Fields))
	for _, field := range in.Fields {
		ft, err := parseFieldType(field.Type)
		if err != nil {
			return Struct{}, fmt.Errorf("field %q: %w", field.Label, err)
		}

		value, err := unmarshalValue(ft, field.Value)
		if err != nil {
			return Struct{}, fmt.Errorf("field %q: %w", field.Label, err)
		}

		fields = append(fields, Field{
			Label: field.Label,
			Type:  ft,
			Value: value,
		})
	}

	return Struct{
		Type:   in.StructType,
		Fields: fields,
	}, nil
}

func marshalValue(value Value) (json.RawMessage, error) {
	switch typed := value.(type) {
	case ByteValue:
		return json.Marshal(uint8(typed))
	case CharValue:
		return json.Marshal(int8(typed))
	case WordValue:
		return json.Marshal(uint16(typed))
	case ShortValue:
		return json.Marshal(int16(typed))
	case DWordValue:
		return json.Marshal(uint32(typed))
	case IntValue:
		return json.Marshal(int32(typed))
	case DWord64Value:
		return json.Marshal(uint64(typed))
	case Int64Value:
		return json.Marshal(int64(typed))
	case FloatValue:
		return json.Marshal(float32(typed))
	case DoubleValue:
		return json.Marshal(float64(typed))
	case StringValue:
		return json.Marshal(string(typed))
	case ResRefValue:
		return json.Marshal(string(typed))
	case LocString:
		return json.Marshal(jsonLocString(typed))
	case VoidValue:
		return json.Marshal(base64.StdEncoding.EncodeToString([]byte(typed)))
	case Struct:
		payload, err := marshalStruct(typed)
		if err != nil {
			return nil, err
		}
		return json.Marshal(payload)
	case ListValue:
		payload := make([]jsonStruct, 0, len(typed))
		for _, item := range typed {
			entry, err := marshalStruct(item)
			if err != nil {
				return nil, err
			}
			payload = append(payload, entry)
		}
		return json.Marshal(payload)
	case Orientation:
		return json.Marshal(typed)
	case Vector:
		return json.Marshal(typed)
	default:
		return nil, fmt.Errorf("unsupported value type %T", value)
	}
}

func unmarshalValue(ft FieldType, data []byte) (Value, error) {
	switch ft {
	case TypeByte:
		var out uint8
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return ByteValue(out), nil
	case TypeChar:
		var out int8
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return CharValue(out), nil
	case TypeWord:
		var out uint16
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return WordValue(out), nil
	case TypeShort:
		var out int16
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return ShortValue(out), nil
	case TypeDWord:
		var out uint32
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return DWordValue(out), nil
	case TypeInt:
		var out int32
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return IntValue(out), nil
	case TypeDWord64:
		var out uint64
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return DWord64Value(out), nil
	case TypeInt64:
		var out int64
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return Int64Value(out), nil
	case TypeFloat:
		var out float32
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return FloatValue(out), nil
	case TypeDouble:
		var out float64
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return DoubleValue(out), nil
	case TypeCExoString:
		var out string
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return StringValue(out), nil
	case TypeResRef:
		var out string
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return ResRefValue(out), nil
	case TypeCExoLocString:
		var out jsonLocString
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return LocString(out), nil
	case TypeVoid:
		var out string
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		decoded, err := base64.StdEncoding.DecodeString(out)
		if err != nil {
			return nil, fmt.Errorf("decode base64 void: %w", err)
		}
		return VoidValue(decoded), nil
	case TypeStruct:
		var payload jsonStruct
		if err := json.Unmarshal(data, &payload); err != nil {
			return nil, err
		}
		return unmarshalStruct(payload)
	case TypeList:
		var payload []jsonStruct
		if err := json.Unmarshal(data, &payload); err != nil {
			return nil, err
		}
		out := make(ListValue, 0, len(payload))
		for _, item := range payload {
			decoded, err := unmarshalStruct(item)
			if err != nil {
				return nil, err
			}
			out = append(out, decoded)
		}
		return out, nil
	case TypeOrientation:
		var out Orientation
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return out, nil
	case TypeVector:
		var out Vector
		if err := json.Unmarshal(data, &out); err != nil {
			return nil, err
		}
		return out, nil
	default:
		return nil, fmt.Errorf("unsupported field type %d", ft)
	}
}

func parseFieldType(name string) (FieldType, error) {
	for kind, candidate := range fieldTypeNames {
		if candidate == name {
			return kind, nil
		}
	}
	return 0, fmt.Errorf("unknown field type %q", name)
}
