// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package crud

import (
	"context"
	"fmt"
	"strings"
	"time"

	"git.secluded.site/go-lunatask"
	"git.secluded.site/lune/internal/mcp/shared"
	"github.com/google/jsonschema-go/jsonschema"
	"github.com/modelcontextprotocol/go-sdk/mcp"
)

// QueryToolName is the name of the consolidated query tool.
const QueryToolName = "query"

// QueryToolDescription describes the query tool for LLMs.
const QueryToolDescription = `Query Lunatask entities. Fallback for agents without MCP resource support.

Provide id for single entity details, omit for filtered list.
E2E encryption means names/content unavailable in lists—only metadata.
Prefer MCP resources (lunatask://tasks/today, lunatask://areas) when available.`

// QueryToolAnnotations returns hints about tool behavior.
func QueryToolAnnotations() *mcp.ToolAnnotations {
	return &mcp.ToolAnnotations{
		ReadOnlyHint:  true,
		OpenWorldHint: ptr(true),
		Title:         "Query entities",
	}
}

// QueryInputSchema returns a custom schema with enum constraints.
func QueryInputSchema() *jsonschema.Schema {
	schema, _ := jsonschema.For[QueryInput](nil)

	schema.Properties["entity"].Enum = []any{
		EntityTask, EntityNote, EntityPerson, EntityArea, EntityGoal, EntityNotebook, EntityHabit,
	}
	schema.Properties["status"].Enum = toAnyStrings(lunatask.AllTaskStatuses())

	return schema
}

// QueryInput is the input schema for the consolidated query tool.
type QueryInput struct {
	Entity string  `json:"entity"       jsonschema:"Entity type to query"`
	ID     *string `json:"id,omitempty" jsonschema:"UUID or deep link for single entity lookup"`

	// Task/Goal filters
	AreaID *string `json:"area_id,omitempty" jsonschema:"Filter by area (UUID, deep link, or config key)"`

	// Task filters
	Status           *string `json:"status,omitempty"            jsonschema:"Filter by task status"`
	IncludeCompleted *bool   `json:"include_completed,omitempty" jsonschema:"Include completed tasks (default: false)"`

	// Note filters
	NotebookID *string `json:"notebook_id,omitempty" jsonschema:"Filter by notebook UUID"`

	// Note/Person filters
	Source   *string `json:"source,omitempty"    jsonschema:"Filter by source identifier"`
	SourceID *string `json:"source_id,omitempty" jsonschema:"Filter by source-specific ID"`
}

// QueryOutput is the output schema for the consolidated query tool.
type QueryOutput struct {
	Entity string `json:"entity"`
	Count  int    `json:"count,omitempty"`
	// Results will be entity-specific; kept as any for flexibility
	Items any `json:"items,omitempty"`
	// Single item fields (when ID is provided)
	DeepLink string `json:"deep_link,omitempty"`
}

// HandleQuery queries entities based on the entity type.
func (h *Handler) HandleQuery(
	ctx context.Context,
	_ *mcp.CallToolRequest,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	switch input.Entity {
	case EntityTask:
		return h.queryTask(ctx, input)
	case EntityNote:
		return h.queryNote(ctx, input)
	case EntityPerson:
		return h.queryPerson(ctx, input)
	case EntityArea:
		return h.queryArea(ctx, input)
	case EntityGoal:
		return h.queryGoal(ctx, input)
	case EntityNotebook:
		return h.queryNotebook(ctx, input)
	case EntityHabit:
		return h.queryHabit(ctx, input)
	default:
		return shared.ErrorResult("invalid entity: must be task, note, person, area, goal, notebook, or habit"),
			QueryOutput{Entity: input.Entity}, nil
	}
}

// TaskSummary represents a task in list output.
type TaskSummary struct {
	DeepLink    string  `json:"deep_link"`
	Status      *string `json:"status,omitempty"`
	Priority    *int    `json:"priority,omitempty"`
	ScheduledOn *string `json:"scheduled_on,omitempty"`
	CreatedAt   string  `json:"created_at"`
	AreaID      *string `json:"area_id,omitempty"`
	GoalID      *string `json:"goal_id,omitempty"`
}

// TaskDetail represents detailed task information.
type TaskDetail struct {
	DeepLink    string  `json:"deep_link"`
	Status      *string `json:"status,omitempty"`
	Priority    *int    `json:"priority,omitempty"`
	Estimate    *int    `json:"estimate,omitempty"`
	ScheduledOn *string `json:"scheduled_on,omitempty"`
	CompletedAt *string `json:"completed_at,omitempty"`
	CreatedAt   string  `json:"created_at"`
	UpdatedAt   string  `json:"updated_at"`
	AreaID      *string `json:"area_id,omitempty"`
	GoalID      *string `json:"goal_id,omitempty"`
	Important   *bool   `json:"important,omitempty"`
	Urgent      *bool   `json:"urgent,omitempty"`
}

func (h *Handler) queryTask(
	ctx context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.ID != nil {
		return h.showTask(ctx, *input.ID)
	}

	return h.listTasks(ctx, input)
}

func (h *Handler) showTask(
	ctx context.Context,
	id string,
) (*mcp.CallToolResult, QueryOutput, error) {
	_, taskID, err := lunatask.ParseReference(id)
	if err != nil {
		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"),
			QueryOutput{Entity: EntityTask}, nil
	}

	task, err := h.client.GetTask(ctx, taskID)
	if err != nil {
		return shared.ErrorResult(err.Error()), QueryOutput{Entity: EntityTask}, nil
	}

	detail := TaskDetail{
		CreatedAt: task.CreatedAt.Format(time.RFC3339),
		UpdatedAt: task.UpdatedAt.Format(time.RFC3339),
		AreaID:    task.AreaID,
		GoalID:    task.GoalID,
	}

	detail.DeepLink, _ = lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)

	if task.Status != nil {
		s := string(*task.Status)
		detail.Status = &s
	}

	if task.Priority != nil {
		p := int(*task.Priority)
		detail.Priority = &p
	}

	if task.Estimate != nil {
		detail.Estimate = task.Estimate
	}

	if task.ScheduledOn != nil {
		s := task.ScheduledOn.Format("2006-01-02")
		detail.ScheduledOn = &s
	}

	if task.CompletedAt != nil {
		s := task.CompletedAt.Format(time.RFC3339)
		detail.CompletedAt = &s
	}

	if task.Eisenhower != nil {
		important := task.Eisenhower.IsImportant()
		urgent := task.Eisenhower.IsUrgent()
		detail.Important = &important
		detail.Urgent = &urgent
	}

	text := formatTaskShowText(detail)

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{Text: text}},
	}, QueryOutput{Entity: EntityTask, DeepLink: detail.DeepLink, Items: detail}, nil
}

func (h *Handler) listTasks(
	ctx context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.AreaID != nil {
		if err := lunatask.ValidateUUID(*input.AreaID); err != nil {
			return shared.ErrorResult("invalid area_id: expected UUID"),
				QueryOutput{Entity: EntityTask}, nil
		}
	}

	if input.Status != nil {
		if _, err := lunatask.ParseTaskStatus(*input.Status); err != nil {
			return shared.ErrorResult(fmt.Sprintf("invalid status '%s'; valid: %s", *input.Status, shared.FormatAllStatuses())),
				QueryOutput{Entity: EntityTask}, nil
		}
	}

	tasks, err := h.client.ListTasks(ctx, nil)
	if err != nil {
		return shared.ErrorResult(err.Error()), QueryOutput{Entity: EntityTask}, nil
	}

	opts := &lunatask.TaskFilterOptions{
		AreaID:           input.AreaID,
		IncludeCompleted: input.IncludeCompleted != nil && *input.IncludeCompleted,
		Today:            time.Now(),
	}

	if input.Status != nil {
		s := lunatask.TaskStatus(*input.Status)
		opts.Status = &s
	}

	filtered := lunatask.FilterTasks(tasks, opts)
	summaries := buildTaskSummaries(filtered)
	text := formatTaskListText(summaries)

	return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{Text: text}},
		}, QueryOutput{
			Entity: EntityTask,
			Items:  summaries,
			Count:  len(summaries),
		}, nil
}

func buildTaskSummaries(tasks []lunatask.Task) []TaskSummary {
	summaries := make([]TaskSummary, 0, len(tasks))

	for _, task := range tasks {
		summary := TaskSummary{
			CreatedAt: task.CreatedAt.Format(time.RFC3339),
			AreaID:    task.AreaID,
			GoalID:    task.GoalID,
		}

		summary.DeepLink, _ = lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)

		if task.Status != nil {
			s := string(*task.Status)
			summary.Status = &s
		}

		if task.Priority != nil {
			p := int(*task.Priority)
			summary.Priority = &p
		}

		if task.ScheduledOn != nil {
			s := task.ScheduledOn.Format("2006-01-02")
			summary.ScheduledOn = &s
		}

		summaries = append(summaries, summary)
	}

	return summaries
}

func formatTaskListText(summaries []TaskSummary) string {
	if len(summaries) == 0 {
		return "No tasks found."
	}

	var builder strings.Builder

	builder.WriteString(fmt.Sprintf("Found %d task(s):\n", len(summaries)))

	for _, summary := range summaries {
		status := "unknown"
		if summary.Status != nil {
			status = *summary.Status
		}

		builder.WriteString(fmt.Sprintf("- %s (%s)\n", summary.DeepLink, status))
	}

	builder.WriteString("\nUse query with id for full details.")

	return builder.String()
}

func formatTaskShowText(detail TaskDetail) string {
	var builder strings.Builder

	builder.WriteString(fmt.Sprintf("Task: %s\n", detail.DeepLink))
	writeOptionalField(&builder, "Status", detail.Status)
	writeOptionalIntField(&builder, "Priority", detail.Priority)
	writeOptionalField(&builder, "Scheduled", detail.ScheduledOn)
	writeOptionalMinutesField(&builder, "Estimate", detail.Estimate)
	writeEisenhowerField(&builder, detail.Important, detail.Urgent)
	builder.WriteString(fmt.Sprintf("Created: %s\n", detail.CreatedAt))
	builder.WriteString("Updated: " + detail.UpdatedAt)
	writeOptionalField(&builder, "\nCompleted", detail.CompletedAt)

	return builder.String()
}

func writeOptionalField(builder *strings.Builder, label string, value *string) {
	if value != nil {
		fmt.Fprintf(builder, "%s: %s\n", label, *value)
	}
}

func writeOptionalIntField(builder *strings.Builder, label string, value *int) {
	if value != nil {
		fmt.Fprintf(builder, "%s: %d\n", label, *value)
	}
}

func writeOptionalMinutesField(builder *strings.Builder, label string, value *int) {
	if value != nil {
		fmt.Fprintf(builder, "%s: %d min\n", label, *value)
	}
}

func writeEisenhowerField(builder *strings.Builder, important, urgent *bool) {
	var parts []string

	if important != nil && *important {
		parts = append(parts, "important")
	}

	if urgent != nil && *urgent {
		parts = append(parts, "urgent")
	}

	if len(parts) > 0 {
		fmt.Fprintf(builder, "Eisenhower: %s\n", strings.Join(parts, ", "))
	}
}

// NoteSummary represents a note in list output.
type NoteSummary struct {
	DeepLink   string  `json:"deep_link"`
	NotebookID *string `json:"notebook_id,omitempty"`
	DateOn     *string `json:"date_on,omitempty"`
	Pinned     bool    `json:"pinned"`
	CreatedAt  string  `json:"created_at"`
}

// NoteSource represents a source reference in note output.
type NoteSource struct {
	Source   string `json:"source"`
	SourceID string `json:"source_id"`
}

// NoteDetail represents detailed note information.
type NoteDetail struct {
	DeepLink   string       `json:"deep_link"`
	NotebookID *string      `json:"notebook_id,omitempty"`
	DateOn     *string      `json:"date_on,omitempty"`
	Pinned     bool         `json:"pinned"`
	Sources    []NoteSource `json:"sources,omitempty"`
	CreatedAt  string       `json:"created_at"`
	UpdatedAt  string       `json:"updated_at"`
}

func (h *Handler) queryNote(
	ctx context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.ID != nil {
		return h.showNote(ctx, *input.ID)
	}

	return h.listNotes(ctx, input)
}

func (h *Handler) showNote(
	ctx context.Context,
	id string,
) (*mcp.CallToolResult, QueryOutput, error) {
	_, noteID, err := lunatask.ParseReference(id)
	if err != nil {
		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"),
			QueryOutput{Entity: EntityNote}, nil
	}

	note, err := h.client.GetNote(ctx, noteID)
	if err != nil {
		return shared.ErrorResult(err.Error()), QueryOutput{Entity: EntityNote}, nil
	}

	detail := NoteDetail{
		NotebookID: note.NotebookID,
		Pinned:     note.Pinned,
		CreatedAt:  note.CreatedAt.Format(time.RFC3339),
		UpdatedAt:  note.UpdatedAt.Format(time.RFC3339),
	}

	detail.DeepLink, _ = lunatask.BuildDeepLink(lunatask.ResourceNote, note.ID)

	if note.DateOn != nil {
		s := note.DateOn.Format("2006-01-02")
		detail.DateOn = &s
	}

	if len(note.Sources) > 0 {
		detail.Sources = make([]NoteSource, 0, len(note.Sources))
		for _, src := range note.Sources {
			detail.Sources = append(detail.Sources, NoteSource{
				Source:   src.Source,
				SourceID: src.SourceID,
			})
		}
	}

	text := formatNoteShowText(detail, h.notebooks)

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{Text: text}},
	}, QueryOutput{Entity: EntityNote, DeepLink: detail.DeepLink, Items: detail}, nil
}

func (h *Handler) listNotes(
	ctx context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.NotebookID != nil {
		if err := lunatask.ValidateUUID(*input.NotebookID); err != nil {
			return shared.ErrorResult("invalid notebook_id: expected UUID"),
				QueryOutput{Entity: EntityNote}, nil
		}
	}

	opts := buildNoteListOptions(input)

	notes, err := h.client.ListNotes(ctx, opts)
	if err != nil {
		return shared.ErrorResult(err.Error()), QueryOutput{Entity: EntityNote}, nil
	}

	if input.NotebookID != nil {
		notes = filterNotesByNotebook(notes, *input.NotebookID)
	}

	summaries := buildNoteSummaries(notes)
	text := formatNoteListText(summaries, h.notebooks)

	return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{Text: text}},
		}, QueryOutput{
			Entity: EntityNote,
			Items:  summaries,
			Count:  len(summaries),
		}, nil
}

func buildNoteListOptions(input QueryInput) *lunatask.ListNotesOptions {
	if input.Source == nil && input.SourceID == nil {
		return nil
	}

	opts := &lunatask.ListNotesOptions{}

	if input.Source != nil {
		opts.Source = input.Source
	}

	if input.SourceID != nil {
		opts.SourceID = input.SourceID
	}

	return opts
}

func filterNotesByNotebook(notes []lunatask.Note, notebookID string) []lunatask.Note {
	filtered := make([]lunatask.Note, 0, len(notes))

	for _, note := range notes {
		if note.NotebookID != nil && *note.NotebookID == notebookID {
			filtered = append(filtered, note)
		}
	}

	return filtered
}

func buildNoteSummaries(notes []lunatask.Note) []NoteSummary {
	summaries := make([]NoteSummary, 0, len(notes))

	for _, note := range notes {
		summary := NoteSummary{
			NotebookID: note.NotebookID,
			Pinned:     note.Pinned,
			CreatedAt:  note.CreatedAt.Format("2006-01-02"),
		}

		summary.DeepLink, _ = lunatask.BuildDeepLink(lunatask.ResourceNote, note.ID)

		if note.DateOn != nil {
			dateStr := note.DateOn.Format("2006-01-02")
			summary.DateOn = &dateStr
		}

		summaries = append(summaries, summary)
	}

	return summaries
}

func formatNoteListText(summaries []NoteSummary, notebooks []shared.NotebookProvider) string {
	if len(summaries) == 0 {
		return "No notes found"
	}

	var text strings.Builder

	text.WriteString(fmt.Sprintf("Found %d note(s):\n", len(summaries)))

	for _, item := range summaries {
		text.WriteString("- ")
		text.WriteString(item.DeepLink)

		var details []string

		if item.NotebookID != nil {
			nbName := *item.NotebookID
			for _, nb := range notebooks {
				if nb.ID == *item.NotebookID {
					nbName = nb.Key

					break
				}
			}

			details = append(details, "notebook: "+nbName)
		}

		if item.Pinned {
			details = append(details, "pinned")
		}

		if len(details) > 0 {
			text.WriteString(" (")
			text.WriteString(strings.Join(details, ", "))
			text.WriteString(")")
		}

		text.WriteString("\n")
	}

	text.WriteString("\nUse query with id for full details.")

	return text.String()
}

func formatNoteShowText(detail NoteDetail, notebooks []shared.NotebookProvider) string {
	var builder strings.Builder

	builder.WriteString(fmt.Sprintf("Note: %s\n", detail.DeepLink))

	if detail.NotebookID != nil {
		nbName := *detail.NotebookID
		for _, nb := range notebooks {
			if nb.ID == *detail.NotebookID {
				nbName = nb.Key

				break
			}
		}

		builder.WriteString(fmt.Sprintf("Notebook: %s\n", nbName))
	}

	if detail.DateOn != nil {
		builder.WriteString(fmt.Sprintf("Date: %s\n", *detail.DateOn))
	}

	if detail.Pinned {
		builder.WriteString("Pinned: yes\n")
	}

	if len(detail.Sources) > 0 {
		builder.WriteString("Sources:\n")

		for _, src := range detail.Sources {
			builder.WriteString(fmt.Sprintf("  - %s: %s\n", src.Source, src.SourceID))
		}
	}

	builder.WriteString(fmt.Sprintf("Created: %s\n", detail.CreatedAt))
	builder.WriteString("Updated: " + detail.UpdatedAt)

	return builder.String()
}

func (h *Handler) queryPerson(
	ctx context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.ID != nil {
		return h.showPerson(ctx, *input.ID)
	}

	return h.listPeople(ctx, input)
}

// PersonSummary represents a person in list output.
type PersonSummary struct {
	DeepLink             string  `json:"deep_link"`
	RelationshipStrength *string `json:"relationship_strength,omitempty"`
	CreatedAt            string  `json:"created_at"`
}

// PersonSource represents a source reference in person output.
type PersonSource struct {
	Source   string `json:"source"`
	SourceID string `json:"source_id"`
}

// PersonDetail represents detailed person information.
type PersonDetail struct {
	DeepLink             string         `json:"deep_link"`
	RelationshipStrength *string        `json:"relationship_strength,omitempty"`
	Sources              []PersonSource `json:"sources,omitempty"`
	CreatedAt            string         `json:"created_at"`
	UpdatedAt            string         `json:"updated_at"`
}

func (h *Handler) showPerson(
	ctx context.Context,
	id string,
) (*mcp.CallToolResult, QueryOutput, error) {
	_, personID, err := lunatask.ParseReference(id)
	if err != nil {
		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"),
			QueryOutput{Entity: EntityPerson}, nil
	}

	person, err := h.client.GetPerson(ctx, personID)
	if err != nil {
		return shared.ErrorResult(err.Error()), QueryOutput{Entity: EntityPerson}, nil
	}

	detail := PersonDetail{
		CreatedAt: person.CreatedAt.Format(time.RFC3339),
		UpdatedAt: person.UpdatedAt.Format(time.RFC3339),
	}

	detail.DeepLink, _ = lunatask.BuildDeepLink(lunatask.ResourcePerson, person.ID)

	if person.RelationshipStrength != nil {
		s := string(*person.RelationshipStrength)
		detail.RelationshipStrength = &s
	}

	if len(person.Sources) > 0 {
		detail.Sources = make([]PersonSource, 0, len(person.Sources))
		for _, src := range person.Sources {
			detail.Sources = append(detail.Sources, PersonSource{
				Source:   src.Source,
				SourceID: src.SourceID,
			})
		}
	}

	text := formatPersonShowText(detail)

	return &mcp.CallToolResult{
		Content: []mcp.Content{&mcp.TextContent{Text: text}},
	}, QueryOutput{Entity: EntityPerson, DeepLink: detail.DeepLink, Items: detail}, nil
}

func (h *Handler) listPeople(
	ctx context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	opts := buildPeopleListOptions(input)

	people, err := h.client.ListPeople(ctx, opts)
	if err != nil {
		return shared.ErrorResult(err.Error()), QueryOutput{Entity: EntityPerson}, nil
	}

	summaries := buildPersonSummaries(people)
	text := formatPeopleListText(summaries)

	return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{Text: text}},
		}, QueryOutput{
			Entity: EntityPerson,
			Items:  summaries,
			Count:  len(summaries),
		}, nil
}

func buildPeopleListOptions(input QueryInput) *lunatask.ListPeopleOptions {
	if input.Source == nil && input.SourceID == nil {
		return nil
	}

	opts := &lunatask.ListPeopleOptions{}

	if input.Source != nil {
		opts.Source = input.Source
	}

	if input.SourceID != nil {
		opts.SourceID = input.SourceID
	}

	return opts
}

func buildPersonSummaries(people []lunatask.Person) []PersonSummary {
	summaries := make([]PersonSummary, 0, len(people))

	for _, person := range people {
		deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourcePerson, person.ID)

		summary := PersonSummary{
			DeepLink:  deepLink,
			CreatedAt: person.CreatedAt.Format("2006-01-02"),
		}

		if person.RelationshipStrength != nil {
			rel := string(*person.RelationshipStrength)
			summary.RelationshipStrength = &rel
		}

		summaries = append(summaries, summary)
	}

	return summaries
}

func formatPeopleListText(summaries []PersonSummary) string {
	if len(summaries) == 0 {
		return "No people found"
	}

	var text strings.Builder

	text.WriteString(fmt.Sprintf("Found %d person(s):\n", len(summaries)))

	for _, item := range summaries {
		text.WriteString("- ")
		text.WriteString(item.DeepLink)

		if item.RelationshipStrength != nil {
			text.WriteString(" (")
			text.WriteString(*item.RelationshipStrength)
			text.WriteString(")")
		}

		text.WriteString("\n")
	}

	text.WriteString("\nUse query with id for full details.")

	return text.String()
}

func formatPersonShowText(detail PersonDetail) string {
	var builder strings.Builder

	builder.WriteString(fmt.Sprintf("Person: %s\n", detail.DeepLink))

	if detail.RelationshipStrength != nil {
		builder.WriteString(fmt.Sprintf("Relationship: %s\n", *detail.RelationshipStrength))
	}

	if len(detail.Sources) > 0 {
		builder.WriteString("Sources:\n")

		for _, src := range detail.Sources {
			builder.WriteString(fmt.Sprintf("  - %s: %s\n", src.Source, src.SourceID))
		}
	}

	builder.WriteString(fmt.Sprintf("Created: %s\n", detail.CreatedAt))
	builder.WriteString("Updated: " + detail.UpdatedAt)

	return builder.String()
}

func (h *Handler) queryArea(
	_ context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.ID != nil {
		return shared.ErrorResult("area entities are config-based and do not support ID lookup"),
			QueryOutput{Entity: input.Entity}, nil
	}

	summaries := make([]AreaSummary, 0, len(h.areas))

	for _, area := range h.areas {
		summaries = append(summaries, AreaSummary{
			ID:       area.ID,
			Name:     area.Name,
			Key:      area.Key,
			Workflow: string(area.Workflow),
		})
	}

	text := formatAreaListText(summaries)

	return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{Text: text}},
		}, QueryOutput{
			Entity: EntityArea,
			Items:  summaries,
			Count:  len(summaries),
		}, nil
}

// AreaSummary represents an area in list output.
type AreaSummary struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	Key      string `json:"key"`
	Workflow string `json:"workflow"`
}

func formatAreaListText(areas []AreaSummary) string {
	if len(areas) == 0 {
		return "No areas configured"
	}

	var text strings.Builder

	text.WriteString(fmt.Sprintf("Found %d area(s):\n", len(areas)))

	for _, a := range areas {
		text.WriteString(fmt.Sprintf("- %s: %s (%s, workflow: %s)\n", a.Key, a.Name, a.ID, a.Workflow))
	}

	return text.String()
}

func (h *Handler) queryGoal(
	_ context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.ID != nil {
		return shared.ErrorResult("goal entities are config-based and do not support ID lookup"),
			QueryOutput{Entity: input.Entity}, nil
	}

	if input.AreaID == nil {
		return shared.ErrorResult("area_id is required for goal query"),
			QueryOutput{Entity: input.Entity}, nil
	}

	area := h.resolveAreaRef(*input.AreaID)
	if area == nil {
		return shared.ErrorResult("unknown area: " + *input.AreaID),
			QueryOutput{Entity: input.Entity}, nil
	}

	summaries := make([]GoalSummary, 0, len(area.Goals))

	for _, goal := range area.Goals {
		summaries = append(summaries, GoalSummary{
			ID:   goal.ID,
			Name: goal.Name,
			Key:  goal.Key,
		})
	}

	text := formatGoalListText(summaries, area.Name)

	return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{Text: text}},
		}, QueryOutput{
			Entity: EntityGoal,
			Items:  summaries,
			Count:  len(summaries),
		}, nil
}

// GoalSummary represents a goal in list output.
type GoalSummary struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Key  string `json:"key"`
}

// resolveAreaRef resolves an area reference to an AreaProvider.
// Accepts config key, UUID, or deep link.
func (h *Handler) resolveAreaRef(input string) *shared.AreaProvider {
	// Try UUID or deep link first
	if _, id, err := lunatask.ParseReference(input); err == nil {
		for i := range h.areas {
			if h.areas[i].ID == id {
				return &h.areas[i]
			}
		}
	}

	// Try config key lookup
	for i := range h.areas {
		if h.areas[i].Key == input {
			return &h.areas[i]
		}
	}

	return nil
}

func formatGoalListText(goals []GoalSummary, areaName string) string {
	if len(goals) == 0 {
		return fmt.Sprintf("No goals configured for area %q", areaName)
	}

	var text strings.Builder

	text.WriteString(fmt.Sprintf("Found %d goal(s) in %q:\n", len(goals), areaName))

	for _, g := range goals {
		text.WriteString(fmt.Sprintf("- %s: %s (%s)\n", g.Key, g.Name, g.ID))
	}

	return text.String()
}

func (h *Handler) queryNotebook(
	_ context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.ID != nil {
		return shared.ErrorResult("notebook entities are config-based and do not support ID lookup"),
			QueryOutput{Entity: input.Entity}, nil
	}

	summaries := make([]NotebookSummary, 0, len(h.notebooks))

	for _, nb := range h.notebooks {
		summaries = append(summaries, NotebookSummary{
			ID:   nb.ID,
			Name: nb.Name,
			Key:  nb.Key,
		})
	}

	text := formatNotebookListText(summaries)

	return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{Text: text}},
		}, QueryOutput{
			Entity: EntityNotebook,
			Items:  summaries,
			Count:  len(summaries),
		}, nil
}

// NotebookSummary represents a notebook in list output.
type NotebookSummary struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Key  string `json:"key"`
}

func formatNotebookListText(notebooks []NotebookSummary) string {
	if len(notebooks) == 0 {
		return "No notebooks configured"
	}

	var text strings.Builder

	text.WriteString(fmt.Sprintf("Found %d notebook(s):\n", len(notebooks)))

	for _, nb := range notebooks {
		text.WriteString(fmt.Sprintf("- %s (key: %s, id: %s)\n", nb.Name, nb.Key, nb.ID))
	}

	return text.String()
}

func (h *Handler) queryHabit(
	_ context.Context,
	input QueryInput,
) (*mcp.CallToolResult, QueryOutput, error) {
	if input.ID != nil {
		return shared.ErrorResult("habit entities are config-based and do not support ID lookup"),
			QueryOutput{Entity: input.Entity}, nil
	}

	summaries := make([]HabitSummary, 0, len(h.habits))

	for _, habit := range h.habits {
		summaries = append(summaries, HabitSummary{
			ID:   habit.ID,
			Name: habit.Name,
			Key:  habit.Key,
		})
	}

	text := formatHabitListText(summaries)

	return &mcp.CallToolResult{
			Content: []mcp.Content{&mcp.TextContent{Text: text}},
		}, QueryOutput{
			Entity: EntityHabit,
			Items:  summaries,
			Count:  len(summaries),
		}, nil
}

// HabitSummary represents a habit in list output.
type HabitSummary struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Key  string `json:"key"`
}

func formatHabitListText(habits []HabitSummary) string {
	if len(habits) == 0 {
		return "No habits configured"
	}

	var text strings.Builder

	text.WriteString(fmt.Sprintf("Found %d habit(s):\n", len(habits)))

	for _, h := range habits {
		text.WriteString(fmt.Sprintf("- %s: %s (%s)\n", h.Key, h.Name, h.ID))
	}

	return text.String()
}
