package main

import (
	"bytes"
	"os"
	"path"
	"sort"
	"strings"
	"time"

	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/renderer/html"

	mathjax "github.com/litao91/goldmark-mathjax"

	"gopkg.in/yaml.v3"
)

var md goldmark.Markdown

func init() {
	md = goldmark.New(
		goldmark.WithExtensions(
			extension.GFM,
			extension.Typographer,
			// Questo pacchetto ha un nome stupido perché in realtà si occupa solo del parsing lato server del Markdown mentre lato client usiamo KaTeX.
			mathjax.NewMathJax(),
		),
		goldmark.WithParserOptions(
			parser.WithAutoHeadingID(),
		),
		goldmark.WithRendererOptions(
			html.WithHardWraps(),
			html.WithXHTML(),
		),
	)
}

type ArticleFrontMatter struct {
	Title       string   `yaml:"title"`
	Description string   `yaml:"description"`
	Tags        []string `yaml:"tags,flow"`
	PublishDate string   `yaml:"publish_date"`
	Important   bool     `yaml:"important"`
}

type Article struct {
	Id          string
	Title       string
	Description string
	Tags        []string
	PublishDate time.Time
	Important   bool

	SourceMarkdown string
	OutputHTML     string
}

type ArticleRegistry struct {
	Articles map[string]*Article
}

func removeBlanks(v []string) []string {
	r := []string{}

	for _, s := range v {
		if len(strings.TrimSpace(s)) > 0 {
			r = append(r, s)
		}
	}

	return r
}

func (ar *ArticleRegistry) LoadArticle(name string) (*Article, error) {
	fileBytes, err := os.ReadFile(path.Join("./news/", name+".md"))
	if err != nil {
		return nil, err
	}

	source := string(fileBytes)

	// TODO: Ehm bugia esiste "https://github.com/yuin/goldmark-meta" però boh per ora funge tutto
	parts := removeBlanks(strings.Split(source, "---"))

	frontMatterSource := parts[0]
	markdownSource := strings.Join(parts[1:], "---")

	var frontMatter ArticleFrontMatter
	if err := yaml.Unmarshal([]byte(frontMatterSource), &frontMatter); err != nil {
		return nil, err
	}

	publishDate, err := time.Parse("2006/01/02 15:04", frontMatter.PublishDate)
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	if err := md.Convert([]byte(markdownSource), &buf); err != nil {
		return nil, err
	}

	article := &Article{
		Id:             name,
		Title:          frontMatter.Title,
		Description:    frontMatter.Description,
		Tags:           frontMatter.Tags,
		PublishDate:    publishDate,
		Important:      frontMatter.Important,
		SourceMarkdown: markdownSource,
		OutputHTML:     buf.String(),
	}

	ar.Articles[name] = article
	return article, nil
}

func (ar *ArticleRegistry) LoadAll() ([]*Article, error) {
	entries, err := os.ReadDir("./news")
	if err != nil {
		return nil, err
	}

	articles := []*Article{}

	for _, entry := range entries {
		if !entry.IsDir() {
			name := strings.TrimRight(entry.Name(), ".md")
			article, err := ar.LoadArticle(name)
			if err != nil {
				return nil, err
			}

			articles = append(articles, article)
		}
	}

	sort.Slice(articles, func(i, j int) bool {
		return articles[i].PublishDate.After(articles[j].PublishDate)
	})

	return articles, nil
}

func NewArticleRegistry() *ArticleRegistry {
	return &ArticleRegistry{
		map[string]*Article{},
	}
}

func (ar *ArticleRegistry) Render(name string) (string, error) {
	article, err := ar.LoadArticle(name)
	if err != nil {
		return "", err
	}

	return article.OutputHTML, nil
}
