package main

import (
	"flag"
	"fmt"
	"io"
	"slices"
	"strconv"
	"text/tabwriter"
	"time"
)

// Kontrak exit code program. Nilai ini dibaca oleh skrip lain
// atau pipeline yang memanggil tugas.
const (
	exitSukses     = 0 // perintah berhasil
	exitPenggunaan = 1 // subperintah tak dikenal atau argumen salah
	exitData       = 2 // id tak ditemukan atau kegagalan penyimpanan
)

// Run menjalankan satu subperintah dan mengembalikan exit code.
// Parameter sekarang menyediakan waktu saat ini sehingga test dapat
// membekukannya; out menampung hasil perintah dan errOut menampung
// pesan galat agar keduanya tidak tercampur.
func Run(args []string, penyimpanan *Penyimpanan, sekarang func() time.Time, out, errOut io.Writer) int {
	if len(args) == 0 {
		tampilkanPenggunaan(errOut)
		return exitPenggunaan
	}

	perintah, sisa := args[0], args[1:]
	switch perintah {
	case "tambah":
		return perintahTambah(sisa, penyimpanan, sekarang, out, errOut)
	case "list":
		return perintahList(sisa, penyimpanan, out, errOut)
	case "selesai":
		return perintahSelesai(sisa, penyimpanan, out, errOut)
	case "hapus":
		return perintahHapus(sisa, penyimpanan, out, errOut)
	default:
		fmt.Fprintf(errOut, "subperintah %q tidak dikenal\n", perintah)
		tampilkanPenggunaan(errOut)
		return exitPenggunaan
	}
}

func tampilkanPenggunaan(errOut io.Writer) {
	fmt.Fprintln(errOut, "Penggunaan:")
	fmt.Fprintln(errOut, "  tugas [-data <berkas>] tambah \"<judul>\"")
	fmt.Fprintln(errOut, "  tugas [-data <berkas>] list [--semua]")
	fmt.Fprintln(errOut, "  tugas [-data <berkas>] selesai <id>")
	fmt.Fprintln(errOut, "  tugas [-data <berkas>] hapus <id>")
}

func perintahTambah(args []string, p *Penyimpanan, sekarang func() time.Time, out, errOut io.Writer) int {
	if len(args) != 1 {
		fmt.Fprintln(errOut, "perintah tambah membutuhkan satu judul tugas.")
		tampilkanPenggunaan(errOut)
		return exitPenggunaan
	}

	judul := args[0]
	if err := ValidasiJudul(judul); err != nil {
		fmt.Fprintln(errOut, err)
		return exitPenggunaan
	}

	tasks, err := p.Muat()
	if err != nil {
		fmt.Fprintln(errOut, "galat memuat tugas:", err)
		return exitData
	}

	t := Task{
		ID:     IDBerikutnya(tasks),
		Judul:  judul,
		Dibuat: sekarang(),
	}
	tasks = append(tasks, t)

	if err := p.Simpan(tasks); err != nil {
		fmt.Fprintln(errOut, "galat menyimpan tugas:", err)
		return exitData
	}

	fmt.Fprintf(out, "Tugas %d \"%s\" tersimpan.\n", t.ID, t.Judul)
	return exitSukses
}

func perintahList(args []string, p *Penyimpanan, out, errOut io.Writer) int {
	semua := false
	fs := flag.NewFlagSet("list", flag.ContinueOnError)
	fs.SetOutput(errOut)
	fs.BoolVar(&semua, "semua", false, "tampilkan juga tugas yang sudah selesai")
	if err := fs.Parse(args); err != nil {
		return exitPenggunaan
	}

	tasks, err := p.Muat()
	if err != nil {
		fmt.Fprintln(errOut, "galat memuat tugas:", err)
		return exitData
	}

	terpilih := tasks
	if !semua {
		terpilih = TugasAktif(tasks)
	}

	switch {
	case len(tasks) == 0:
		fmt.Fprintln(out, "Belum ada tugas tersimpan.")
	case len(terpilih) == 0:
		fmt.Fprintln(out, "Tidak ada tugas yang belum selesai.")
	default:
		slices.SortStableFunc(terpilih, Bandingkan)
		cetakTabel(out, terpilih)
	}
	return exitSukses
}

func cetakTabel(out io.Writer, tasks []Task) {
	w := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
	fmt.Fprintln(w, "ID\tSTATUS\tJUDUL")
	for _, t := range tasks {
		status := "[ ]"
		if t.Selesai {
			status = "[x]"
		}
		fmt.Fprintf(w, "%d\t%s\t%s\n", t.ID, status, t.Judul)
	}
	w.Flush()
}

func perintahSelesai(args []string, p *Penyimpanan, out, errOut io.Writer) int {
	id, ok := parseID(args, "selesai", errOut)
	if !ok {
		return exitPenggunaan
	}

	tasks, err := p.Muat()
	if err != nil {
		fmt.Fprintln(errOut, "galat memuat tugas:", err)
		return exitData
	}

	i := CariIndeks(tasks, id)
	if i < 0 {
		fmt.Fprintf(errOut, "tugas dengan id %d tidak ditemukan.\n", id)
		return exitData
	}

	tasks[i].Selesai = true
	if err := p.Simpan(tasks); err != nil {
		fmt.Fprintln(errOut, "galat menyimpan tugas:", err)
		return exitData
	}

	fmt.Fprintf(out, "Tugas %d \"%s\" ditandai selesai.\n", tasks[i].ID, tasks[i].Judul)
	return exitSukses
}

func perintahHapus(args []string, p *Penyimpanan, out, errOut io.Writer) int {
	id, ok := parseID(args, "hapus", errOut)
	if !ok {
		return exitPenggunaan
	}

	tasks, err := p.Muat()
	if err != nil {
		fmt.Fprintln(errOut, "galat memuat tugas:", err)
		return exitData
	}

	i := CariIndeks(tasks, id)
	if i < 0 {
		fmt.Fprintf(errOut, "tugas dengan id %d tidak ditemukan.\n", id)
		return exitData
	}

	terhapus := tasks[i]
	tasks = slices.Delete(tasks, i, i+1)
	if err := p.Simpan(tasks); err != nil {
		fmt.Fprintln(errOut, "galat menyimpan tugas:", err)
		return exitData
	}

	fmt.Fprintf(out, "Tugas %d \"%s\" dihapus.\n", terhapus.ID, terhapus.Judul)
	return exitSukses
}

func parseID(args []string, perintah string, errOut io.Writer) (int, bool) {
	if len(args) != 1 {
		fmt.Fprintf(errOut, "perintah %s membutuhkan satu id tugas.\n", perintah)
		tampilkanPenggunaan(errOut)
		return 0, false
	}

	id, err := strconv.Atoi(args[0])
	if err != nil || id <= 0 {
		fmt.Fprintf(errOut, "id tugas harus berupa angka bulat positif, bukan %q.\n", args[0])
		return 0, false
	}
	return id, true
}
