// KEKS -- Go KEKS codec implementation
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

package keks

import (
	"errors"
	"fmt"
	"time"

	"github.com/mitchellh/mapstructure"
	"go.cypherpunks.su/keks/types"
	"go.cypherpunks.su/tai64n/v4"
)

var (
	ErrTAILeapSecond = errors.New("leap second")
	ErrNonMap        = errors.New("non-map decoded")
)

func toUTC(t time.Time) (ret time.Time, err error) {
	var isLeap bool
	ret, isLeap = tai64n.Leapsecs.Sub(t)
	if isLeap {
		err = ErrTAILeapSecond
	}
	return
}

func (ctx *Decoder) unmarshal(iter *Iterator) (v any, err error) {
	switch iter.T {
	case types.NIL:
		return nil, nil
	case types.Bool:
		return iter.Bool(), nil
	case types.Hexlet:
		return iter.Hexlet(), nil
	case types.UInt:
		return iter.UInt(), nil
	case types.Int:
		return iter.Int(), nil
	case types.List:
		ret := make([]any, 0, iter.Len())
		for range iter.Len() {
			if !iter.Next() {
				panic("less list elements than expected")
			}
			var e any
			e, err = ctx.unmarshal(iter)
			if err != nil {
				return
			}
			ret = append(ret, e)
		}
		v = ret
		return
	case types.Map:
		ret := make(map[string]any, iter.Len())
		var key string
		for range iter.Len() {
			if !iter.Next() {
				panic("less map elements than expected")
			}
			if iter.T != types.Str {
				panic("unexpected non-str key")
			}
			key = iter.Str()
			if !iter.Next() {
				panic("less map elements than expected")
			}
			var e any
			e, err = ctx.unmarshal(iter)
			if err != nil {
				return
			}
			ret[key] = e
		}
		v = ret
		return
	case types.Blob:
		return iter.Blob(), nil
	case types.BigInt:
		return iter.BigInt(), nil
	case types.Float:
		return iter.Float(), nil
	case types.TAI64:
		t := iter.TAI64()
		if ctx.opts != nil {
			if ctx.opts.LeaveTAI64 {
				return t, nil
			}
			if ctx.opts.LeaveTAI {
				return t.Time(), nil
			}
		}
		return toUTC(t.Time())
	case types.TAI64N:
		t := iter.TAI64N()
		if ctx.opts != nil {
			if ctx.opts.LeaveTAI64 {
				return t, nil
			}
			if ctx.opts.LeaveTAI {
				return t.Time(), nil
			}
		}
		return toUTC(t.Time())
	case types.TAI64NA:
		return iter.TAI64NA(), nil
	case types.Magic:
		return iter.Magic(), nil
	case types.Bin:
		return iter.Bin(), nil
	case types.Str:
		return iter.Str(), nil
	case types.Raw:
		return iter.Raw(), nil
	default:
		return nil, fmt.Errorf("unhandled type: %+v", iter.T)
	}
}

// Unmarshal previously parsed data (ctx.Parse) to native Go type.
func (ctx *Decoder) Unmarshal() (any, error) {
	iter := ctx.Iter()
	if !iter.Next() {
		return nil, errors.New("no parsed data")
	}
	v, err := ctx.unmarshal(iter)
	if err != nil {
		return v, err
	}
	if iter.Next() {
		panic("unread elements left")
	}
	return v, err
}

// Fill up dst structure with the contents taken from the src map.
func Map2Struct(dst any, src map[string]any) error {
	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
		Result: dst, TagName: "keks",
	})
	if err != nil {
		return err
	}
	return decoder.Decode(src)
}

// Unmarshal previously parsed data (ctx.Parse) to dst structure.
// Data must be an encoded map to succeed in that.
func (ctx *Decoder) UnmarshalStruct(dst any) error {
	srcAny, err := ctx.Unmarshal()
	if err != nil {
		return err
	}
	src, ok := srcAny.(map[string]any)
	if !ok {
		return ErrNonMap
	}
	return Map2Struct(dst, src)
}

// Just a convenient call to ctx.Parse and ctx.Unmarshal.
func (ctx *Decoder) Decode() (any, error) {
	_, err := ctx.Parse()
	if err != nil {
		return nil, err
	}
	return ctx.Unmarshal()
}

// Just a convenient call to ctx.Parse and ctx.UnmarshalStruct.
func (ctx *Decoder) DecodeStruct(dst any) error {
	_, err := ctx.Parse()
	if err != nil {
		return err
	}
	return ctx.UnmarshalStruct(dst)
}
