package formula

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"haixun-backend/internal/library/copyvoice"
	libllmjson "haixun-backend/internal/library/llmjson"
	"haixun-backend/internal/library/threadspost"
	domai "haixun-backend/internal/model/ai/domain/usecase"
	cfdom "haixun-backend/internal/model/content_formula/domain/usecase"
)

var generatedDraftFenceRE = regexp.MustCompile("(?s)```(?:json)?\\s*([\\s\\S]*?)```")

type GenerateInput struct {
	Topic         string
	Brief         string
	PersonaBlock  string
	ResearchNotes string
	Formula       cfdom.FormulaSummary
}

type GeneratedDraft struct {
	Text           string `json:"text"`
	TopicTag       string `json:"topic_tag"`
	Hook           string `json:"hook"`
	Angle          string `json:"angle"`
	Rationale      string `json:"rationale"`
	StructureNotes string `json:"structure_notes"`
}

func BuildGenerateSystemPrompt() string {
	return strings.TrimSpace(`你是 Threads / Instagram 貼文代筆。寫出符合人設語言指紋、可直接發布的完整新貼文，不是解釋公式，也不是填空模板。

` + copyvoice.SystemRules() + `

規則：
- 用台灣繁體中文，符合人設語氣。
- 正文素材以【主題】、【補充】、【周邊知識參考】為準；人設不得改變題材。
- 人設定位決定誰在說，語言指紋決定怎麼說；必須遵守段落節奏、標點換行、知識轉譯與 CTA 習慣。
- text 必須是一篇完整自然的 Threads 貼文，可直接複製發布。
- 不可以出現「公式、模板、Hook、情境、轉折、觀點、收尾、[括號]、步驟、結構」等後台分析字眼。
- 不可以輸出填空骨架，不可以照抄參考原文。
- 有參考原文時：只學資訊密度、情緒力度與可用結構；最後仍要換成人設會講出口的話。
- text 排版：像真人手機發文；可使用自然標點、空行、列點與少量 emoji。不要完全無標點，也不要每句硬換行，除非人設樣本就是這樣。
- topic_tag 請產生 1 個適合 Threads 主題標籤的短詞，不含 #，例如「AI工具」「內容創作」「職場」。
- 只回傳 JSON：text, topic_tag, hook, angle, rationale, structure_notes（繁體中文）。`)
}

func BuildGenerateUserPrompt(in GenerateInput) string {
	var b strings.Builder
	b.WriteString("【主題】\n")
	b.WriteString(strings.TrimSpace(in.Topic))
	if brief := strings.TrimSpace(in.Brief); brief != "" {
		b.WriteString("\n補充：")
		b.WriteString(brief)
	}
	b.WriteString("\n\n【人設定位、語言指紋與禁忌（最高優先）】\n")
	b.WriteString(strings.TrimSpace(in.PersonaBlock))
	b.WriteString("\n")
	b.WriteString(copyvoice.PersonaCalibrationNote())
	b.WriteString("\n\n【這篇爆文的故事感（只感受情緒與敘事，禁止照搬句型或填空）】\n")
	b.WriteString(strings.TrimSpace(in.Formula.Formula))
	if structure := strings.TrimSpace(in.Formula.Structure); structure != "" {
		b.WriteString("\n\n敘事推進參考（勿照搬句型）：")
		b.WriteString(structure)
	}
	if len(in.Formula.Avoid) > 0 {
		b.WriteString("\n\n【避免】\n")
		b.WriteString(strings.Join(in.Formula.Avoid, "\n"))
	}
	if notes := strings.TrimSpace(in.ResearchNotes); notes != "" {
		b.WriteString("\n\n【周邊知識參考】\n")
		b.WriteString(notes)
	}
	if src := strings.TrimSpace(in.Formula.SourcePostText); src != "" {
		if note := threadspost.MimicLengthGuidance(src); note != "" {
			b.WriteString("\n\n")
			b.WriteString(note)
		}
		b.WriteString("\n\n【參考原文（僅學結構與篇幅，勿抄內容）】\n")
		b.WriteString(src)
	}
	b.WriteString("\n\n本次敘事切入：")
	b.WriteString(copyvoice.NarrativeLens(in.Topic))
	b.WriteString("\n\n請產出一篇可直接發布的 Threads 貼文 JSON。text 欄位只能放貼文正文，不要放分析、標題、公式、模板或填空欄位。topic_tag 請只放標籤文字，不要加 #。")
	return b.String()
}

func GenerateDraft(ctx context.Context, ai domai.UseCase, req domai.GenerateRequest, in GenerateInput) (*GeneratedDraft, error) {
	temp := 0.82
	req.System = BuildGenerateSystemPrompt()
	req.Messages = []domai.Message{{Role: "user", Content: BuildGenerateUserPrompt(in)}}
	req.Temperature = &temp
	out, err := ai.GenerateText(ctx, req)
	if err != nil {
		return nil, err
	}
	return ParseGeneratedDraft(out.Text)
}

func extractGeneratedDraftJSON(raw string) ([]byte, error) {
	raw = strings.TrimSpace(raw)
	if m := generatedDraftFenceRE.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("generate output missing json object")
	}
	return []byte(raw[start : end+1]), nil
}

func ParseGeneratedDraft(raw string) (*GeneratedDraft, error) {
	payload, err := extractGeneratedDraftJSON(raw)
	if err != nil {
		return nil, err
	}
	var draft GeneratedDraft
	if err := libllmjson.Unmarshal(payload, &draft); err != nil {
		return nil, fmt.Errorf("parse generated draft: %w", err)
	}
	draft.Text = threadspost.FormatDraftText(draft.Text)
	draft.TopicTag = normalizeTopicTag(draft.TopicTag)
	if draft.Text == "" {
		return nil, fmt.Errorf("generated draft missing text")
	}
	badTokens := []string{"[", "]", "【", "】", "Hook", "公式", "模板", "情境→", "轉折→", "觀點→", "收尾", "步驟"}
	for _, token := range badTokens {
		if strings.Contains(draft.Text, token) {
			return nil, fmt.Errorf("generated draft looks like a template, not a publishable post")
		}
	}
	return &draft, nil
}

func normalizeTopicTag(value string) string {
	value = strings.TrimSpace(strings.TrimPrefix(value, "#"))
	if len([]rune(value)) > 40 {
		runes := []rune(value)
		value = string(runes[:40])
	}
	return value
}
