package listunsubscribe

import (
	"net/mail"
	"net/url"
	"strings"
)

// Mailto holds a one-click mailto unsubscribe target.
type Mailto struct {
	Address string
	Subject string
	Body    string
}

// Parsed from List-Unsubscribe (RFC 2369).
type Parsed struct {
	Mailto *Mailto
	HTTP   string
}

func splitHeaderParts(raw string) []string {
	var parts []string
	var cur strings.Builder
	depth := 0
	for _, r := range raw {
		switch r {
		case '<':
			depth++
			cur.WriteRune(r)
		case '>':
			if depth > 0 {
				depth--
			}
			cur.WriteRune(r)
		case ',':
			if depth == 0 {
				if p := strings.TrimSpace(cur.String()); p != "" {
					parts = append(parts, p)
				}
				cur.Reset()
				continue
			}
			cur.WriteRune(r)
		default:
			cur.WriteRune(r)
		}
	}
	if p := strings.TrimSpace(cur.String()); p != "" {
		parts = append(parts, p)
	}
	return parts
}

func unwrapAngle(s string) string {
	s = strings.TrimSpace(s)
	if strings.HasPrefix(s, "<") && strings.HasSuffix(s, ">") {
		return strings.TrimSpace(s[1 : len(s)-1])
	}
	return s
}

// ParseMailtoURL parses mailto:user@host?subject=...&body=...
func ParseMailtoURL(raw string) (*Mailto, bool) {
	raw = unwrapAngle(strings.TrimSpace(raw))
	if raw == "" {
		return nil, false
	}
	if !strings.HasPrefix(strings.ToLower(raw), "mailto:") {
		return nil, false
	}
	u, err := url.Parse(raw)
	if err != nil {
		return nil, false
	}
	addr := strings.TrimSpace(u.Opaque)
	if addr == "" {
		addr = strings.TrimSpace(strings.TrimPrefix(u.Path, "/"))
	}
	if addr == "" {
		return nil, false
	}
	if _, err := mail.ParseAddress(addr); err != nil {
		// bare addr@host
		if !strings.Contains(addr, "@") {
			return nil, false
		}
	}
	return &Mailto{
		Address: addr,
		Subject: u.Query().Get("subject"),
		Body:    u.Query().Get("body"),
	}, true
}

// Parse extracts mailto and https targets from a List-Unsubscribe header value.
func Parse(listUnsubscribe string) Parsed {
	out := Parsed{}
	for _, part := range splitHeaderParts(listUnsubscribe) {
		part = unwrapAngle(part)
		if m, ok := ParseMailtoURL(part); ok && out.Mailto == nil {
			out.Mailto = m
			continue
		}
		lower := strings.ToLower(part)
		if strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") {
			if out.HTTP == "" {
				out.HTTP = part
			}
		}
	}
	return out
}
