package locales

import (
	"git.perx.ru/perxis/perxis-go/pkg/data"
	"git.perx.ru/perxis/perxis-go/pkg/errors"
	pb "git.perx.ru/perxis/perxis-go/proto/locales"
	"golang.org/x/text/language"
)

const (
	DefaultID        = "default" // DefaultID идентификатор локали по умолчанию
	DefaultDirection = "ltr"     // DefaultDirection направление письма по умолчанию
)

type Locale struct {
	ID         string `json:"id" bson:"_id"`                  // Идентификатор локали, генерируется автоматически. Для локали по умолчанию устанавливается как "default".
	SpaceID    string `json:"spaceId" bson:"-"`               // Идентификатор пространства.
	Name       string `json:"name" bson:"name"`               // Название локали. Опционально, заполняется автоматически (Пример: russian, english)
	NativeName string `json:"native_name" bson:"native_name"` // Название локали на языке локали. Опционально, заполняется автоматически (Пример: Русский, English)
	Code       string `json:"code" bson:"code"`               // Код локали https://en.wikipedia.org/wiki/IETF_language_tag
	Fallback   string `json:"fallback" bson:"fallback"`       // Идентификатор локали, который будет использоваться при отсутствии перевода
	Direction  string `json:"direction" bson:"direction"`     // Направление письма - слева направо (ltr) или справа налево (rtl). По умолчанию устанавливается ltr.
	Weight     int    `json:"weight" bson:"weight"`           // Вес локали.
	NoPublish  bool   `json:"no_publish" bson:"no_publish"`   // Не публиковать контент данной локали. Не будет доступен контент через Delivery API. (кроме default)
	Disabled   bool   `json:"disabled" bson:"disabled"`       // Запретить использование локали. Нельзя создавать и редактировать контент для данной локали (кроме default)
}

func (locale *Locale) Clone() *Locale {
	return &Locale{
		ID:         locale.ID,
		SpaceID:    locale.SpaceID,
		Name:       locale.Name,
		NativeName: locale.NativeName,
		Code:       locale.Code,
		Fallback:   locale.Fallback,
		Direction:  locale.Direction,
		Weight:     locale.Weight,
		NoPublish:  locale.NoPublish,
		Disabled:   locale.Disabled,
	}
}

func (locale *Locale) IsDefault() bool {
	return locale.ID == DefaultID
}

func (locale *Locale) Equal(other *Locale) bool {
	return locale == other || locale != nil && other != nil && *locale == *other
}

func (locale *Locale) Key() string {
	if locale == nil {
		return ""
	}
	return locale.ID
}

// Возвращает язык локали, например "en", "ru"
func (locale *Locale) GetLanguage() string {
	lang, err := language.Parse(locale.Code)
	if err != nil {
		return ""
	}

	base, _ := lang.Base()
	return base.String()
}

func GetIDs(locales []*Locale) []string {
	result := make([]string, 0, len(locales))
	for _, locale := range locales {
		result = append(result, locale.ID)
	}
	return result
}

func LocaleToProto(locale *Locale) *pb.Locale {
	if locale == nil {
		return nil
	}
	protoLocale := &pb.Locale{
		Id:         locale.ID,
		SpaceId:    locale.SpaceID,
		Name:       locale.Name,
		NativeName: locale.NativeName,
		Code:       locale.Code,
		Fallback:   locale.Fallback,
		Direction:  locale.Direction,
		Weight:     int64(locale.Weight),
		NoPublish:  locale.NoPublish,
		Disabled:   locale.Disabled,
	}
	return protoLocale
}

func LocaleFromProto(protoLocale *pb.Locale) *Locale {
	if protoLocale == nil {
		return nil
	}
	locale := &Locale{
		ID:         protoLocale.Id,
		SpaceID:    protoLocale.SpaceId,
		Name:       protoLocale.Name,
		NativeName: protoLocale.NativeName,
		Code:       protoLocale.Code,
		Fallback:   protoLocale.Fallback,
		Direction:  protoLocale.Direction,
		Weight:     int(protoLocale.Weight),
		NoPublish:  protoLocale.NoPublish,
		Disabled:   protoLocale.Disabled,
	}
	return locale
}

func FallbackSort(locales []*Locale) ([]*Locale, error) {
	if len(locales) == 0 {
		return locales, nil
	}

	var (
		unsorted = data.SliceToMap(locales)
		sorted   = make([]*Locale, 0, len(locales))
		visited  = make(map[string]bool, len(locales))
	)

	for {
		changed := false
		for _, node := range locales {

			if visited[node.ID] {
				continue
			}

			if _, exist := unsorted[node.Fallback]; !exist && node.Fallback != "" {
				return nil, errors.Wrap(ErrNotFound, node.Fallback)
			}

			if node.IsDefault() {
				sorted = append([]*Locale{node}, sorted...)
				visited[node.ID] = true
				changed = true
				continue
			}

			if node.Fallback == "" || visited[node.Fallback] {
				sorted = append(sorted, node)
				visited[node.ID] = true
				changed = true
			}
		}

		// Все локали перенесены в список отсортированных
		if len(sorted) == len(locales) {
			break
		}

		// Если не произошло изменений, но не все локали отсортированы - обнаружены циклические зависимости
		if !changed {
			return nil, ErrCircularDependency
		}
	}

	return sorted, nil
}