package epubtemplates

import (
	"fmt"

	"github.com/beevik/etree"
	epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
	epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
)

type ContentOptions struct {
	Title        string
	UID          string
	Author       string
	Publisher    string
	UpdatedAt    string
	ImageOptions *epuboptions.Image
	Cover        *epubimage.Image
	Images       []*epubimage.Image
	Current      int
	Total        int
}

type tagAttrs map[string]string

type tag struct {
	name  string
	attrs tagAttrs
	value string
}

// create the content file
func Content(o *ContentOptions) string {
	doc := etree.NewDocument()
	doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)

	pkg := doc.CreateElement("package")
	pkg.CreateAttr("xmlns", "http://www.idpf.org/2007/opf")
	pkg.CreateAttr("unique-identifier", "ean")
	pkg.CreateAttr("version", "3.0")
	pkg.CreateAttr("prefix", "rendition: http://www.idpf.org/vocab/rendition/#")

	addToElement := func(elm *etree.Element, meth func(o *ContentOptions) []tag) {
		for _, p := range meth(o) {
			meta := elm.CreateElement(p.name)
			for k, v := range p.attrs {
				meta.CreateAttr(k, v)
			}
			meta.SortAttrs()
			if p.value != "" {
				meta.CreateText(p.value)
			}
		}
	}

	metadata := pkg.CreateElement("metadata")
	metadata.CreateAttr("xmlns:dc", "http://purl.org/dc/elements/1.1/")
	metadata.CreateAttr("xmlns:opf", "http://www.idpf.org/2007/opf")
	addToElement(metadata, getMeta)

	manifest := pkg.CreateElement("manifest")
	addToElement(manifest, getManifest)

	spine := pkg.CreateElement("spine")
	if o.ImageOptions.Manga {
		spine.CreateAttr("page-progression-direction", "rtl")
	} else {
		spine.CreateAttr("page-progression-direction", "ltr")
	}
	addToElement(spine, getSpine)

	guide := pkg.CreateElement("guide")
	addToElement(guide, getGuide)

	doc.Indent(2)
	r, _ := doc.WriteToString()

	return r
}

// metadata part of the content
func getMeta(o *ContentOptions) []tag {
	metas := []tag{
		{"meta", tagAttrs{"property": "dcterms:modified"}, o.UpdatedAt},
		{"meta", tagAttrs{"property": "rendition:layout"}, "pre-paginated"},
		{"meta", tagAttrs{"property": "rendition:spread"}, "auto"},
		{"meta", tagAttrs{"property": "rendition:orientation"}, "auto"},
		{"meta", tagAttrs{"property": "schema:accessMode"}, "visual"},
		{"meta", tagAttrs{"property": "schema:accessModeSufficient"}, "visual"},
		{"meta", tagAttrs{"property": "schema:accessibilityHazard"}, "noFlashingHazard"},
		{"meta", tagAttrs{"property": "schema:accessibilityHazard"}, "noMotionSimulationHazard"},
		{"meta", tagAttrs{"property": "schema:accessibilityHazard"}, "noSoundHazard"},
		{"meta", tagAttrs{"name": "book-type", "content": "comic"}, ""},
		{"opf:meta", tagAttrs{"name": "fixed-layout", "content": "true"}, ""},
		{"opf:meta", tagAttrs{"name": "original-resolution", "content": fmt.Sprintf("%dx%d", o.ImageOptions.View.Width, o.ImageOptions.View.Height)}, ""},
		{"dc:title", tagAttrs{}, o.Title},
		{"dc:identifier", tagAttrs{"id": "ean"}, fmt.Sprintf("urn:uuid:%s", o.UID)},
		{"dc:language", tagAttrs{}, "en"},
		{"dc:creator", tagAttrs{}, o.Author},
		{"dc:publisher", tagAttrs{}, o.Publisher},
		{"dc:contributor", tagAttrs{}, "Go Comic Convertor"},
		{"dc:date", tagAttrs{}, o.UpdatedAt},
	}

	if o.ImageOptions.Manga {
		metas = append(metas, tag{"meta", tagAttrs{"name": "primary-writing-mode", "content": "horizontal-rl"}, ""})
	} else {
		metas = append(metas, tag{"meta", tagAttrs{"name": "primary-writing-mode", "content": "horizontal-lr"}, ""})
	}

	if o.Cover != nil {
		metas = append(metas, tag{"meta", tagAttrs{"name": "cover", "content": o.Cover.ImgKey()}, ""})
	}

	if o.Total > 1 {
		metas = append(
			metas,
			tag{"meta", tagAttrs{"name": "calibre:series", "content": o.Title}, ""},
			tag{"meta", tagAttrs{"name": "calibre:series_index", "content": fmt.Sprint(o.Current)}, ""},
		)
	}

	return metas
}

func getManifest(o *ContentOptions) []tag {
	var imageTags, pageTags, spaceTags []tag
	addTag := func(img *epubimage.Image, withSpace bool) {
		imageTags = append(imageTags,
			tag{"item", tagAttrs{"id": img.ImgKey(), "href": img.ImgPath(), "media-type": "image/jpeg"}, ""},
		)
		pageTags = append(pageTags,
			tag{"item", tagAttrs{"id": img.PageKey(), "href": img.PagePath(), "media-type": "application/xhtml+xml"}, ""},
		)
		if withSpace {
			spaceTags = append(spaceTags,
				tag{"item", tagAttrs{"id": img.SpaceKey(), "href": img.SpacePath(), "media-type": "application/xhtml+xml"}, ""},
			)
		}
	}

	items := []tag{
		{"item", tagAttrs{"id": "toc", "href": "toc.xhtml", "properties": "nav", "media-type": "application/xhtml+xml"}, ""},
		{"item", tagAttrs{"id": "css", "href": "Text/style.css", "media-type": "text/css"}, ""},
		{"item", tagAttrs{"id": "space_title", "href": "Text/space_title.xhtml", "media-type": "application/xhtml+xml"}, ""},
		{"item", tagAttrs{"id": "page_title", "href": "Text/title.xhtml", "media-type": "application/xhtml+xml"}, ""},
		{"item", tagAttrs{"id": "img_title", "href": "Images/title.jpg", "media-type": "image/jpeg"}, ""},
	}

	if o.ImageOptions.HasCover || o.Current > 1 {
		addTag(o.Cover, false)
	}

	lastImage := o.Images[len(o.Images)-1]
	for _, img := range o.Images {
		addTag(img, img.DoublePage || (img.Part == 0 && img == lastImage))
	}

	items = append(items, imageTags...)
	items = append(items, pageTags...)
	items = append(items, spaceTags...)

	return items
}

// spine part of the content
func getSpine(o *ContentOptions) []tag {
	isOnTheRight := !o.ImageOptions.Manga
	getSpread := func(isDoublePage bool) string {
		isOnTheRight = !isOnTheRight
		if isDoublePage {
			// Center the double page then start back to comic mode (mange/normal)
			isOnTheRight = !o.ImageOptions.Manga
			return "rendition:page-spread-center"
		}
		if isOnTheRight {
			return "rendition:page-spread-right"
		} else {
			return "rendition:page-spread-left"
		}
	}
	getSpreadBlank := func() string {
		return fmt.Sprintf("%s layout-blank", getSpread(false))
	}

	spine := []tag{
		{"itemref", tagAttrs{"idref": "space_title", "properties": getSpreadBlank()}, ""},
		{"itemref", tagAttrs{"idref": "page_title", "properties": getSpread(false)}, ""},
	}
	for _, img := range o.Images {
		if img.DoublePage && o.ImageOptions.Manga == isOnTheRight {
			spine = append(spine, tag{
				"itemref",
				tagAttrs{"idref": img.SpaceKey(), "properties": getSpreadBlank()},
				"",
			})
		}
		spine = append(spine, tag{
			"itemref",
			tagAttrs{"idref": img.PageKey(), "properties": getSpread(img.DoublePage)},
			"",
		})
	}
	if o.ImageOptions.Manga == isOnTheRight {
		spine = append(spine, tag{
			"itemref",
			tagAttrs{"idref": o.Images[len(o.Images)-1].SpaceKey(), "properties": getSpread(false)},
			"",
		})
	}

	return spine
}

// guide part of the content
func getGuide(o *ContentOptions) []tag {
	guide := []tag{}
	if o.Cover != nil {
		guide = append(guide, tag{"reference", tagAttrs{"type": "cover", "title": "cover", "href": o.Cover.PagePath()}, ""})
	}
	guide = append(guide, tag{"reference", tagAttrs{"type": "text", "title": "content", "href": o.Images[0].PagePath()}, ""})
	return guide
}