package aiprompts import ( "encoding/json" "regexp" "strings" ) var ( reLegacyHTMLBlock = regexp.MustCompile(`(?is)<\s*(h[1-4]|p|ul|ol|li|b|strong)\s*>\s*\{?\s*(.*?)\s*\}?\s*<\s*/\s*(?:h[1-4]|p|ul|ol|li|b|strong)\s*>`) reLegacyBoldUL = regexp.MustCompile(`(?is)<\s*b\s*>\s*\{?\s*(.*?)\s*\}?\s*<\s*/\s*b\s*>\s*<\s*ul\s*>\s*<\s*li\s*>\s*\{?\s*(.*?)\s*\}?\s*<\s*/\s*li\s*>\s*<\s*/\s*ul\s*>`) ) // DescriptionFormulaSection is one ordered block in categories.description_template. type DescriptionFormulaSection struct { Type string `json:"type"` Instructions string `json:"instructions"` } // DescriptionFormula is the JSON shape stored in categories.description_template // (sections + optional SEO meta instruction strings). type DescriptionFormula struct { Sections []DescriptionFormulaSection `json:"sections,omitempty"` MetaTitle string `json:"metaTitle,omitempty"` MetaDescription string `json:"metaDescription,omitempty"` } // DescriptionTemplateNeedsRepair is true when template is missing or has no sections // (and no usable meta instructions). func DescriptionTemplateNeedsRepair(template any) bool { f, ok := parseDescriptionFormula(template) if !ok { return true } if len(f.Sections) > 0 { return false } return strings.TrimSpace(f.MetaTitle) == "" && strings.TrimSpace(f.MetaDescription) == "" } // DeriveDescriptionFormulaFromLegacyParts builds description_template from split // legacy DescriptionRules HTML + MetaRules. func DeriveDescriptionFormulaFromLegacyParts(parts LegacyEnhanceParts) DescriptionFormula { out := DescriptionFormula{ MetaDescription: strings.TrimSpace(parts.MetaRules), } body := strings.TrimSpace(parts.DescriptionRules) if body == "" { return out } // Walk left-to-right so section order matches the legacy HTML template. remaining := body for remaining != "" { boldUL := reLegacyBoldUL.FindStringSubmatchIndex(remaining) block := reLegacyHTMLBlock.FindStringSubmatchIndex(remaining) switch { case boldUL != nil && (block == nil || boldUL[0] <= block[0]): m := reLegacyBoldUL.FindStringSubmatch(remaining[boldUL[0]:boldUL[1]]) heading, instr := "", "" if len(m) >= 3 { heading = cleanLegacyInstruction(m[1]) instr = cleanLegacyInstruction(m[2]) } if heading != "" && instr != "" && !strings.Contains(strings.ToLower(instr), strings.ToLower(heading)) { instr = heading + ": " + instr } else if instr == "" { instr = heading } if strings.TrimSpace(instr) != "" { out.Sections = append(out.Sections, DescriptionFormulaSection{Type: "ul", Instructions: instr}) } remaining = remaining[boldUL[1]:] case block != nil: m := reLegacyHTMLBlock.FindStringSubmatch(remaining[block[0]:block[1]]) if len(m) >= 3 { typ := strings.ToLower(strings.TrimSpace(m[1])) instr := cleanLegacyInstruction(m[2]) if instr != "" { switch typ { case "b", "strong": typ = "h2" case "li", "ol": typ = "ul" } out.Sections = append(out.Sections, DescriptionFormulaSection{Type: typ, Instructions: instr}) } } remaining = remaining[block[1]:] default: return out } } return out } // DeriveDescriptionTemplateJSON encodes DeriveDescriptionFormulaFromLegacyParts for DB write. func DeriveDescriptionTemplateJSON(parts LegacyEnhanceParts) string { f := DeriveDescriptionFormulaFromLegacyParts(parts) if len(f.Sections) == 0 && strings.TrimSpace(f.MetaTitle) == "" && strings.TrimSpace(f.MetaDescription) == "" { return "" } b, err := json.Marshal(f) if err != nil { return "" } return string(b) } func parseDescriptionFormula(template any) (DescriptionFormula, bool) { if template == nil { return DescriptionFormula{}, false } switch t := template.(type) { case DescriptionFormula: return t, true case *DescriptionFormula: if t == nil { return DescriptionFormula{}, false } return *t, true case []byte: if len(t) == 0 { return DescriptionFormula{}, false } var f DescriptionFormula if err := json.Unmarshal(t, &f); err != nil { return DescriptionFormula{}, false } return f, true case string: s := strings.TrimSpace(t) if s == "" || s == "null" || s == "{}" { return DescriptionFormula{}, false } var f DescriptionFormula if err := json.Unmarshal([]byte(s), &f); err != nil { return DescriptionFormula{}, false } return f, true default: b, err := json.Marshal(t) if err != nil { return DescriptionFormula{}, false } var f DescriptionFormula if err := json.Unmarshal(b, &f); err != nil { return DescriptionFormula{}, false } return f, true } }