package main

import "testing"

// TestResolveThreadRoot pins the conversation-routing gate — the single place that decides
// whether a trigger continues a thread, roots a NEW conversation, or stays on the main
// timeline. The load-bearing invariant is the LAST case: a group is NEVER auto-threaded, so
// the threading feature can't change group behavior. Auto-threading in 1:1 DMs is always on
// (no flag); the only gate is isDM.
func TestResolveThreadRoot(t *testing.T) {
	inThread := &MessageContent{RelatesTo: &RelatesTo{RelType: "m.thread", EventID: "$root"}}
	topLevel := &MessageContent{}
	reply := &MessageContent{RelatesTo: &RelatesTo{RelType: "", EventID: "", InReplyTo: &InReplyTo{EventID: "$x"}}}
	ev := &Event{EventID: "$trigger"}

	cases := []struct {
		name string
		isDM bool
		mc   *MessageContent
		want string
	}{
		{"existing thread continues (DM)", true, inThread, "$root"},
		{"existing thread continues (group)", false, inThread, "$root"},
		{"DM top-level roots a new thread on the trigger", true, topLevel, "$trigger"},
		{"GROUP top-level never auto-threads", false, topLevel, ""},
		{"DM plain reply (no m.thread) roots a new thread", true, reply, "$trigger"},
		{"GROUP plain reply never auto-threads", false, reply, ""},
	}
	for _, c := range cases {
		b := &Bot{}
		if got := b.resolveThreadRoot(c.isDM, ev, c.mc); got != c.want {
			t.Errorf("%s: resolveThreadRoot = %q, want %q", c.name, got, c.want)
		}
	}
}

// TestBuildNoticeContentThreadRelation asserts the reply lands where resolveThreadRoot
// decided: a non-empty threadRoot emits an m.thread relation (so the answer joins the
// conversation), an empty one emits only m.in_reply_to (a plain top-level reply).
func TestBuildNoticeContentThreadRelation(t *testing.T) {
	threaded := buildNoticeContent("$reply", "@u:vojo.chat", "$root", "hi")
	rel, ok := threaded["m.relates_to"].(map[string]any)
	if !ok {
		t.Fatalf("m.relates_to missing or wrong type: %T", threaded["m.relates_to"])
	}
	if rel["rel_type"] != "m.thread" {
		t.Errorf("rel_type = %v, want m.thread", rel["rel_type"])
	}
	if rel["event_id"] != "$root" {
		t.Errorf("event_id = %v, want $root", rel["event_id"])
	}
	if rel["is_falling_back"] != true {
		t.Errorf("is_falling_back = %v, want true", rel["is_falling_back"])
	}
	if inReply, _ := rel["m.in_reply_to"].(map[string]any); inReply["event_id"] != "$reply" {
		t.Errorf("m.in_reply_to.event_id = %v, want $reply", inReply["event_id"])
	}

	topLevel := buildNoticeContent("$reply", "@u:vojo.chat", "", "hi")
	rel2, ok := topLevel["m.relates_to"].(map[string]any)
	if !ok {
		t.Fatalf("m.relates_to missing or wrong type: %T", topLevel["m.relates_to"])
	}
	if _, hasThread := rel2["rel_type"]; hasThread {
		t.Errorf("a top-level reply must not carry rel_type, got %v", rel2["rel_type"])
	}
	if inReply, _ := rel2["m.in_reply_to"].(map[string]any); inReply["event_id"] != "$reply" {
		t.Errorf("m.in_reply_to.event_id = %v, want $reply", inReply["event_id"])
	}
}
