package ipc

import (
	"context"
	"time"
)

// CoreAPI and the eight domain interfaces it composes (Vikunja #408).
//
// It used to be one flat block of forty methods, and the cost of that shape
// was paid three times over: every method added an arm to the dispatcher, a
// stub to the daemon's locked API, and a stub to every test double. Two of
// those three are already gone — the dispatcher is a table (methodTable in
// server.go), and UnimplementedCoreAPI took the padding out of the doubles and
// out of lockedAPI, which no longer exists.
//
// What was left is the interface itself, and this file is that half. CoreAPI
// is unchanged as a type: the same forty methods, in the same order, so the
// wire contract, the client proxy and the store adapter are all untouched.
// What it gains is a named seam per domain, so a caller that only reads facts
// can say FactAPI and a reader can see which cluster a method belongs to
// without counting lines.
//
// Add a method to the domain it belongs to, not to CoreAPI.

// FactAPI — the fact store: write, read the current value, read history, and
// undo. Presence sits here because it is a hysteresis view over presence
// facts, not a store of its own.
type FactAPI interface {
	WriteFact(ctx context.Context, req WriteFactReq) (int64, error)
	LatestFact(ctx context.Context, key string) (Fact, error)
	LatestFactBySource(ctx context.Context, key, source string) (Fact, error)
	Since(ctx context.Context, key string, now time.Time) (time.Duration, error)
	Presence(ctx context.Context) (Presence, error)
	RecentFacts(ctx context.Context, n int) ([]Fact, error)
	RecentActiveFactsByKind(ctx context.Context, kind string, n int) ([]Fact, error)
	CalendarEvents(ctx context.Context, from, to time.Time) ([]Fact, error)
	RevertFact(ctx context.Context, key string) (int64, error)
}

// ReminderAPI — scheduled sends the owner asked for.
type ReminderAPI interface {
	CreateReminder(ctx context.Context, fire time.Time, payload, cron string) (int64, error)
	MarkReminder(ctx context.Context, id int64, status string) error
	CancelReminder(ctx context.Context, id int64) error
	ListReminders(ctx context.Context, n int) ([]Reminder, error)
	ListPendingReminders(ctx context.Context, n int) ([]Reminder, error)
}

// NudgeAPI — proactive sends Maven proposed, their outcomes, and the outbox
// they went out through.
type NudgeAPI interface {
	RecordNudge(ctx context.Context, rule, channel, message string, ts time.Time) (int64, error)
	ResolveNudge(ctx context.Context, id int64, outcome string, ts time.Time) error
	RecentOutcomes(ctx context.Context, rule string, n int) ([]string, error)
	RecentNudges(ctx context.Context, n int) ([]Nudge, error)
	// DeliveryAttempts reads the outbox, newest first. An empty status means
	// every status (Vikunja #390).
	DeliveryAttempts(ctx context.Context, status string, n int) ([]DeliveryAttempt, error)
}

// NoteAPI — free text he captured, plus the embedded recall over it.
type NoteAPI interface {
	WriteNote(ctx context.Context, ts time.Time, text string, embedding []float32, source string) (int64, error)
	QueryNotes(ctx context.Context, embedding []float32, k int) ([]Note, error)
	RecentNotes(ctx context.Context, n int) ([]Note, error)
	// RecentNotesFromSource — the newest n notes whose source starts with
	// prefix. Notes Maven read rather than heard (rss:, crawl:) are excluded
	// from recall, so this is the only way to reach them, and it keeps the feed
	// answer from being crowded out of a fixed window by his own notes.
	RecentNotesFromSource(ctx context.Context, prefix string, n int) ([]Note, error)
}

// ToolAPI — the capability allowlist and its read side.
//
// ProposeTool drafts an inert 'proposed' tool scaffold (maven-callable);
// returns whether a new proposal was written. EnableTool fills cmd +
// destructive and flips status to 'enabled'. DisableTool reverts an
// enabled tool back to proposed (it stays in the store, won't run).
// Enable/DisableTool gate at AuthStepUp (allowlist mutation, human-only);
// ProposeTool is maven-callable (no step-up — she has no passkey).
// LookupTool/ListTools read them.
// scope defaults to "homelab" when empty.
type ToolAPI interface {
	ProposeTool(ctx context.Context, name, utterance, scope string, ts time.Time) (bool, error)
	EnableTool(ctx context.Context, name string, cmd []string, destructive bool, scope string, ts time.Time) error
	DisableTool(ctx context.Context, name string) error
	DeleteTool(ctx context.Context, name string) error
	LookupTool(ctx context.Context, name string) (Tool, error)
	ListTools(ctx context.Context, status string) ([]Tool, error)

	// MCPServers reports the configured MCP servers and their health
	// (Vikunja #251). Read-only introspection for /tools — there is no
	// "call this tool" method on purpose: an MCP tool runs through the same
	// allowlist, confirm turn and act path as any other tool, and a second
	// mutation path would be a second thing to get wrong. Empty when the
	// mcp config block is absent, which is the default.
	MCPServers(ctx context.Context) ([]MCPServerStatus, error)
}

// RoutineAPI — the shapes of his day: routines Maven noticed and proposed, the
// morning checklist, and today's plan.
//
// MorningStatus and DayPlan are daemon-computed rather than stored, so the
// store adapter returns an error for both — the same shape as TickTrace.
type RoutineAPI interface {
	// ListProposedRoutines returns proposed routines, newest first.
	ListProposedRoutines(ctx context.Context) ([]ProposedRoutine, error)
	// DismissProposedRoutine flips a proposed routine to 'dismissed'.
	DismissProposedRoutine(ctx context.Context, id int64) error
	// AcceptProposedRoutine flips a proposed routine to 'accepted'. The tick
	// loop takes the schedule from there — no reminder is created (Vikunja #366).
	AcceptProposedRoutine(ctx context.Context, id int64) error

	// SeedEvent writes a backdated fact and runs extraction and detection over
	// it, so a proposal can be produced in one sitting rather than over real
	// days (Vikunja #518). See SeedEventReq for why this exists and what keeps
	// it shut. Daemon-computed, like MorningStatus — the store adapter refuses
	// it, because the detect-and-propose step lives in mavend.
	SeedEvent(ctx context.Context, req SeedEventReq) (SeedEventResp, error)

	// MorningStatus returns each configured morning routine's current
	// checklist state (see internal/morning): active today/now, which items
	// are done, which are still missing.
	MorningStatus(ctx context.Context) ([]MorningRoutineStatus, error)

	// DayPlan returns today's ordered plan — calendar events, pending
	// reminders and any morning checklist still outstanding (see
	// internal/morning.BuildPlan) — plus the spoken RU rendering of it.
	// Read-only: asking for the plan never dispatches or schedules anything.
	DayPlan(ctx context.Context) (DayPlan, error)
}

// TaskAPI — outstanding work, whatever surface it arrived from.
type TaskAPI interface {
	// CaptureTask records a task. See CaptureTaskReq — this is the single
	// intake seam for the voice path, the web form and the future email
	// extractor. Idempotent per live normalised text; the response says
	// whether a row was actually created.
	CaptureTask(ctx context.Context, req CaptureTaskReq) (CaptureTaskResp, error)
	// ListTasks returns tasks in one status, newest first. "" is every row,
	// "live" is candidate + open (outstanding work).
	ListTasks(ctx context.Context, status string) ([]Task, error)
	// SetTaskStatus moves a task forward once: candidate→open|dropped,
	// open→done|dropped. Any other move is refused.
	SetTaskStatus(ctx context.Context, id int64, status string, ts time.Time, by string) error
	// ResolveEntity asks Nexus for the canonical id behind a name. It is how a
	// surface turns "Kate" into an entity id before storing one, because
	// identity lives in Nexus and a local name is a second answer to a
	// question Nexus already owns. ErrNotImplemented when no Nexus is
	// configured, ErrNoEntity when the name matched nothing, and an Ambiguous
	// ref when it matched several — the caller asks, it does not pick.
	ResolveEntity(ctx context.Context, query string, types []string) (EntityRef, error)
	// EditTask rewrites the three fields capture set: text, due date and
	// weight. Status is not among them — that ladder is one-way and belongs to
	// SetTaskStatus. A resolved task is refused, and a text edit that would
	// duplicate another live task is refused rather than merged.
	EditTask(ctx context.Context, id int64, text string, due *time.Time, weight int) error
	// SetTaskFields writes the definition of done and the blocker. Not a
	// status move, so it is not one-way: he may sharpen a criterion, and a
	// blocker clears when the person answers. blockedOn is a canonical Nexus
	// entity id or empty, never a name the caller had lying around.
	SetTaskFields(ctx context.Context, id int64, doneWhen, blockedOn string) error
}

// SystemAPI — what the daemon knows about itself, plus the one method that
// runs a whole turn.
type SystemAPI interface {
	// TickTrace returns the most recent tick's rule trace. The daemon caches
	// this after every tick; the store adapter returns an error (trace is not
	// persisted — it's a daemon-level cache).
	TickTrace(ctx context.Context) (TickTrace, error)

	// TurnDecisions returns the newest turn arbitration records, newest first
	// (V-564). Same shape as TickTrace and RecentEvents: a bounded in-memory
	// ring on the daemon, so the store adapter returns an error rather than
	// pretending a table exists. Empty is a normal answer — it means no turn
	// has run since the daemon started.
	TurnDecisions(ctx context.Context, n int) ([]TurnDecision, error)

	// CorrectTurn records that one persisted turn was routed wrongly, and what
	// it should have been (V-630). shouldBe empty means "wrong, target
	// unstated", which is a usable negative and must not cost more to give than
	// the full answer. Unlike TurnDecisions this DOES reach a table, because a
	// correction is the only supervised signal the box gets and it has to
	// outlive the trace that carried it.
	CorrectTurn(ctx context.Context, traceID int64, shouldBe string) error

	// RecentEcosystemTraces reads the ecosystem call log, which lives in its
	// own table so machine-rate traces never crowd out human-rate facts.
	RecentEcosystemTraces(ctx context.Context, n int) ([]EcosystemTrace, error)

	// RecentEvents returns the daemon's unified intake journal, newest first
	// (Vikunja #283) — one envelope per thing that arrived, whatever direction
	// it came from: a relayed notification, a mail candidate, a feed item, a
	// changed page, a spend, a presence probe.
	//
	// Read-only and daemon-cached, the same shape as TickTrace and DayPlan:
	// the store adapter returns an error, because the journal is a bounded
	// in-memory ring and not a table. Its contents are a window over intake,
	// never the durable record — that is still the fact, note or task the
	// intake path wrote.
	RecentEvents(ctx context.Context, n int) ([]IntakeEvent, error)

	// Chat routes a text utterance through the reactive handler's core path
	// (router → dialogue → action → replier) and returns the reply text.
	// No audio or stt/tts — for text channels (mavweb, telegram).
	//
	// conversation names the thread. A parked clarifying question is held per
	// conversation, so an unanswered question on one reach cannot eat the next
	// utterance from another (Vikunja #466). Empty means the unattributed text
	// tap and is still one conversation of its own, separate from the mic.
	Chat(ctx context.Context, conversation, text string) (ChatReply, error)
}

// CoreAPI — what core exposes to modules. One Go interface, satisfied by:
//   - the in-process store adapter (storeapi.go) — used by the daemon
//     for modules that live in-process for now (router, delivery) and by tests,
//   - the socket-backed server's dispatcher (which delegates to a CoreAPI),
//   - the client proxy (client.go) — same interface, over the wire.
//
// So a module imports ipc, holds a CoreAPI, and is agnostic to whether it's
// been wired in-process (tests / daemon-embedded) or socketed (full topology).
// That swappability is the seam the auth layer inserts into without touching
// the module code.
type CoreAPI interface {
	FactAPI
	ReminderAPI
	NudgeAPI
	NoteAPI
	ToolAPI
	RoutineAPI
	TaskAPI
	SystemAPI
}
