package tasks

import (
	"fmt"
	"strings"
	"time"

	"github.com/kami/maven/internal/say"
)

// Counted stall shapes (Vikunja #512, step 5 of docs/plans/15-board-surface.md).
//
// She may count. She may not assess. Every shape here is arithmetic over rows he
// can see — how many, how long, how many undated — and none of it says whether a
// task matters, whether a blocker is real, or whether something should be
// dropped. That is the line internal/memory/behavior.go already drew for habits
// and the reason is the same: a 1.7B asked to judge will agree fluently and
// launder a guess into a decision.
//
// Not a nag either. Nothing here is read by the tick loop; the counts go on
// /tasks and into the answer when he asks for the list. tickLoop.dayPlan
// deliberately does not read tasks — keep it that way.

// StallDays — how long a live task has to have sat before it is counted as
// sitting. Ten days rather than a week, because a task captured on a Friday and
// still open the next Friday is an ordinary week, not a stall.
//
// Measured from Created, which is the only clock a live row carries: the store
// stamps resolved_ts and nothing else, so "no state change in eleven days" is
// exactly "captured eleven days ago and still live". That is a narrower claim
// than the plan's wording and it is the one the data supports.
const StallDays = 10

// Stall — one counted shape: how many tasks, and the sentence that says what
// they have in common. N is always ≥ 1; a shape with no tasks in it is not
// returned, because "нет просроченных" is a reassurance nobody asked for.
type Stall struct {
	N    int
	Line string
}

// Stalls counts the shapes present in a set of live tasks, in a fixed order:
// overdue first, then sitting, then unconfirmed. Fixed because the order is what
// he reads first, and sorting by count would move the sections around every time
// one number changed.
//
// Resolved tasks are not passed in and would not be counted if they were: this
// is a statement about outstanding work.
func Stalls(items []Item, now time.Time) []Stall {
	var overdue, sitting, unconfirmed int
	for _, it := range items {
		if it.Status == StatusCandidate {
			unconfirmed++
			// A candidate is Maven's reading of something she read. Counting it as
			// overdue would put her own guess about a deadline in a number he is
			// meant to act on.
			continue
		}
		// Whole calendar days, the reading Rank already uses. An instant
		// comparison calls a task due at 18:00 overdue from 18:01, so the count
		// above the table would say "просрочено" beside a row whose own reason
		// still said "сегодня".
		if it.Due != nil && dayDelta(*it.Due, now) < 0 {
			overdue++
		}
		// A row with no capture time has not sat for anything. Without the
		// guard its zero time is January of year 1 and it always counts.
		if !it.Created.IsZero() && now.Sub(it.Created) >= StallDays*24*time.Hour {
			sitting++
		}
	}
	var out []Stall
	add := func(n int, key string, args map[string]string) {
		if n == 0 {
			return
		}
		out = append(out, Stall{N: n, Line: say.S(key, args)})
	}
	add(overdue, say.StallOverdue, map[string]string{
		"n": fmt.Sprint(overdue), "word": say.CountWord(overdue, "задача", "задачи", "задач"),
	})
	add(sitting, say.StallSitting, map[string]string{
		"n": fmt.Sprint(sitting), "word": say.CountWord(sitting, "задача", "задачи", "задач"),
		"days": fmt.Sprint(StallDays), "dayword": say.Days(StallDays),
	})
	add(unconfirmed, say.StallUnconfirmed, map[string]string{
		"n": fmt.Sprint(unconfirmed), "word": say.CountWord(unconfirmed, "задача", "задачи", "задач"),
	})
	return out
}

// StallsRU joins the shapes into one sentence for the spoken list, or "" when
// there are none. Empty on purpose: the list read is the answer to his question,
// and appending "ничего не залежалось" to it every time is a nag with a friendly
// face.
func StallsRU(stalls []Stall) string {
	if len(stalls) == 0 {
		return ""
	}
	parts := make([]string, 0, len(stalls))
	for _, s := range stalls {
		parts = append(parts, s.Line)
	}
	return strings.Join(parts, "; ") + "."
}
