package main

import (
	"context"
	"log"
	"strings"

	"github.com/kami/maven/internal/router"
	"github.com/kami/maven/internal/store"
)

// Standing lists on the voice path (Vikunja #453).
//
// Three halves, mirroring what task capture already does: an add that runs at
// the top of actionNote, a read-back query source, and a crossing-off that runs
// on the same note path because "всё купил" is note-shaped.
//
// These read h.dataStore rather than the CoreAPI. A list is local to the core
// and nothing outside it writes one: the web UI has no list page, no reach
// files groceries, and the digestion worker does not read the table. When
// something outside mavend needs to add to a list, the ipc seam is what it
// grows through — the intake rules that CaptureTaskReq documents are about
// shared intake, and there is none here yet.
//
// Nothing here speaks unprompted. A list is answered when asked about.

// captureListFromNote claims the turn when the utterance adds to, clears, or
// crosses one item off a list. ("", false) hands the turn back to the note path.
func (h *reactiveHandler) captureListFromNote(ctx context.Context, dec router.Decision) (string, bool) {
	if h.dataStore == nil {
		return "", false
	}
	// Clearing is read before removing on purpose: "всё купил" and "купил
	// молоко" start with the same word, and only the second one names an item.
	if list, ok := router.ParseListClear(dec.Utterance); ok {
		n, err := h.dataStore.ClearList(ctx, list, h.now())
		if err != nil {
			log.Printf("voice: clear list: %v", err)
			return "не получилось обновить список.", true
		}
		if n == 0 {
			return "в списке и так ничего не было.", true
		}
		return "вычеркнула всё, список пустой.", true
	}
	if cap, ok := router.ParseListRemove(dec.Utterance); ok {
		if reply, ok := h.removeListItem(ctx, cap); ok {
			return reply, true
		}
		// Nothing on the list by that name. "купил новый ноутбук" is a note and
		// must stay one, so the turn goes back rather than claiming a removal
		// that removed nothing.
		return "", false
	}
	cap, ok := router.ParseListCapture(dec.Utterance)
	if !ok {
		return "", false
	}
	res, err := h.dataStore.AddListItem(ctx, store.ListItem{
		List:      cap.List,
		Item:      cap.Item,
		Source:    "tap:voice",
		CreatedTs: h.now(),
	})
	if err != nil {
		log.Printf("voice: add list item: %v", err)
		return "не получилось добавить в список.", true
	}
	if !res.Created {
		return cap.Item + " уже в списке.", true
	}
	return "добавила в список: " + cap.Item + ".", true
}

// removeListItem crosses one named item off. It reports false when the list
// holds nothing by that name, which is what keeps the marker words from
// swallowing ordinary notes.
func (h *reactiveHandler) removeListItem(ctx context.Context, cap router.ListCapture) (string, bool) {
	items, err := h.dataStore.ListItems(ctx, cap.List, "")
	if err != nil {
		log.Printf("voice: list items: %v", err)
		return "", false
	}
	want := store.NormalizeTaskText(cap.Item)
	for _, li := range items {
		if store.NormalizeTaskText(li.Item) != want {
			continue
		}
		if err := h.dataStore.SetListItemStatus(ctx, li.ID, store.ListItemDone, h.now()); err != nil {
			log.Printf("voice: cross off list item: %v", err)
			return "не получилось обновить список.", true
		}
		return "вычеркнула: " + li.Item + ".", true
	}
	return "", false
}

// listFloor — the keyword test behind topicList, in the shape turnIsAbout takes.
func listFloor(u string) bool {
	_, ok := router.ParseListQuery(u)
	return ok
}

// queryList — "что в списке покупок?", "что мне купить?".
//
// A query source, so it sits in querySources and either claims the turn or
// passes it on. It is before the recall sources for the reason every specific
// source is: the notes pass would otherwise answer a list question with
// whatever note is nearest.
func (h *reactiveHandler) queryList(ctx context.Context, t *queryTurn) (string, bool) {
	if h.dataStore == nil {
		return "", false
	}
	// The seeds decide the subject and listQueryPrefixes is the floor behind
	// them (V-522). Which list he named is a noun lookup either way.
	if !h.turnIsAbout(ctx, t, topicList, listFloor) {
		return "", false
	}
	list := router.ListNamedIn(t.dec.Utterance)
	items, err := h.dataStore.ListItems(ctx, list, "")
	if err != nil {
		log.Printf("voice: list items: %v", err)
		return "не получилось посмотреть список.", true
	}
	return formatListRU(list, items), true
}

// formatListRU reads a list aloud. One sentence, comma-separated, because a
// shopping list is heard in a shop and a numbered recital is unusable there.
func formatListRU(list string, items []store.ListItem) string {
	name := "списке " + listGenitive(list)
	if len(items) == 0 {
		return "в " + name + " пусто."
	}
	names := make([]string, 0, len(items))
	for _, li := range items {
		names = append(names, li.Item)
	}
	return "в " + name + ": " + strings.Join(names, ", ") + "."
}

// listGenitive puts a list tag into the case "список <…>" needs. Russian
// declines the noun and she must not say "в списке покупки".
func listGenitive(list string) string {
	switch list {
	case "покупки":
		return "покупок"
	case "аптека":
		return "аптеки"
	case "хозяйство":
		return "хозяйства"
	}
	return list
}
