package morning

import (
	"fmt"
	"sort"
	"strings"
	"time"

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

// The day plan (Vikunja #128).
//
// It lives here, with the morning routine engine, because it is the same
// question asked at a different scale: the routine knows what is still missing
// from a window, the plan knows what the whole day holds. A parallel system
// would have to re-read the same facts and re-decide what "today" means.
//
// It is pure, like the rest of this package: the daemon reads the calendar,
// the reminders and the checklist facts, and BuildPlan puts them in order.
//
// It is also NOT a nag. A plan she can recite when asked is the whole feature;
// nothing here fires, schedules or announces. Unprompted delivery stays with
// the existing morning nudge and the dispatcher's policy.

// PlanKind — where a plan line came from. It survives into the reply and the
// web view because the three read differently: an event is something happening
// to the owner, a reminder is something he asked for, a checklist item is
// something he has not done yet.
type PlanKind string

const (
	PlanEvent     PlanKind = "event"
	PlanReminder  PlanKind = "reminder"
	PlanChecklist PlanKind = "checklist"
)

// PlanEntry — one timed thing on the day, as the daemon read it out of the
// store. Text is rendered verbatim; the plan does not rephrase.
//
// Uncertain marks provenance below a full-confidence read — a work meeting
// relayed off a phone notification (#126). It travels through to the reply so
// she hedges instead of reciting a guess as fact.
type PlanEntry struct {
	At        time.Time
	Text      string
	Kind      PlanKind
	Uncertain bool
}

// Plan — the ordered day. Date is the calendar day it describes. Rest marks a
// plan trimmed by After, which changes what an empty one means: a day with
// nothing on it and a day whose last item has passed are different answers.
type Plan struct {
	Date  time.Time
	Items []PlanEntry
	Rest  bool
}

// BuildPlan orders everything known about the day Now falls on: calendar
// events, pending reminders, and one line per morning routine that still has
// unfinished items.
//
// Entries outside that calendar day are dropped — a plan for today that
// includes tomorrow's meeting is wrong in a way that is worse than terse.
// Ordering is by time, then by kind, then by text, so the same day always reads
// the same way.
func BuildPlan(routines []Routine, facts map[string]store.Fact, events, reminders []PlanEntry, now time.Time) Plan {
	y, m, d := now.Date()
	dayStart := time.Date(y, m, d, 0, 0, 0, 0, now.Location())
	dayEnd := dayStart.AddDate(0, 0, 1)

	p := Plan{Date: dayStart}
	for _, group := range [][]PlanEntry{events, reminders} {
		for _, e := range group {
			at := e.At.In(now.Location())
			if at.Before(dayStart) || !at.Before(dayEnd) {
				continue
			}
			if strings.TrimSpace(e.Text) == "" {
				continue
			}
			e.At = at
			p.Items = append(p.Items, e)
		}
	}
	p.Items = append(p.Items, checklistEntries(routines, facts, now)...)

	sort.SliceStable(p.Items, func(i, j int) bool {
		a, b := p.Items[i], p.Items[j]
		if !a.At.Equal(b.At) {
			return a.At.Before(b.At)
		}
		if a.Kind != b.Kind {
			return a.Kind < b.Kind
		}
		return a.Text < b.Text
	})
	return p
}

// checklistEntries renders one line per routine with work left in it, placed at
// the routine's nudge time — where the checklist actually matters in the day.
// A routine that does not apply today, has not opened yet, or is already
// complete contributes nothing: the plan says what is left, not what was done.
//
// A closed window still counts. Asked at 14:00 with the morning routine
// unfinished, the plan used to say nothing about it, because Evaluate reports
// Active only inside the window. What he skipped is the one thing the plan can
// tell him that the calendar cannot, and the entry sorts to its nudge time, not
// to the moment of asking.
func checklistEntries(routines []Routine, facts map[string]store.Fact, now time.Time) []PlanEntry {
	var out []PlanEntry
	for _, r := range routines {
		missing := Outstanding(r, facts, now)
		if len(missing) == 0 {
			continue
		}
		labels := make([]string, 0, len(missing))
		for _, it := range missing {
			label := it.Label
			if label == "" {
				label = it.Key
			}
			labels = append(labels, label)
		}
		at := r.NudgeAt
		if at == "" {
			at = r.WindowEnd
		}
		when, ok := todayAt(at, now)
		if !ok {
			continue
		}
		out = append(out, PlanEntry{
			At:   when,
			Text: fmt.Sprintf("%s — осталось: %s", r.Name, strings.Join(labels, ", ")),
			Kind: PlanChecklist,
		})
	}
	return out
}

// After returns the part of the plan that has not happened yet — the answer to
// "что дальше?" as opposed to "какие планы на сегодня?". The Date is kept, so an
// empty result still knows which day it is empty for.
func (p Plan) After(now time.Time) Plan {
	out := Plan{Date: p.Date, Rest: true}
	for _, it := range p.Items {
		if it.At.Before(now) {
			continue
		}
		out.Items = append(out.Items, it)
	}
	return out
}

// FormatRU renders the plan as maven says it. Feminine self-reference,
// informal address, no pet names — and no exhortation: she reads the day back,
// she does not tell him to get on with it.
func (p Plan) FormatRU() string {
	if len(p.Items) == 0 {
		// "что дальше?" after the last item of the day. The day was not empty,
		// it is over, and saying it was empty is a false statement about a day
		// he just lived.
		if p.Rest {
			return say.S(say.PlanRestEmpty, nil)
		}
		return say.S(say.PlanDayEmpty, map[string]string{"date": p.Date.Format("02.01.2006")})
	}
	parts := make([]string, len(p.Items))
	for i, it := range p.Items {
		line := fmt.Sprintf("%s — %s", it.At.Format("15:04"), it.Text)
		if it.Uncertain {
			line = say.S(say.PlanUncertain, map[string]string{"line": line})
		}
		parts[i] = line
	}
	return say.S(say.PlanDay, map[string]string{
		"date":  p.Date.Format("02.01.2006"),
		"items": strings.Join(parts, "; "),
	})
}
