package main

import (
	"bytes"
	"fmt"
	"html/template"
	"net/http"
	"net/http/httptest"
	"strings"
)

// Artikel adalah entitas konten yang dirender menjadi halaman.
type Artikel struct {
	Slug  string
	Judul string
	Isi   string
}

// repoArtikel adalah repository in-memory pengganti database.
type repoArtikel struct {
	data []Artikel
}

// Semua mengembalikan seluruh artikel.
func (r repoArtikel) Semua() []Artikel {
	return r.data
}

// Cari mencari satu artikel berdasarkan slug.
func (r repoArtikel) Cari(slug string) (Artikel, bool) {
	for _, a := range r.data {
		if a.Slug == slug {
			return a, true
		}
	}
	return Artikel{}, false
}

// tmpl diurai SEKALI saat package diinisialisasi. Kesalahan template
// harus menggagalkan startup lewat template.Must, bukan menghantui
// request pertama pengguna.
var tmpl = template.Must(template.New("layout").Parse(`
{{define "header"}}<!DOCTYPE html>
<html lang="id">
<head><meta charset="utf-8"><title>{{.Judul}} — Blog Contoh</title></head>
<body>
{{end}}{{define "footer"}}</body>
</html>
{{end}}{{define "list"}}{{template "header" .}}
<h1>{{.Judul}}</h1>
<ul>
{{range .Artikel}}<li><a href="/articles/{{.Slug}}">{{.Judul}}</a></li>
{{end}}
</ul>
{{template "footer" .}}
{{end}}{{define "show"}}{{template "header" .}}
<h1>{{.Judul}}</h1>
<p>{{.Isi}}</p>
<p><a href="/">kembali ke daftar</a></p>
{{template "footer" .}}
{{end}}
`))

// server menyatukan repository dan router.
type server struct {
	repo repoArtikel
	mux  *http.ServeMux
}

func serverBaru(repo repoArtikel) *server {
	s := &server{repo: repo, mux: http.NewServeMux()}
	s.mux.HandleFunc("GET /{$}", s.handleDaftar)
	s.mux.HandleFunc("GET /articles/{slug}", s.handleArtikel)
	return s
}

// handleDaftar merender halaman indeks dari seluruh artikel.
func (s *server) handleDaftar(w http.ResponseWriter, r *http.Request) {
	data := struct {
		Judul   string
		Artikel []Artikel
	}{
		Judul:   "Daftar Artikel",
		Artikel: s.repo.Semua(),
	}
	renderHTML(w, "list", data)
}

// handleArtikel merender satu artikel berdasarkan path value.
func (s *server) handleArtikel(w http.ResponseWriter, r *http.Request) {
	artikel, ok := s.repo.Cari(r.PathValue("slug"))
	if !ok {
		http.NotFound(w, r)
		return
	}
	renderHTML(w, "show", artikel)
}

// renderHTML merender template bernama ke buffer sebelum dikirim;
// header 500 dikirim bila render gagal, tanpa output setengah jadi.
func renderHTML(w http.ResponseWriter, nama string, data any) {
	var buf bytes.Buffer
	if err := tmpl.ExecuteTemplate(&buf, nama, data); err != nil {
		http.Error(w, "gagal merender halaman", http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	_, _ = w.Write(buf.Bytes())
}

func main() {
	repo := repoArtikel{data: []Artikel{
		{
			Slug:  "belajar-template",
			Judul: "Belajar Template",
			Isi:   "Go menyediakan html/template untuk merender halaman dengan <b>auto-escape</b>.",
		},
		{
			Slug:  "server-side-rendering",
			Judul: "Server-Side Rendering",
			Isi:   "Halaman dirender penuh di server lalu dikirim sebagai HTML jadi.",
		},
	}}
	srv := serverBaru(repo)

	// httptest mensimulasikan request tanpa jaringan nyata.
	target := []struct{ metode, alamat string }{
		{http.MethodGet, "/"},
		{http.MethodGet, "/articles/belajar-template"},
		{http.MethodGet, "/articles/tidak-ada"},
	}
	for i, t := range target {
		if i > 0 {
			fmt.Println()
		}
		req := httptest.NewRequest(t.metode, t.alamat, nil)
		rec := httptest.NewRecorder()

		srv.mux.ServeHTTP(rec, req)

		res := rec.Result()
		fmt.Printf("%s %-28s status=%d content-type=%q\n",
			t.metode, t.alamat, res.StatusCode, res.Header.Get("Content-Type"))
		body := rec.Body.String()
		fmt.Print(body)
		if res.StatusCode == http.StatusOK {
			fmt.Println("memuat data dari repository:", strings.Contains(body, "Belajar Template"))
		}
	}
}
