package nip27

import (
	"iter"
	"net/url"
	"regexp"
	"strings"

	"github.com/nbd-wtf/go-nostr"
	"github.com/nbd-wtf/go-nostr/nip19"
	"github.com/nbd-wtf/go-nostr/nip73"
)

type Block struct {
	Text    string
	Start   int
	Pointer nostr.Pointer
}

var (
	noCharacter    = regexp.MustCompile(`(?m)\W`)
	noURLCharacter = regexp.MustCompile(`(?m)\W |\W$|$|,| `)
)

func Parse(content string) iter.Seq[Block] {
	return func(yield func(Block) bool) {
		max := len(content)
		index := 0
		prevIndex := 0

		for index < max {
			pu := strings.IndexRune(content[index:], ':')
			if pu == -1 {
				// reached end
				break
			}
			u := pu + index

			switch {
			case u >= 5 && content[u-5:u] == "nostr" && u+60 < max:
				m := noCharacter.FindStringIndex(content[u+60:])
				end := max
				if m != nil {
					end = u + 60 + m[0]
				}

				prefix, data, err := nip19.Decode(content[u+1 : end])
				if err != nil {
					// ignore this, not a valid nostr uri
					index = u + 1
					continue
				}

				var pointer nostr.Pointer
				switch prefix {
				case "npub":
					pointer = nostr.ProfilePointer{PublicKey: data.(string)}
				case "nprofile", "nevent", "naddr":
					pointer = data.(nostr.Pointer)
				case "note", "nsec":
					fallthrough // I'm so cool
				default:
					// ignore this, treat it as not a valid uri
					index = end + 1
					continue
				}

				if prevIndex != u-5 {
					if !yield(Block{Text: content[prevIndex : u-5], Start: prevIndex}) {
						return
					}
				}

				if !yield(Block{Pointer: pointer, Text: content[u-5 : end], Start: u - 5}) {
					return
				}

				index = end
				prevIndex = index
				continue
			case (u >= 5 && content[u-5:u] == "https") || (u >= 4 && content[u-4:u] == "http"):
				m := noURLCharacter.FindStringIndex(content[u+4:])
				end := max
				if m != nil {
					end = u + 4 + m[0]
				}
				prefixLen := 4
				if content[u-1] == 's' {
					prefixLen = 5
				}
				parsed, err := url.Parse(content[u-prefixLen : end])
				if err != nil || !strings.Contains(parsed.Host, ".") {
					// ignore this, not a valid url
					index = end + 1
					continue
				}

				if prevIndex != u-prefixLen {
					if !yield(Block{Text: content[prevIndex : u-prefixLen], Start: prevIndex}) {
						return
					}
				}

				if !yield(Block{Pointer: nip73.ExternalPointer{Thing: content[u-prefixLen : end]}, Text: content[u-prefixLen : end], Start: u - prefixLen}) {
					return
				}

				index = end
				prevIndex = index
				continue
			case (u >= 3 && content[u-3:u] == "wss") || (u >= 2 && content[u-2:u] == "ws"):
				m := noURLCharacter.FindStringIndex(content[u+4:])
				end := max
				if m != nil {
					end = u + 4 + m[0]
				}
				prefixLen := 2
				if content[u-1] == 's' {
					prefixLen = 3
				}
				parsed, err := url.Parse(content[u-prefixLen : end])
				if err != nil || !strings.Contains(parsed.Host, ".") {
					// ignore this, not a valid url
					index = end + 1
					continue
				}

				if prevIndex != u-prefixLen {
					if !yield(Block{Text: content[prevIndex : u-prefixLen], Start: prevIndex}) {
						return
					}
				}

				if !yield(Block{Pointer: nip73.ExternalPointer{Thing: content[u-prefixLen : end]}, Text: content[u-prefixLen : end], Start: u - prefixLen}) {
					return
				}

				index = end
				prevIndex = index
				continue
			default:
				// ignore this, it is nothing
				index = u + 1
				continue
			}
		}

		if prevIndex != max {
			yield(Block{Text: content[prevIndex:], Start: prevIndex})
		}
	}
}
