package main

import (
	"bytes"
	"flag"
	"fmt"
	"html/template"
	"io/fs"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"smartbar/internal/config"
)

// Expose page size to templates via CSS variables inline on <body>.
// Use HTMLAttr so it renders as an attribute safely.
func pageSizeStyleAttr(widthIn, heightIn float64) template.HTMLAttr {
	return template.HTMLAttr(
		`style="--page-w: ` + floatToIn(widthIn) + `; --page-h: ` + floatToIn(heightIn) + `;"`,
	)
}

func floatToIn(v float64) string {
	return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.4fin", v), "0"), ".")
}

type PageData struct {
	Title   string
	Content template.HTML
}

type IndexPage struct {
	Name string
	Href string
}

type PrintPage struct {
	Content template.HTML
}

type TwoUpSheet struct {
	Left  PrintPage
	Right *PrintPage
}

func mustReadFile(path string) string {
	data, err := os.ReadFile(path)
	if err != nil {
		panic(err)
	}
	return string(data)
}

func writeFile(path string, content []byte) error {
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return err
	}
	return os.WriteFile(path, content, 0o644)
}

func main() {
	// Required flags
	var pagesDir string
	var outDir string
	var templatesDir string
	var orderPath string
	flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	flagSet.StringVar(&pagesDir, "pages", "", "path to pages directory (required)")
	flagSet.StringVar(&outDir, "out", "", "path to output directory (required)")
	flagSet.StringVar(&templatesDir, "templates", "", "path to templates directory (required)")
	flagSet.StringVar(&orderPath, "order", "pages.yaml", "path to YAML file listing page order (optional)")
	_ = flagSet.Parse(os.Args[1:])
	if pagesDir == "" || outDir == "" || templatesDir == "" {
		fmt.Fprintln(os.Stderr, "error: --pages, --out, and --templates are required")
		os.Exit(2)
	}
	baseTplPath := filepath.Join(templatesDir, "base.gohtml")

	baseTpl := template.Must(template.New("base").Parse(mustReadFile(baseTplPath)))

	var pageFiles []string
	if data, err := os.ReadFile(orderPath); err == nil {
		ordered, perr := parseYAMLListOfStrings(string(data))
		if perr != nil {
			panic(perr)
		}
		for _, rel := range ordered {
			abs := filepath.Join(pagesDir, rel)
			if _, statErr := os.Stat(abs); statErr == nil {
				pageFiles = append(pageFiles, abs)
			} else {
				fmt.Fprintf(os.Stderr, "warning: listed page not found: %s\n", rel)
			}
		}
	}
	if len(pageFiles) == 0 {
		filepath.WalkDir(pagesDir, func(path string, d fs.DirEntry, err error) error {
			if err != nil {
				return err
			}
			if d.IsDir() {
				return nil
			}
			if strings.HasSuffix(d.Name(), ".html") {
				pageFiles = append(pageFiles, path)
			}
			return nil
		})
		sort.Strings(pageFiles)
	}

	// Build each page
	for _, pagePath := range pageFiles {
		raw := mustReadFile(pagePath)
		// Trim outer whitespace but keep inner spacing
		content := strings.TrimSpace(raw)

		// Title based on filename
		base := filepath.Base(pagePath)
		title := strings.TrimSuffix(base, filepath.Ext(base))

		var out bytes.Buffer
		if err := baseTpl.Execute(&out, map[string]any{
			"Title":        title,
			"Content":      template.HTML(content),
			"PageSizeAttr": pageSizeStyleAttr(config.PageWidthIn, config.PageHeightIn),
		}); err != nil {
			panic(err)
		}

		rel, _ := filepath.Rel(pagesDir, pagePath)
		outPath := filepath.Join(outDir, rel)
		if err := writeFile(outPath, out.Bytes()); err != nil {
			panic(err)
		}
	}

	// Build index.html via template
	indexPath := filepath.Join(outDir, "index.html")
	indexTplPath := filepath.Join(templatesDir, "index.gohtml")
	indexTpl := template.Must(template.New("index").Parse(mustReadFile(indexTplPath)))

	var pages []IndexPage
	for _, pagePath := range pageFiles {
		rel, _ := filepath.Rel(pagesDir, pagePath)
		pages = append(pages, IndexPage{
			Name: filepath.Base(pagePath),
			// Index is written to outDir, and the compiled pages are also in outDir.
			// So iframe src should be relative to index location, not prefixed with outDir again.
			Href: filepath.ToSlash(rel),
		})
	}
	var outIndex bytes.Buffer
	if err := indexTpl.Execute(&outIndex, map[string]any{"Pages": pages}); err != nil {
		panic(err)
	}
	if err := writeFile(indexPath, outIndex.Bytes()); err != nil {
		panic(err)
	}

	// Prepare pages for aggregate printing (2-up, etc.)
	var printPages []PrintPage
	for _, pagePath := range pageFiles {
		content := strings.TrimSpace(mustReadFile(pagePath))
		printPages = append(printPages, PrintPage{Content: template.HTML(content)})
	}

	// Build 2-up print HTML (letter pages with two half-letter pages side by side)
	var sheets []TwoUpSheet
	for i := 0; i < len(printPages); i += 2 {
		left := printPages[i]
		var right *PrintPage
		if i+1 < len(printPages) {
			r := printPages[i+1]
			right = &r
		}
		sheets = append(sheets, TwoUpSheet{Left: left, Right: right})
	}
	print2TplPath := filepath.Join(templatesDir, "print_2up.gohtml")
	print2Tpl := template.Must(template.New("print2up").Parse(mustReadFile(print2TplPath)))
	var outPrint2 bytes.Buffer
	if err := print2Tpl.Execute(&outPrint2, map[string]any{"Sheets": sheets}); err != nil {
		panic(err)
	}
	if err := writeFile(filepath.Join(outDir, "print_2up.html"), outPrint2.Bytes()); err != nil {
		panic(err)
	}
}

// parseYAMLListOfStrings expects a YAML document that is a top-level sequence of strings.
// We avoid adding a dependency to keep the builder lightweight; this is minimal and strict.
func parseYAMLListOfStrings(src string) ([]string, error) {
	var out []string
	for _, line := range strings.Split(src, "\n") {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		if strings.HasPrefix(line, "-") {
			item := strings.TrimSpace(strings.TrimPrefix(line, "-"))
			if item != "" {
				out = append(out, item)
			}
		} else {
			// For simplicity, only support list form
			return nil, fmt.Errorf("invalid yaml line (expect '- item'): %s", line)
		}
	}
	return out, nil
}
