package twt_pb

import (
	"io"
	"iter"
	"slices"
	"time"

	"go.yarn.social/lextwt"
	"go.yarn.social/types"
	"google.golang.org/protobuf/proto"
)

type twtRegistry interface {
	Twters() []*types.Twter
	Preamble() lextwt.Comments
	Twts() types.Twts
	WriteTo(w io.Writer) (int64, error)
}

func EncodeRegistry(r twtRegistry) ([]byte, error) {
	o := &TwtRegistry{}
	o.FromLextwt(r)

	return proto.Marshal(o)
}
func DecodeRegistry(b []byte) (twtRegistry, error) {
	o := &TwtRegistry{}
	err := proto.Unmarshal(b, o)
	if err != nil {
		return nil, err
	}
	return o.ToLextwt(), nil
}

func (r *TwtRegistry) FromLextwt(twt twtRegistry) error {
	twters := twt.Twters()
	r.Twters = apply(
		slices.All(twters),
		make([]*Twter, len(twters)),
		fromTwter)

	preamble := twt.Preamble()
	r.Preamble = apply(
		slices.All(preamble),
		make([]*Comment, len(preamble)),
		fromComment)

	twts := twt.Twts()
	r.Twts = apply(
		slices.All(twts),
		make([]*Twt, len(twts)),
		fromTwt)

	return nil
}
func (r *TwtRegistry) ToLextwt() twtRegistry {
	return lextwt.NewTwtRegistry(
		apply(slices.All(r.Twters), make([]*types.Twter, len(r.Twters)), func(c *Twter) *types.Twter {
			return c.ToLextwt()
		}),
		apply(slices.All(r.Preamble), make(lextwt.Comments, len(r.Preamble)), func(c *Comment) *lextwt.Comment {
			return c.ToLextwt()
		}),
		apply(slices.All(r.Twts), make(types.Twts, len(r.Twts)), func(c *Twt) types.Twt {
			return c.ToLextwt()
		}),
	)
}

func apply[U, V any](u iter.Seq2[int, U], v []V, fn func(U) V) []V {
	for i, u := range u {
		v[i] = fn(u)
	}
	return v

}

func (e *Twter) FromLextwt(twt *types.Twter) {
	e.Nick = twt.Nick
	e.Uri = twt.URI
}
func fromTwter(twt *types.Twter) *Twter {
	return &Twter{
		Nick: twt.Nick,
		Uri:  twt.URI,
	}
}
func (e *Twter) ToLextwt() *types.Twter {
	return &types.Twter{
		Nick: e.Nick,
		URI:  e.Uri,
	}
}

func (e *Twt) FromLextwt(twt *lextwt.Twt) {
	twter := twt.Twter()

	e.Twter.FromLextwt(&twter)
	e.Created = fromDateTime(twt.Created())
	e.Msg = apply(
		slices.All(twt.Elems()),
		make([]*Elem, len(twt.Elems())),
		fromElem,
	)
}
func fromTwt(twt types.Twt) *Twt {
	twter := twt.Twter()
	lx, ok := twt.(*lextwt.Twt)
	if !ok {
		return &Twt{}
	}

	msg := lx.Elems()
	return &Twt{
		Twter:   fromTwter(&twter),
		Created: fromDateTime(twt.Created()),
		Msg: apply(
			slices.All(msg),
			make([]*Elem, len(msg)),
			fromElem,
		),
	}
}
func (e *Twt) ToLextwt() *lextwt.Twt {
	return lextwt.NewTwt(
		*e.Twter.ToLextwt(),
		e.Created.ToLextwt(),
		apply(
			slices.All(e.Msg),
			make([]lextwt.Elem, len(e.Msg)),
			func(e *Elem) lextwt.Elem {
				return e.ToLextwt()
			},
		)...,
	)
}

func fromDateTime(t time.Time) *DateTime {
	zone, offset := t.Zone()
	return &DateTime{
		Timestamp: t.UnixMicro(),
		Offset:    int32(offset),
		Zone:      zone,
	}
}
func (c *DateTime) ToLextwt() *lextwt.DateTime {
	dt := time.UnixMicro(c.Timestamp)
	dt = dt.In(time.FixedZone(c.Zone, int(c.Offset)))
	return lextwt.NewDateTime(dt, dt.Format(time.RFC3339))
}

func fromElem(e lextwt.Elem) *Elem {
	if e == lextwt.LineSeparator {
		return &Elem{Elem: &Elem_Lineseparator{fromLineSeparator()}}
	}

	switch e := e.(type) {
	case *lextwt.BangMention:
		return &Elem{Elem: &Elem_Bangmention{fromBangMention(e)}}

	case *lextwt.Code:
		return &Elem{Elem: &Elem_Code{fromCode(e)}}

	case *lextwt.Comment:
		return &Elem{Elem: &Elem_Comment{fromComment(e)}}

	case *lextwt.Mention:
		return &Elem{Elem: &Elem_Mention{fromMention(e)}}

	case *lextwt.Subject:
		return &Elem{Elem: &Elem_Subject{fromSubject(e)}}

	case *lextwt.Tag:
		return &Elem{Elem: &Elem_Tag{fromTag(e)}}

	case *lextwt.Link:
		return &Elem{Elem: &Elem_Link{fromLink(e)}}

	case *lextwt.Text:
		return &Elem{Elem: &Elem_Text{fromText(e)}}
	}

	return &Elem{}
}
func (c *Elem) ToLextwt() lextwt.Elem {
	switch e := c.Elem.(type) {
	case *Elem_Bangmention:
		return e.Bangmention.ToLextwt()
	case *Elem_Code:
		return e.Code.ToLextwt()
	case *Elem_Comment:
		return e.Comment.ToLextwt()
	case *Elem_Lineseparator:
		return e.Lineseparator.ToLextwt()
	case *Elem_Link:
		return e.Link.ToLextwt()
	case *Elem_Mention:
		return e.Mention.ToLextwt()
	case *Elem_Subject:
		return e.Subject.ToLextwt()
	case *Elem_Tag:
		return e.Tag.ToLextwt()
	case *Elem_Text:
		return e.Text.ToLextwt()
	}
	return nil
}

func fromCode(c *lextwt.Code) *Code {
	return &Code{
		Type: Code_Type(c.CodeType()),
		Code: c.Text(),
	}
}
func (c *Code) ToLextwt() *lextwt.Code {
	return lextwt.NewCode(c.Code, lextwt.CodeType(c.Type))
}

func fromComment(c *lextwt.Comment) *Comment {
	return &Comment{
		Comment: c.Text(),
		Key:     c.Key(),
		Value:   c.Value(),
	}
}
func (c *Comment) ToLextwt() *lextwt.Comment {
	if c.Key != "" {
		return lextwt.NewCommentValue(c.Comment, c.Key, c.Value)
	}
	return lextwt.NewComment(c.Comment)
}

func fromBangMention(c *lextwt.BangMention) *BangMention {
	return &BangMention{
		Name:   c.Name(),
		Target: c.Target(),
	}
}
func (c *BangMention) ToLextwt() *lextwt.BangMention {
	return lextwt.NewBangMention(c.Name, c.Target)
}

func fromMention(c *lextwt.Mention) *Mention {
	return &Mention{
		Name:   c.Name(),
		Target: c.Target(),
	}
}
func (c *Mention) ToLextwt() *lextwt.Mention {
	return lextwt.NewMention(c.Name, c.Target)
}

func fromTag(c *lextwt.Tag) *Tag {
	return &Tag{
		Tag:    c.Text(),
		Target: c.Target(),
	}
}
func (c *Tag) ToLextwt() *lextwt.Tag {
	return lextwt.NewTag(c.Tag, c.Target)
}

func fromLineSeparator() *LineSeparator {
	return &LineSeparator{}
}
func (c *LineSeparator) ToLextwt() lextwt.Elem {
	return lextwt.LineSeparator
}

func fromLink(c *lextwt.Link) *Link {
	return &Link{
		Type:   Link_Type(c.LinkType()),
		Alt:    c.Text(),
		Target: c.Target(),
		Title:  c.Title(),
	}
}
func (c *Link) ToLextwt() *lextwt.Link {
	if c.Type == Link_Type(lextwt.LinkMedia) {
		return lextwt.NewMedia(c.Alt, c.Target, c.Title)
	}
	return lextwt.NewLink(c.Alt, c.Target, lextwt.LinkType(c.Type))
}

func fromSubject(c *lextwt.Subject) *Subject {
	if tag, ok := c.Tag().(*lextwt.Tag); ok {
		return &Subject{
			Tag: fromTag(tag),
		}
	}
	return &Subject{
		Subject: c.Text(),
	}
}
func (c *Subject) ToLextwt() *lextwt.Subject {
	if c.Tag != nil {
		return lextwt.NewSubjectTag(c.Tag.Tag, c.Tag.Target)
	}
	return lextwt.NewSubject(c.Subject)
}

func fromText(c *lextwt.Text) *Text {
	return &Text{
		Text: c.Literal(),
	}
}
func (c *Text) ToLextwt() *lextwt.Text {
	return lextwt.NewText(c.Text)
}
