package backend

import (
	"archive/zip"
	"bytes"
	"fmt"
	"strings"
)

type epubChapter struct {
	Number int
	Title  string
	HTML   string
}

func generateEPUB(slug, title, author string, chapters []epubChapter) ([]byte, error) {
	var buf bytes.Buffer
	w := zip.NewWriter(&buf)

	// 1. mimetype — MUST be first, MUST be uncompressed (Store method)
	mw, err := w.CreateHeader(&zip.FileHeader{
		Name:   "mimetype",
		Method: zip.Store,
	})
	if err != nil {
		return nil, err
	}
	mw.Write([]byte("application/epub+zip"))

	// 2. META-INF/container.xml
	addFile(w, "META-INF/container.xml", containerXML())

	// 3. OEBPS/style.css
	addFile(w, "OEBPS/style.css", epubCSS())

	// 4. OEBPS/content.opf
	addFile(w, "OEBPS/content.opf", contentOPF(slug, title, author, chapters))

	// 5. OEBPS/toc.ncx
	addFile(w, "OEBPS/toc.ncx", tocNCX(slug, title, chapters))

	// 6. Chapter files
	for _, ch := range chapters {
		name := fmt.Sprintf("OEBPS/chapter-%04d.xhtml", ch.Number)
		addFile(w, name, chapterXHTML(ch))
	}

	w.Close()
	return buf.Bytes(), nil
}

func addFile(w *zip.Writer, name, content string) {
	f, _ := w.Create(name)
	f.Write([]byte(content))
}

func containerXML() string {
	return `<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
  <rootfiles>
    <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>`
}

func contentOPF(slug, title, author string, chapters []epubChapter) string {
	var items, spine strings.Builder
	for _, ch := range chapters {
		id := fmt.Sprintf("ch%04d", ch.Number)
		href := fmt.Sprintf("chapter-%04d.xhtml", ch.Number)
		items.WriteString(fmt.Sprintf(`    <item id="%s" href="%s" media-type="application/xhtml+xml"/>`+"\n", id, href))
		spine.WriteString(fmt.Sprintf(`    <itemref idref="%s"/>`+"\n", id))
	}
	return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="uid" version="2.0">
  <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
    <dc:title>%s</dc:title>
    <dc:creator>%s</dc:creator>
    <dc:identifier id="uid">%s</dc:identifier>
    <dc:language>en</dc:language>
  </metadata>
  <manifest>
    <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
    <item id="css" href="style.css" media-type="text/css"/>
%s  </manifest>
  <spine toc="ncx">
%s  </spine>
</package>`, escapeXML(title), escapeXML(author), slug, items.String(), spine.String())
}

func tocNCX(slug, title string, chapters []epubChapter) string {
	var points strings.Builder
	for i, ch := range chapters {
		chTitle := ch.Title
		if chTitle == "" {
			chTitle = fmt.Sprintf("Chapter %d", ch.Number)
		}
		points.WriteString(fmt.Sprintf(`  <navPoint id="np%d" playOrder="%d">
    <navLabel><text>%s</text></navLabel>
    <content src="chapter-%04d.xhtml"/>
  </navPoint>`+"\n", i+1, i+1, escapeXML(chTitle), ch.Number))
	}
	return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd">
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
  <head><meta name="dtb:uid" content="%s"/></head>
  <docTitle><text>%s</text></docTitle>
  <navMap>
%s  </navMap>
</ncx>`, slug, escapeXML(title), points.String())
}

func chapterXHTML(ch epubChapter) string {
	title := ch.Title
	if title == "" {
		title = fmt.Sprintf("Chapter %d", ch.Number)
	}
	return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>%s</title><link rel="stylesheet" href="style.css"/></head>
<body>
<h1 class="chapter-title">%s</h1>
%s
</body>
</html>`, escapeXML(title), escapeXML(title), ch.HTML)
}

func epubCSS() string {
	return `body { font-family: Georgia, serif; font-size: 1em; line-height: 1.6; margin: 1em 2em; }
h1.chapter-title { font-size: 1.4em; margin-bottom: 1em; }
p { margin: 0 0 0.8em 0; text-indent: 1.5em; }
p:first-of-type { text-indent: 0; }
`
}

func escapeXML(s string) string {
	s = strings.ReplaceAll(s, "&", "&amp;")
	s = strings.ReplaceAll(s, "<", "&lt;")
	s = strings.ReplaceAll(s, ">", "&gt;")
	s = strings.ReplaceAll(s, `"`, "&quot;")
	return s
}
