package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"
	"slices"
	"strings"
)

// Katalog menyimpan daftar nama produk sebagai berkas JSON di dalam
// sebuah direktori.
type Katalog struct {
	direktori string
}

// KatalogBaru menyiapkan katalog yang menyimpan datanya di dalam
// direktori tersebut.
func KatalogBaru(direktori string) (*Katalog, error) {
	if err := os.MkdirAll(direktori, 0o755); err != nil {
		return nil, fmt.Errorf("siapkan direktori: %w", err)
	}
	return &Katalog{direktori: direktori}, nil
}

// Simpan menyimpan daftar nama produk dengan kunci id.
func (k *Katalog) Simpan(id string, nama []string) error {
	berkas := filepath.Join(k.direktori, id+".json")
	data, err := json.Marshal(nama)
	if err != nil {
		return fmt.Errorf("marshal %s: %w", id, err)
	}
	if err := os.WriteFile(berkas, data, 0o644); err != nil {
		return fmt.Errorf("tulis %s: %w", berkas, err)
	}
	return nil
}

// Muat membaca kembali daftar nama produk dengan kunci id.
func (k *Katalog) Muat(id string) ([]string, error) {
	berkas := filepath.Join(k.direktori, id+".json")
	data, err := os.ReadFile(berkas)
	if err != nil {
		return nil, fmt.Errorf("baca %s: %w", berkas, err)
	}
	var nama []string
	if err := json.Unmarshal(data, &nama); err != nil {
		return nil, fmt.Errorf("unmarshal %s: %w", id, err)
	}
	return nama, nil
}

// DaftarID mengembalikan seluruh id yang tersimpan, terurut naik.
func (k *Katalog) DaftarID() ([]string, error) {
	entri, err := os.ReadDir(k.direktori)
	if err != nil {
		return nil, fmt.Errorf("baca direktori: %w", err)
	}
	var ids []string
	for _, e := range entri {
		if filepath.Ext(e.Name()) == ".json" {
			ids = append(ids, strings.TrimSuffix(e.Name(), ".json"))
		}
	}
	slices.Sort(ids)
	return ids, nil
}

// Impor membaca nama produk baris demi baris dari sebuah berkas di
// dalam fsys, menyimpannya dengan kunci id, lalu mengembalikan nama
// yang dibaca. fs.FS memungkinkan sumber berkas nyata, berkas hasil
// embed, maupun berkas palsu buatan fstest.MapFS.
func (k *Katalog) Impor(fsys fs.FS, id, namaBerkas string) ([]string, error) {
	f, err := fsys.Open(namaBerkas)
	if err != nil {
		return nil, fmt.Errorf("buka %s: %w", namaBerkas, err)
	}
	defer f.Close()

	var nama []string
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		if teks := scanner.Text(); teks != "" {
			nama = append(nama, teks)
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("baca %s: %w", namaBerkas, err)
	}
	if err := k.Simpan(id, nama); err != nil {
		return nil, err
	}
	return nama, nil
}
