package export

import (
	"bytes"
	"fmt"
	"html"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"

	"pinscrape-allgo/internal/util"
)

var (
	tagSplitRe   = regexp.MustCompile(`(<\/(?:p|h[1-6]|ul|ol|table|blockquote)>)`)
	tagStartRe   = regexp.MustCompile(`^<(p|h[1-6]|ul|ol|table|blockquote)[\s>]`)
	tagStripRe   = regexp.MustCompile(`<[^>]*>`)
)

type ArticleMeta struct {
	Slug    string
	Title   string
	Keyword string
}

type Image struct {
	ImageURL string
	Title    string
}

type ArticleData struct {
	Title            string
	Description      string
	Canonical        string
	Schema           string
	SiteName         string
	AuthorName       string
	Date             string
	CreatedAt        string
	AIBlocks         []string
	Images           []Image
	RelatedArticles  []ArticleMeta
	InjectHeader     string
	InjectFooter     string
	InjectAds        string
	HasAIBlocks      bool
	HasRelated       bool
	ContentBody      string
	MainImage        *Image
	AdditionalImages []Image
	HeaderInject     string
	FooterInject     string
}

type IndexData struct {
	SiteName     string
	AuthorName   string
	Description  string
	Canonical    string
	Schema       string
	Articles     []ArticleMeta
	InjectHeader string
	InjectFooter string
}

type TemplateEngine struct {
	templates *template.Template
}

// NewTemplateEngine builds the template engine. renderCompleteArticle is a
// passthrough because the article body is fully rendered by BuildBody before
// the template runs; templates only place the pre-rendered body in the page.
func NewTemplateEngine(templateDir string) (*TemplateEngine, error) {
	renderArticle := func(aiBlocks []string, images []Image, injectAds string, templateType string) string {
		var result strings.Builder
		for _, block := range aiBlocks {
			if block != "" {
				result.WriteString(block)
			}
		}
		return result.String()
	}
	funcMap := template.FuncMap{
		"safe":      func(s string) string { return s },
		"htmlEscape": func(s string) string { return html.EscapeString(s) },
		"renderCompleteArticle":  renderArticle,
		"renderParagraph":        RenderParagraph,
		"renderImageWithCaption": RenderImageWithCaption,
		"slice": func(s []Image, start int) []Image {
			if start >= len(s) {
				return []Image{}
			}
			return s[start:]
		},
		"sliceArticles": func(s []ArticleMeta, start int) []ArticleMeta {
			if start >= len(s) {
				return []ArticleMeta{}
			}
			return s[start:]
		},
		"truncateImages": func(s []Image, limit int) []Image {
			if len(s) <= limit {
				return s
			}
			return s[:limit]
		},
		"truncateArticles": func(s []ArticleMeta, limit int) []ArticleMeta {
			if len(s) <= limit {
				return s
			}
			return s[:limit]
		},
		"add": func(a, b int) int { return a + b },
		"formatDate": func(t string) string {
			return ParseTime(t).Format("Jan 02, 2006")
		},
	}
	pattern := filepath.Join(templateDir, "*.gohtml")
	tmpl, err := template.New("").Funcs(funcMap).ParseGlob(pattern)
	if err != nil {
		return nil, fmt.Errorf("error parsing templates: %w", err)
	}
	return &TemplateEngine{templates: tmpl}, nil
}

func (e *TemplateEngine) Render(name string, data interface{}) (string, error) {
	var buf bytes.Buffer
	if err := e.templates.ExecuteTemplate(&buf, name, data); err != nil {
		return "", fmt.Errorf("error rendering template %s: %w", name, err)
	}
	return buf.String(), nil
}

func RenderParagraph(content string, templateType string) string {
	return RenderParagraphHTML(content, templateType)
}

func RenderParagraphHTML(content string, templateType string) string {
	switch templateType {
	case "monochrome":
		return `<div class="mb-6 leading-relaxed text-lg">` + content + `</div>`
	case "high-contrast":
		return `<div class="mb-8 leading-tight font-medium text-gray-800">` + content + `</div>`
	case "cyberpunk":
		return `<div class="mb-6 opacity-90 leading-relaxed">` + content + `</div>`
	default:
		return content
	}
}

func RenderImageWithCaption(img Image, templateType string) string {
	return RenderImageHTML(img, templateType)
}

func RenderImageHTML(img Image, templateType string) string {
	url := html.EscapeString(img.ImageURL)
	alt := html.EscapeString(img.Title)
	switch templateType {
	case "monochrome":
		return fmt.Sprintf(`<figure class="my-12 p-1 bg-white"><img src="%s" alt="%s" class="w-full"><figcaption class="text-sm text-gray-400 mt-2 text-center italic">%s</figcaption></figure>`, url, alt, alt)
	case "high-contrast":
		return fmt.Sprintf(`<figure class="my-16 img-zoom"><img src="%s" alt="%s" class="w-full h-auto"><figcaption class="text-sm text-gray-500 mt-3 font-medium">%s</figcaption></figure>`, url, alt, alt)
	case "glassmorphism":
		return fmt.Sprintf(`<figure class="my-6"><img src="%s" alt="%s" class="w-full rounded-2xl shadow-xl"><figcaption class="text-sm text-white/60 mt-3 text-center">%s</figcaption></figure>`, url, alt, alt)
	case "cyberpunk":
		return fmt.Sprintf(`<figure class="my-8 relative"><img src="%s" alt="%s" class="w-full grayscale hover:grayscale-0 transition-all duration-500 cyber-border"><figcaption class="text-xs text-cyan-400/60 mt-2 font-mono">%s</figcaption></figure>`, url, alt, alt)
	case "pinterest":
		return fmt.Sprintf(`<figure><img src="%s" style="width: 100%%; border-radius: 10px; margin: 20px 0;" alt="%s"><figcaption style="font-size:0.85rem; color:#666; text-align:center;">%s</figcaption></figure>`, url, alt, alt)
	case "gallery":
		return fmt.Sprintf(`<figure class="image-section" style="margin: 30px 0;"><img src="%s" style="width: 100%%; border-radius: 12px;" alt="%s"><figcaption style="font-size:0.8rem; color:#888; margin-top:8px; text-align:center;">%s</figcaption></figure>`, url, alt, alt)
	case "bento":
		return fmt.Sprintf(`<figure class="my-4"><img src="%s" alt="%s" class="w-full rounded-xl shadow-sm"><figcaption class="text-sm text-gray-500 mt-2">%s</figcaption></figure>`, url, alt, alt)
	case "nordic-sage":
		return fmt.Sprintf(`<figure class="ns-figure"><img src="%s" alt="%s"><figcaption>%s</figcaption></figure>`, url, alt, alt)
	case "ink-paper":
		return fmt.Sprintf(`<figure class="ip-figure"><img src="%s" alt="%s"><figcaption>%s</figcaption></figure>`, url, alt, alt)
	case "playful-pop":
		return fmt.Sprintf(`<figure class="pp-figure"><img src="%s" alt="%s"><figcaption>%s</figcaption></figure>`, url, alt, alt)
	case "lilac-soft":
		return fmt.Sprintf(`<figure class="ls-figure"><img src="%s" alt="%s"><figcaption>%s</figcaption></figure>`, url, alt, alt)
	case "midnight-gold":
		return fmt.Sprintf(`<figure class="mg-figure"><img src="%s" alt="%s"><figcaption>%s</figcaption></figure>`, url, alt, alt)
	default:
		return fmt.Sprintf(`<figure class="image-section"><img src="%s" class="section-image" style="width:100%%; height:auto; border-radius:8px; margin: 20px 0;" alt="%s"><figcaption class="text-sm text-gray-500; margin-top:8px;">%s</figcaption></figure>`, url, alt, alt)
	}
}

// PickRandomRelated picks an unused related keyword (index 0 is the main
// keyword and is never picked), mirroring go-snippet/template.go.
func PickRandomRelated(relatedKW []string, used map[int]bool) string {
	if len(relatedKW) <= 1 {
		return ""
	}
	var candidates []int
	for i := 1; i < len(relatedKW); i++ {
		if !used[i] {
			candidates = append(candidates, i)
		}
	}
	if len(candidates) == 0 {
		for i := 1; i < len(relatedKW); i++ {
			candidates = append(candidates, i)
		}
	}
	if len(candidates) == 0 {
		return ""
	}
	idx := candidates[randIntN(len(candidates))]
	used[idx] = true
	return util.Ucwords(relatedKW[idx])
}

func StripTags(s string) string {
	return tagStripRe.ReplaceAllString(s, "")
}

func ExtractFirstParagraph(htmlContent string) string {
	start := strings.Index(htmlContent, "<p")
	if start == -1 {
		return ""
	}
	end := strings.Index(htmlContent[start:], "</p>")
	if end == -1 {
		return ""
	}
	p := htmlContent[start : start+end+4]
	return strings.TrimSpace(StripTags(p))
}

func EscapeJSON(s string) string {
	s = strings.ReplaceAll(s, `\`, `\\`)
	s = strings.ReplaceAll(s, `"`, `\"`)
	s = strings.ReplaceAll(s, "\n", `\n`)
	s = strings.ReplaceAll(s, "\r", `\r`)
	s = strings.ReplaceAll(s, "\t", `\t`)
	return s
}
