package matrix

import (
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	libprompt "haixun-backend/internal/library/prompt"
	"haixun-backend/internal/library/threadspost"
)

type Row struct {
	SortOrder        int      `json:"sort_order"`
	SearchTag        string   `json:"search_tag"`
	Angle            string   `json:"angle"`
	Hook             string   `json:"hook"`
	Text             string   `json:"text"`
	ReferenceNotes   string   `json:"reference_notes"`
	SourcePermalinks []string `json:"source_permalinks"`
	Rationale        string   `json:"rationale"`
}

type GenerateResult struct {
	Rows []Row `json:"rows"`
}

type MaterialPost struct {
	SearchTag string
	Author    string
	Text      string
	Permalink string
	Priority  string
}

type GenerateInput struct {
	Persona       string
	TopicLabel    string
	AudienceBrief string
	ProductBrief  string
	Posts         []MaterialPost
	Count         int
}

var codeFenceRE = regexp.MustCompile(`(?s)^` + "```(?:json)?\\s*(.*?)\\s*" + "```$")

func BuildUserPrompt(in GenerateInput) (string, error) {
	count := in.Count
	if count <= 0 {
		count = 5
	}
	personaBlock := ""
	if strings.TrimSpace(in.Persona) != "" {
		personaBlock = "人設與語氣：\n" + strings.TrimSpace(in.Persona) + "\n"
	}
	audience := strings.TrimSpace(in.AudienceBrief)
	if audience == "" {
		audience = "（未指定）"
	}
	product := strings.TrimSpace(in.ProductBrief)
	if product == "" {
		product = "（尚未填寫）"
	}
	topic := strings.TrimSpace(in.TopicLabel)
	if topic == "" {
		topic = "未指定"
	}
	return libprompt.MatrixPlacementUser(map[string]string{
		"persona_block":   personaBlock,
		"topic_label":     topic,
		"audience_line":   audience,
		"product_brief":   product,
		"post_count":      fmt.Sprintf("%d", len(in.Posts)),
		"materials_block": buildMaterialsBlock(in.Posts),
		"count":           fmt.Sprintf("%d", count),
	})
}

func buildMaterialsBlock(posts []MaterialPost) string {
	if len(posts) == 0 {
		return "（無素材）"
	}
	lines := make([]string, 0, len(posts))
	for i, post := range posts {
		lines = append(lines, fmt.Sprintf(
			"%d. [%s/%s] @%s\n%s\n連結：%s",
			i+1,
			strings.TrimSpace(post.Priority),
			strings.TrimSpace(post.SearchTag),
			strings.TrimSpace(post.Author),
			strings.TrimSpace(post.Text),
			strings.TrimSpace(post.Permalink),
		))
	}
	return strings.Join(lines, "\n\n")
}

func ParseGenerateOutput(raw string) (GenerateResult, error) {
	payload, err := extractJSONObject(raw)
	if err != nil {
		return GenerateResult{}, err
	}
	var out GenerateResult
	if err := json.Unmarshal(payload, &out); err != nil {
		return GenerateResult{}, fmt.Errorf("parse matrix json: %w", err)
	}
	if len(out.Rows) == 0 {
		return GenerateResult{}, fmt.Errorf("matrix rows missing")
	}
	for i := range out.Rows {
		out.Rows[i].Text = trimText(out.Rows[i].Text)
		if out.Rows[i].Text == "" {
			return GenerateResult{}, fmt.Errorf("matrix row %d empty", i+1)
		}
		if out.Rows[i].SortOrder <= 0 {
			out.Rows[i].SortOrder = i + 1
		}
	}
	return out, nil
}

func trimText(text string) string {
	text = strings.TrimSpace(text)
	runes := []rune(text)
	if len(runes) > threadspost.MaxPublishRunes {
		return string(runes[:threadspost.MaxPublishRunes])
	}
	return text
}

func extractJSONObject(raw string) ([]byte, error) {
	raw = strings.TrimSpace(raw)
	if m := codeFenceRE.FindStringSubmatch(raw); len(m) == 2 {
		raw = strings.TrimSpace(m[1])
	}
	start := strings.Index(raw, "{")
	end := strings.LastIndex(raw, "}")
	if start < 0 || end <= start {
		return nil, fmt.Errorf("matrix output missing json object")
	}
	return []byte(raw[start : end+1]), nil
}
