// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
//
// SPDX-License-Identifier: EUPL-1.2

package main

import (
	_ "embed"
	"encoding/json"
	"errors"
	"fmt"
	"io/fs"
	"path/filepath"
	"strings"

	gitroot "gitroot.dev/libs/golang/plugin"
	"gitroot.dev/libs/golang/plugin/model"
)

const PLUGIN_MARKDOWN = "pkg:gitroot/gitroot/apex_markdown"
const PLUGIN_MARKDOWN_FUNC = "renderMd"
const PLUGIN_CODE = "pkg:gitroot/gitroot/apex_code"
const PLUGIN_CODE_FUNC = "renderCode"
const PLUGIN_MERMAID = "pkg:gitroot/gitroot/apex_mermaid"
const PLUGIN_MERMAID_FUNC = "renderCode"

//go:embed resources/styles/add.css
var addStyle string

//go:embed resources/styles/pico.min.css
var picoStyle string

//go:embed resources/styles/simple.min.css
var simpleStyle string

//go:embed resources/index.md
var index string

type Plugin struct {
	server            model.Server
	config            *conf
	renderer          *renderer
	gitWorktree       *worktree
	currentCommit     model.Commit
	branchCommits     []*branchCommits
	reports           []string
	canCallMdPlugin   bool
	canCallCodePlugin bool
}

func (p *Plugin) Init(repoName string, initKind model.InitKind, serializedConf string) error {
	p.config = p.newConf(serializedConf)
	p.reports = []string{}

	forgeConf, err := p.server.ForgeConf()
	if err != nil {
		p.server.LogError("can't get forge conf", err)
	}
	p.renderer = p.newRender(repoName, forgeConf)
	p.branchCommits = make([]*branchCommits, 0)

	if initKind == model.InitKindFuncCall {
		return nil
	}

	p.canCallMdPlugin = p.server.CanCallFunc(PLUGIN_MARKDOWN, PLUGIN_MARKDOWN_FUNC, map[string]string{"fp": "", "md": "", "extraMedata": ""})
	p.canCallCodePlugin = p.server.CanCallFunc(PLUGIN_CODE, PLUGIN_CODE_FUNC, map[string]string{"code": "", "lang": ""})

	if p.config.generateGitWorktree {
		p.LoadWorktree()
	}

	// css style
	if _, err := fs.Stat(p.server.Webcontent(), p.config.style); errors.Is(err, fs.ErrNotExist) || initKind == model.InitKindConfHasChange {
		style := ""
		switch p.config.style {
		case "pico.min.css":
			style = picoStyle
		case "simple.min.css":
			style = simpleStyle
		default:
			// TODO download if distant? Copy if local?
		}
		if style == "" {
			style = simpleStyle
		}
		p.server.Webcontent().WriteContent(p.config.style, strings.Join([]string{style, addStyle}, "\n"))
	} else if err != nil {
		p.server.LogError("can't stats styles", err)
	}

	// index.html
	if _, err := fs.Stat(p.server.Webcontent(), "index.html"); errors.Is(err, fs.ErrNotExist) || initKind == model.InitKindConfHasChange {
		if _, err := fs.Stat(p.server.Worktree(), "index.html"); errors.Is(err, fs.ErrNotExist) {
			if _, err := fs.Stat(p.server.Worktree(), "index.md"); errors.Is(err, fs.ErrNotExist) {
				p.server.Worktree().WriteContent("index.md", index)
				p.server.CommitAllIfNeeded("init web page")
				p.AddFile(model.File{Path: "index.md"})
			} else if err != nil {
				p.server.LogError("can't stats index.md in wortree", err)
			}
		} else if err != nil {
			p.server.LogError("can't stats index.html in wortree", err)
		}
	} else if err != nil {
		p.server.LogError("can't stats index in webContent", err)
	}

	// 404.html
	if _, err := fs.Stat(p.server.Webcontent(), "404.html"); errors.Is(err, fs.ErrNotExist) || initKind == model.InitKindConfHasChange {
		newContent := p.renderer.render("404.html", "<p>Not found</p>", map[string]string{"title": "not found"})
		p.server.Webcontent().WriteContent("404.html", newContent)
	}
	return nil
}

func (p *Plugin) StartCommit(commit model.Commit) error {
	p.currentCommit = commit
	if p.config.branchesDir != "" {
		p.AddIfNotExist(commit)
	}
	return nil
}

func (p *Plugin) AddFile(fp model.File) error {
	newContent := ""
	path := fp.Path
	if strings.HasSuffix(fp.Path, ".md") {
		path = fmt.Sprintf("%s.html", strings.TrimSuffix(fp.Path, ".md"))
		mdContent, err := fs.ReadFile(p.server.Worktree(), fp.Path)
		if err != nil {
			p.server.LogError("AddFile ReadFile "+fp.Path, err)
			return nil
		}
		html := ""
		metas := map[string]string{}
		if p.canCallMdPlugin {
			extraMetadata := p.renderer.extraMetadataForMarkdown(fp.Path, path)
			extraMetadataJson, err := json.Marshal(extraMetadata)
			if err != nil {
				p.server.LogError("AddFile can't marshall extraMetadataForMarkdown", err)
				return nil
			}
			res, err := p.server.CallFunc(PLUGIN_MARKDOWN, PLUGIN_MARKDOWN_FUNC, map[string]string{"fp": fp.Path, "md": string(mdContent), "extraMetadata": string(extraMetadataJson)})
			if err != nil {
				p.server.LogError(fmt.Sprintf("AddFile call renderMd fail with %s", string(extraMetadataJson)), err)
				return nil
			}
			html = res["html"]
			metasJson := res["metas"]
			if len(metasJson) > 0 {
				if err := json.Unmarshal([]byte(metasJson), &metas); err != nil {
					p.server.LogError("AddFile md metasdata unmarshal fail "+metasJson, err)
					return nil
				}
			}
		} else {
			html = fmt.Sprintf("<code>%s</code>", mdContent)
		}
		newContent = p.renderer.render(fp.Path, html, metas)
	} else {
		content, err := fs.ReadFile(p.server.Worktree(), fp.Path)
		if err != nil {
			p.server.LogError("AddFile ReadFile "+fp.Path, err)
			return nil
		}
		newContent = string(content)
	}
	// branch can be "" in init scenario
	if p.currentCommit.Branch != "" && p.currentCommit.Branch != p.config.defaultBranch {
		path = filepath.Join(p.config.branchesDir, p.currentCommit.Branch, path)
		p.reports = append(p.reports, fmt.Sprintf(`- Render [%s](%s%s)`, fp.Path, p.renderer.vars["repo.url"], path))
	} else {
		p.server.Log(fmt.Sprintf("no report because branch is %s", p.currentCommit.Branch))
	}
	p.server.Webcontent().WriteContent(path, newContent)
	if p.config.generateGitWorktree && p.currentCommit.Branch == p.config.defaultBranch {
		p.gitWorktree.addOrModFile(fp.Path, p.currentCommit)
	}
	return nil
}

func (p *Plugin) DelFile(fp model.File) error {
	if p.config.generateGitWorktree && p.currentCommit.Branch == p.config.defaultBranch {
		p.gitWorktree.delFile(fp.Path)
	}
	return nil
}

func (p *Plugin) ModFile(fp model.File) error {
	if fp.OldPath != "" && fp.OldPath != fp.Path {
		p.DelFile(model.File{Path: fp.OldPath, FileHash: fp.OldFileHash})
	}
	return p.AddFile(fp)
}

func (p *Plugin) EndCommit(commit model.Commit) error {
	return nil
}

func (p *Plugin) Finish() error {
	if p.config.generateGitWorktree && p.currentCommit.Branch == p.config.defaultBranch {
		p.StoreWorktree()
		p.gitWorktree.renderHtml("", "worktree", func(fp string, htmlContent string) {
			p.server.Webcontent().WriteContent(fp, p.renderer.render(fp, htmlContent, map[string]string{"title": fp}))
		})
	}
	if p.config.branchesDir != "" {
		p.RenderBranches()
	}
	if len(p.reports) > 0 {
		p.server.Report(model.ReportLevelInfo, p.reports)
		p.reports = []string{}
	}
	p.config = nil
	p.gitWorktree = nil
	return nil
}

func Build(server model.Server) model.Plugin {
	p := &Plugin{
		server: server,
	}
	server.ExportFunc("renderHtml", func(args map[string]string) (map[string]string, error) {
		fp := args["fp"]
		html := args["html"]
		extraMetadataJson := args["extraMetadata"]
		extraMetadata := map[string]string{}
		if extraMetadataJson != "" {
			err := json.Unmarshal([]byte(extraMetadataJson), &extraMetadata)
			if err != nil {
				return nil, err
			}
		}
		finalHtml := p.renderer.render(fp, html, extraMetadata)
		return map[string]string{
			"html": finalHtml,
		}, nil
	})
	return p
}

//go:wasmexport install
func main() {
	loadMimeType()
	loadEmojis()
	gitroot.Register(defaultRun, Build)
}
