package main

import (
	"bytes"
	"fmt"
	"strconv"
	"strings"
	"text/template"
)

// formatRupiah memformat bilangan bulat dengan pemisah ribuan,
// contoh: 1234567 menjadi "1.234.567".
func formatRupiah(n int) string {
	s := strconv.Itoa(n)
	var b strings.Builder
	awal := len(s) % 3
	if awal > 0 {
		b.WriteString(s[:awal])
	}
	for i := awal; i < len(s); i += 3 {
		if b.Len() > 0 {
			b.WriteByte('.')
		}
		b.WriteString(s[i : i+3])
	}
	return b.String()
}

// potongan menghitung nilai diskon; error dikembalikan bila
// persen berada di luar rentang yang masuk akal.
func potongan(total, persen int) (int, error) {
	if persen < 0 || persen > 100 {
		return 0, fmt.Errorf("persen %d di luar rentang 0-100", persen)
	}
	return total * persen / 100, nil
}

// Pembelian adalah data yang dirender menjadi nota singkat.
type Pembelian struct {
	NamaToko  string
	Pelanggan string
	Member    bool
	Items     []string
	Total     int
}

func main() {
	fm := template.FuncMap{
		"upper":        strings.ToUpper,
		"formatRupiah": formatRupiah,
		"potongan":     potongan,
	}

	const nota = `TOKO     : {{upper .NamaToko}}
PELANGGAN: {{.Pelanggan}}
ITEM     : {{len .Items}} buah ({{index .Items 0}} dst)
TOTAL    : {{.Total | formatRupiah}}
DISKON   : {{potongan .Total 10 | formatRupiah}}
STATUS   : {{if .Member}}MEMBER{{else}}UMUM{{end}}`

	// Funcs wajib dipanggil SEBELUM Parse agar nama fungsi
	// dikenali saat string template diurai.
	t := template.Must(template.New("nota").Funcs(fm).Parse(nota))

	data := Pembelian{
		NamaToko:  "Toko Contoh",
		Pelanggan: "Siti Rahma",
		Member:    true,
		Items:     []string{"Buku Go", "Mug", "Stiker"},
		Total:     1234567,
	}

	var buf bytes.Buffer
	if err := t.Execute(&buf, data); err != nil {
		panic(err)
	}
	fmt.Println(buf.String())
	fmt.Println()

	// Fungsi dua-nilai: error yang dikembalikan menghentikan eksekusi.
	const salah = `DISKON: {{potongan .Total 150 | formatRupiah}}`
	t2 := template.Must(template.New("nota-salah").Funcs(fm).Parse(salah))

	var buf2 bytes.Buffer
	err := t2.Execute(&buf2, data)
	if err != nil {
		fmt.Println("eksekusi gagal:", err)
	}

	// Nama fungsi divalidasi saat Parse, bukan saat Execute:
	// template ini diurai tanpa pemanggilan Funcs lebih dulu.
	_, err3 := template.New("terlambat").Parse(`{{upper .NamaToko}}`)
	fmt.Println("parse gagal:", err3)
}
