package packagejson

import (
	"fmt"

	"github.com/microsoft/typescript-go/internal/collections"
	"github.com/microsoft/typescript-go/internal/json"
)

type JSONValueType int8

const (
	JSONValueTypeNotPresent JSONValueType = iota
	JSONValueTypeNull
	JSONValueTypeString
	JSONValueTypeNumber
	JSONValueTypeBoolean
	JSONValueTypeArray
	JSONValueTypeObject
)

func (t JSONValueType) String() string {
	switch t {
	case JSONValueTypeNull:
		return "null"
	case JSONValueTypeString:
		return "string"
	case JSONValueTypeNumber:
		return "number"
	case JSONValueTypeBoolean:
		return "boolean"
	case JSONValueTypeArray:
		return "array"
	case JSONValueTypeObject:
		return "object"
	default:
		return fmt.Sprintf("unknown(%d)", t)
	}
}

type JSONValue struct {
	Type  JSONValueType
	Value any
}

func (v *JSONValue) IsPresent() bool {
	return v.Type != JSONValueTypeNotPresent
}

func (v *JSONValue) IsFalsy() bool {
	switch v.Type {
	case JSONValueTypeNotPresent, JSONValueTypeNull:
		return true
	case JSONValueTypeString:
		return v.Value == ""
	case JSONValueTypeNumber:
		return v.Value == 0
	case JSONValueTypeBoolean:
		return !v.Value.(bool)
	default:
		return false
	}
}

func (v JSONValue) AsObject() *collections.OrderedMap[string, JSONValue] {
	if v.Type != JSONValueTypeObject {
		panic(fmt.Sprintf("expected object, got %v", v.Type))
	}
	return v.Value.(*collections.OrderedMap[string, JSONValue])
}

func (v JSONValue) AsArray() []JSONValue {
	if v.Type != JSONValueTypeArray {
		panic(fmt.Sprintf("expected array, got %v", v.Type))
	}
	return v.Value.([]JSONValue)
}

func (v JSONValue) AsString() string {
	if v.Type != JSONValueTypeString {
		panic(fmt.Sprintf("expected string, got %v", v.Type))
	}
	return v.Value.(string)
}

var _ json.UnmarshalerFrom = (*JSONValue)(nil)

func (v *JSONValue) UnmarshalJSONFrom(dec *json.Decoder) error {
	return unmarshalJSONValueV2[JSONValue](v, dec)
}

func unmarshalJSONValue[T any](v *JSONValue, data []byte) error {
	if string(data) == "null" {
		*v = JSONValue{Type: JSONValueTypeNull}
	} else if data[0] == '"' {
		v.Type = JSONValueTypeString
		return json.Unmarshal(data, &v.Value)
	} else if data[0] == '[' {
		var elements []T
		if err := json.Unmarshal(data, &elements); err != nil {
			return err
		}
		v.Type = JSONValueTypeArray
		v.Value = elements
	} else if data[0] == '{' {
		var object collections.OrderedMap[string, T]
		if err := json.Unmarshal(data, &object); err != nil {
			return err
		}
		v.Type = JSONValueTypeObject
		v.Value = &object
	} else if string(data) == "true" {
		v.Type = JSONValueTypeBoolean
		v.Value = true
	} else if string(data) == "false" {
		v.Type = JSONValueTypeBoolean
		v.Value = false
	} else {
		v.Type = JSONValueTypeNumber
		return json.Unmarshal(data, &v.Value)
	}
	return nil
}

func unmarshalJSONValueV2[T any](v *JSONValue, dec *json.Decoder) error {
	switch dec.PeekKind() {
	case 'n': // json.Null.Kind()
		if _, err := dec.ReadToken(); err != nil {
			return err
		}
		v.Value = nil
		v.Type = JSONValueTypeNull
		return nil
	case '"':
		v.Type = JSONValueTypeString
		if err := json.UnmarshalDecode(dec, &v.Value); err != nil {
			return err
		}
	case '[':
		if _, err := dec.ReadToken(); err != nil {
			return err
		}
		var elements []T
		for dec.PeekKind() != json.EndArray.Kind() {
			var element T
			if err := json.UnmarshalDecode(dec, &element); err != nil {
				return err
			}
			elements = append(elements, element)
		}
		if _, err := dec.ReadToken(); err != nil {
			return err
		}
		v.Type = JSONValueTypeArray
		v.Value = elements
	case '{':
		var object collections.OrderedMap[string, T]
		if err := json.UnmarshalDecode(dec, &object); err != nil {
			return err
		}
		v.Type = JSONValueTypeObject
		v.Value = &object
	case 't', 'f': // json.True.Kind(), json.False.Kind()
		v.Type = JSONValueTypeBoolean
		if err := json.UnmarshalDecode(dec, &v.Value); err != nil {
			return err
		}
	default:
		v.Type = JSONValueTypeNumber
		if err := json.UnmarshalDecode(dec, &v.Value); err != nil {
			return err
		}
	}
	return nil
}
