// package arch_link -- связь между архитектурными объектами
package arch_link

import (
	"fmt"

	"gitp78su.ipnodns.ru/svi/kern/v3"
	"gitp78su.ipnodns.ru/svi/kern/v3/krn/ktypes"

	"gitp78su.ipnodns.ru/svi/goarch/lev0/alias"
	"gitp78su.ipnodns.ru/svi/goarch/lev0/cons"
	"gitp78su.ipnodns.ru/svi/goarch/lev0/types"
	"gitp78su.ipnodns.ru/svi/goarch/lev1/arch_text"
)

// ArchLink -- связь между архитектурными объектами
type ArchLink struct {
	*arch_text.ArchText
	SrcId_ alias.ArchId `yaml:"src_id"` // Источник координат
	src    types.IArchNode
	DstId_ alias.ArchId `yaml:"dst_id"` // Получатель координат
	dst    types.IArchNode

	TypeLink_ string `yaml:"type_link"` // Тип связи
}

var (
	hassert = kern.GetFnHassert()
)

// NewArchLink -- возвращает новую связь
func NewArchLink(archText *arch_text.ArchText) *ArchLink {
	hassert(archText != nil, "NewArchLink: archText == nil")
	sf := &ArchLink{
		ArchText: archText,
	}
	_ = types.IArchLink(sf)
	return sf
}

// InvarCheck -- самопроверка элемента
func (sf *ArchLink) InvarCheck() ktypes.Option[error] {
	optErr := sf.InvarNode()
	if optErr.IsVal() {
		return optErr
	}
	if sf.SrcId_ == "" {
		err := fmt.Errorf("ArchLink.SelfCheck(): id=%v, Src_ == nil", sf.Id())
		return ktypes.NewSome(err)
	}
	if sf.DstId_ == "" {
		err := fmt.Errorf("ArchLink.SelfCheck(): id=%v, Dst_ == nil", sf.Id())
		return ktypes.NewSome(err)
	}
	if sf.TypeLink_ == "" {
		err := fmt.Errorf("ArchLink.SelfCheck(): id=%v, TypeLink_ is empty", sf.Id())
		return ktypes.NewSome(err)
	}
	return ktypes.NewNone[error]()
}

// TypeLink -- тип связи
//
//go:fix inline
func (sf *ArchLink) TypeLink() string {
	return sf.TypeLink_
}

// Links -- ссылки актора
func (sf *ArchLink) Links() []types.IArchNode {
	return []types.IArchNode{sf.src, sf.dst}
}

// Check -- проверяет связи между объектами
func (sf *ArchLink) Check() string {
	if sf.DstId_ == sf.SrcId_ {
		return fmt.Sprintf("ВНИМАНИЕ! Источник и получатель совпадают\nsrc=%v, dst=%v\n",
			sf.SrcId_, sf.DstId_)
	}
	if msgErr := sf.checkSrc(); msgErr != "" {
		return "ОШИБКА при проверке связи источника\n" + msgErr
	}
	if msgErr := sf.checkDst(); msgErr != "" {
		return "ОШИБКА при проверке связи получателя\n" + msgErr
	}
	if sf.TypeLink_ == "" { // Проверка на правильность типа связи
		return "Пустой собственный тип связи\n"
	}
	return ""
}

// проверить связь получателя
func (sf *ArchLink) checkDst() string {
	if sf.DstId_ == "" {
		return "ОШИБКА получатель `dst` не задан"
	}
	// Вычислить на допустимые типы для получателя
	switch sf.dst.Type() {
	case cons.TypeUseActor:
		actor, isOk := sf.dst.(types.IArchActor)
		if !isOk {
			return fmt.Sprintf("Недопустимый тип получателя(%q)\nтип=%q\n",
				sf.DstId_, sf.dst.Type())
		}
		isOk = false
		for _, id := range actor.GroupLink().Links() {
			if id == sf.Id() {
				isOk = true
				break
			}
		}
		if !isOk {
			return fmt.Sprintf("Невзаимная ссылка\nid=%q,dst=(%q)",
				sf.Id(), actor.Id())
		}
	case cons.TypeUseLink:
		link, isOk := sf.dst.(types.IArchLink)
		if !isOk {
			return fmt.Sprintf("Недопустимый тип получателя(%q)\nтип=%q\n",
				sf.DstId_, sf.dst.Type())
		}
		isOk = sf.Id() == link.DstId() || sf.Id() == link.SrcId()
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n", sf.Id(), link.Id())
		}
	case cons.TypeUseCase:
		useCase, isOk := sf.dst.(types.IArchUseCase)
		if !isOk {
			return fmt.Sprintf("id=%q, Недопустимый тип получателя(%q)\nтип=%q\n",
				sf.Id(), sf.DstId_, sf.dst.Type())
		}
		isOk = false
		for id := range useCase.Links() {
			if id == sf.Id() {
				isOk = true
				break
			}
		}
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n",
				sf.Id(), useCase.Id())
		}
	default: // Недопустимый источник
		return fmt.Sprintf("id=%q, Недопустимый тип получателя(%q)\nid=%q\n",
			sf.Id(), sf.dst.Type(), sf.DstId_)
	}
	return ""
}

// Проверяет связь источника
func (sf *ArchLink) checkSrc() string {
	if sf.src == nil {
		return "ВНИМАНИЕ! Источник связи не задан(src)\n"
	}
	switch sf.src.Type() {
	case cons.TypeUseActor:
		isOk := false
		actor := sf.src.(types.IArchActor)
		for _, id := range actor.GroupLink().Links() { // Проверка на взаимность
			if id == sf.Id() {
				isOk = true
				break
			}
		}
		if !isOk {
			return fmt.Sprintf("Невзаимная ссылка с актором %q\n", actor.Id())
		}
	case cons.TypeUseLink:
		link, isOk := sf.src.(types.IArchLink)
		if !isOk {
			return fmt.Sprintf("Несоответствие типа источника(%q)\nтип='useLink'\n",
				sf.SrcId_)
		}
		isOk = sf.Id() == link.DstId() || sf.Id() == link.SrcId()
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n",
				sf.Id(), link.Id())
		}
	case cons.TypeUseCase:
		useCase, isOk := sf.src.(types.IArchUseCase)
		if !isOk {
			return fmt.Sprintf("id=%q, Тип источника(%q) не совпадает с фактическим\nтип='useCase'\n",
				sf.Id(), sf.SrcId())
		}
		isOk = false
		for id := range useCase.Links() { // Проверка на взаимность
			if id == sf.Id() {
				isOk = true
				break
			}
		}
		if !isOk {
			return fmt.Sprintf("id=%q, Невзаимная ссылка(%q)\n",
				sf.Id(), useCase.Id())
		}
	default: // Неизвестный источник
		return fmt.Sprintf("id=%q, неизвестный тип источника(%q)<br>id=%q\n",
			sf.Id(), sf.src.Type(), sf.SrcId())
	}
	return ""
}

// SrcId -- возвращает ID источника связи
//
//go:fix inline
func (sf *ArchLink) SrcId() alias.ArchId {
	return sf.SrcId_
}

// DstId -- возвращает ID получателя связи
//
//go:fix inline
func (sf *ArchLink) DstId() alias.ArchId {
	return sf.DstId_
}
