package model

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
)

// JSONB helps store arbitrary metadata blobs.
type JSONB map[string]interface{}

func (j JSONB) Value() (driver.Value, error) {
	if j == nil {
		return "{}", nil
	}
	data, err := json.Marshal(j)
	if err != nil {
		return nil, err
	}
	return string(data), nil
}

func (j *JSONB) Scan(src interface{}) error {
	if src == nil {
		*j = JSONB{}
		return nil
	}
	switch value := src.(type) {
	case []byte:
		return json.Unmarshal(value, j)
	case string:
		return json.Unmarshal([]byte(value), j)
	default:
		return fmt.Errorf("unsupported type for JSONB: %T", src)
	}
}

// StringArray helps store JSON arrays of strings in the database.
type StringArray []string

func (s StringArray) Value() (driver.Value, error) {
	if s == nil {
		return "[]", nil
	}
	data, err := json.Marshal(s)
	if err != nil {
		return nil, err
	}
	return string(data), nil
}

func (s *StringArray) Scan(src interface{}) error {
	if src == nil {
		*s = StringArray{}
		return nil
	}
	switch value := src.(type) {
	case []byte:
		return json.Unmarshal(value, s)
	case string:
		return json.Unmarshal([]byte(value), s)
	default:
		return fmt.Errorf("unsupported type for StringArray: %T", src)
	}
}

// RawJSON preserves arbitrary JSON roots, including arrays and objects.
type RawJSON json.RawMessage

func (j RawJSON) Value() (driver.Value, error) {
	if len(j) == 0 {
		return "null", nil
	}
	if !json.Valid(j) {
		return nil, fmt.Errorf("invalid RawJSON payload")
	}
	return string(j), nil
}

func (j *RawJSON) Scan(src interface{}) error {
	if src == nil {
		*j = nil
		return nil
	}

	var data []byte
	switch value := src.(type) {
	case []byte:
		data = append([]byte(nil), value...)
	case string:
		data = []byte(value)
	default:
		return fmt.Errorf("unsupported type for RawJSON: %T", src)
	}

	if len(data) == 0 {
		*j = nil
		return nil
	}
	if !json.Valid(data) {
		return fmt.Errorf("invalid RawJSON payload")
	}
	*j = RawJSON(data)
	return nil
}

func (j RawJSON) MarshalJSON() ([]byte, error) {
	if len(j) == 0 {
		return []byte("null"), nil
	}
	if !json.Valid(j) {
		return nil, fmt.Errorf("invalid RawJSON payload")
	}
	return j, nil
}

func (j *RawJSON) UnmarshalJSON(data []byte) error {
	if len(data) == 0 {
		*j = nil
		return nil
	}
	if !json.Valid(data) {
		return fmt.Errorf("invalid RawJSON payload")
	}
	*j = RawJSON(append([]byte(nil), data...))
	return nil
}
