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

package lunatask

import (
	"context"
	"time"
)

// Task is a task in Lunatask. Name and Note are encrypted client-side
// and will be null when read back from the API.
type Task struct {
	ID             string      `json:"id"`
	AreaID         *string     `json:"area_id"`
	GoalID         *string     `json:"goal_id"`
	Name           *string     `json:"name"`
	Note           *string     `json:"note"`
	Status         *TaskStatus `json:"status"`
	PreviousStatus *TaskStatus `json:"previous_status"`
	Estimate       *int        `json:"estimate"`
	Priority       *Priority   `json:"priority"`
	Progress       *int        `json:"progress"`
	Motivation     *Motivation `json:"motivation"`
	Eisenhower     *Eisenhower `json:"eisenhower"`
	Sources        []Source    `json:"sources"`
	ScheduledOn    *Date       `json:"scheduled_on"`
	CompletedAt    *time.Time  `json:"completed_at"`
	CreatedAt      time.Time   `json:"created_at"`
	UpdatedAt      time.Time   `json:"updated_at"`
}

// createTaskRequest defines a new task for JSON serialization.
type createTaskRequest struct {
	Name        string      `json:"name"`
	AreaID      *string     `json:"area_id,omitempty"`
	GoalID      *string     `json:"goal_id,omitempty"`
	Note        *string     `json:"note,omitempty"`
	Status      *TaskStatus `json:"status,omitempty"`
	Motivation  *Motivation `json:"motivation,omitempty"`
	Estimate    *int        `json:"estimate,omitempty"`
	Priority    *Priority   `json:"priority,omitempty"`
	Eisenhower  *Eisenhower `json:"eisenhower,omitempty"`
	ScheduledOn *Date       `json:"scheduled_on,omitempty"`
	CompletedAt *time.Time  `json:"completed_at,omitempty"`
	Source      *string     `json:"source,omitempty"`
	SourceID    *string     `json:"source_id,omitempty"`
}

// updateTaskRequest specifies which fields to change on a task.
type updateTaskRequest struct {
	Name        *string     `json:"name,omitempty"`
	AreaID      *string     `json:"area_id,omitempty"`
	GoalID      *string     `json:"goal_id,omitempty"`
	Note        *string     `json:"note,omitempty"`
	Status      *TaskStatus `json:"status,omitempty"`
	Motivation  *Motivation `json:"motivation,omitempty"`
	Estimate    *int        `json:"estimate,omitempty"`
	Priority    *Priority   `json:"priority,omitempty"`
	Eisenhower  *Eisenhower `json:"eisenhower,omitempty"`
	ScheduledOn *Date       `json:"scheduled_on,omitempty"`
	CompletedAt *time.Time  `json:"completed_at,omitempty"`
}

// taskResponse wraps a single task from the API.
type taskResponse struct {
	Task Task `json:"task"`
}

// tasksResponse wraps a list of tasks from the API.
type tasksResponse struct {
	Tasks []Task `json:"tasks"`
}

// ListTasksOptions filters tasks by source integration.
type ListTasksOptions struct {
	Source   *string
	SourceID *string
}

// GetSource implements [SourceFilter].
func (o *ListTasksOptions) GetSource() *string { return o.Source }

// GetSourceID implements [SourceFilter].
func (o *ListTasksOptions) GetSourceID() *string { return o.SourceID }

// ListTasks returns all tasks, optionally filtered. Pass nil for all.
func (c *Client) ListTasks(ctx context.Context, opts *ListTasksOptions) ([]Task, error) {
	var filter SourceFilter
	if opts != nil {
		filter = opts
	}

	return list(ctx, c, "/tasks", filter, func(r tasksResponse) []Task { return r.Tasks })
}

// GetTask fetches a task by ID. Name and Note will be null (E2EE).
func (c *Client) GetTask(ctx context.Context, taskID string) (*Task, error) {
	return get(ctx, c, "/tasks", taskID, "task", func(r taskResponse) Task { return r.Task })
}

// DeleteTask removes a task and returns its final state.
func (c *Client) DeleteTask(ctx context.Context, taskID string) (*Task, error) {
	return del(ctx, c, "/tasks", taskID, "task", func(r taskResponse) Task { return r.Task })
}

// TaskBuilder constructs and creates a task via method chaining.
//
//	task, err := client.NewTask("Review PR").
//		InArea(areaID).
//		WithStatus(lunatask.StatusNext).
//		WithEstimate(30).
//		Create(ctx)
type TaskBuilder struct {
	client    *Client
	req       createTaskRequest
	important *bool
	urgent    *bool
}

// NewTask starts building a task with the given name.
func (c *Client) NewTask(name string) *TaskBuilder {
	return &TaskBuilder{client: c, req: createTaskRequest{Name: name}}
}

// InArea assigns the task to an area. IDs are in the area's settings in the app.
func (b *TaskBuilder) InArea(areaID string) *TaskBuilder {
	b.req.AreaID = &areaID

	return b
}

// InGoal assigns the task to a goal. IDs are in the goal's settings in the app.
func (b *TaskBuilder) InGoal(goalID string) *TaskBuilder {
	b.req.GoalID = &goalID

	return b
}

// WithNote attaches a Markdown note to the task.
func (b *TaskBuilder) WithNote(note string) *TaskBuilder {
	b.req.Note = &note

	return b
}

// WithStatus sets the workflow status.
// Use one of the Status* constants (e.g., [StatusNext]).
func (b *TaskBuilder) WithStatus(status TaskStatus) *TaskBuilder {
	b.req.Status = &status

	return b
}

// WithMotivation sets why this task matters.
// Use one of the Motivation* constants (e.g., [MotivationMust]).
func (b *TaskBuilder) WithMotivation(motivation Motivation) *TaskBuilder {
	b.req.Motivation = &motivation

	return b
}

// WithEstimate sets the expected duration in minutes (0–720).
func (b *TaskBuilder) WithEstimate(minutes int) *TaskBuilder {
	b.req.Estimate = &minutes

	return b
}

// Priority sets the priority level. Use Priority* constants (e.g., [PriorityHigh]).
func (b *TaskBuilder) Priority(p Priority) *TaskBuilder {
	b.req.Priority = &p

	return b
}

// WithEisenhower sets the Eisenhower matrix quadrant directly.
// Prefer [TaskBuilder.Important] and [TaskBuilder.Urgent] for a more readable API.
func (b *TaskBuilder) WithEisenhower(eisenhower Eisenhower) *TaskBuilder {
	b.req.Eisenhower = &eisenhower

	return b
}

// Important marks the task as important. Produces [EisenhowerDoLater] alone,
// or [EisenhowerDoNow] when combined with [TaskBuilder.Urgent].
func (b *TaskBuilder) Important() *TaskBuilder {
	t := true
	b.important = &t

	return b
}

// NotImportant marks the task as not important. Use with [TaskBuilder.NotUrgent]
// to explicitly set [EisenhowerEliminate], or with [TaskBuilder.Urgent] for [EisenhowerDelegate].
// Calling neither Important nor NotImportant leaves Eisenhower unset.
func (b *TaskBuilder) NotImportant() *TaskBuilder {
	f := false
	b.important = &f

	return b
}

// Urgent marks the task as urgent. Produces [EisenhowerDelegate] alone,
// or [EisenhowerDoNow] when combined with [TaskBuilder.Important].
func (b *TaskBuilder) Urgent() *TaskBuilder {
	t := true
	b.urgent = &t

	return b
}

// NotUrgent marks the task as not urgent. Use with [TaskBuilder.NotImportant]
// to explicitly set [EisenhowerEliminate], or with [TaskBuilder.Important] for [EisenhowerDoLater].
// Calling neither Urgent nor NotUrgent leaves Eisenhower unset.
func (b *TaskBuilder) NotUrgent() *TaskBuilder {
	f := false
	b.urgent = &f

	return b
}

// ScheduledOn sets when the task should appear on your schedule.
func (b *TaskBuilder) ScheduledOn(date Date) *TaskBuilder {
	b.req.ScheduledOn = &date

	return b
}

// CompletedAt marks the task completed at a specific time.
func (b *TaskBuilder) CompletedAt(t time.Time) *TaskBuilder {
	b.req.CompletedAt = &t

	return b
}

// FromSource tags the task with a free-form origin identifier, useful for
// tracking tasks created by scripts or external integrations.
func (b *TaskBuilder) FromSource(source, sourceID string) *TaskBuilder {
	b.req.Source = &source
	b.req.SourceID = &sourceID

	return b
}

// Create sends the task to Lunatask. Returns (nil, nil) if a not-completed
// task already exists in the same area with matching source/source_id.
func (b *TaskBuilder) Create(ctx context.Context) (*Task, error) {
	if b.important != nil || b.urgent != nil {
		important := b.important != nil && *b.important
		urgent := b.urgent != nil && *b.urgent
		e := NewEisenhower(important, urgent)
		b.req.Eisenhower = &e
	}

	return create(ctx, b.client, "/tasks", b.req, func(r taskResponse) Task { return r.Task })
}

// TaskUpdateBuilder constructs and updates a task via method chaining.
// Only fields you set will be modified; others remain unchanged.
//
//	task, err := client.NewTaskUpdate(taskID).
//		WithStatus(lunatask.StatusCompleted).
//		CompletedAt(time.Now()).
//		Update(ctx)
type TaskUpdateBuilder struct {
	client    *Client
	taskID    string
	req       updateTaskRequest
	important *bool
	urgent    *bool
}

// NewTaskUpdate starts building a task update for the given task ID.
func (c *Client) NewTaskUpdate(taskID string) *TaskUpdateBuilder {
	return &TaskUpdateBuilder{client: c, taskID: taskID}
}

// Name changes the task's name.
func (b *TaskUpdateBuilder) Name(name string) *TaskUpdateBuilder {
	b.req.Name = &name

	return b
}

// InArea moves the task to an area. IDs are in the area's settings in the app.
func (b *TaskUpdateBuilder) InArea(areaID string) *TaskUpdateBuilder {
	b.req.AreaID = &areaID

	return b
}

// InGoal moves the task to a goal. IDs are in the goal's settings in the app.
func (b *TaskUpdateBuilder) InGoal(goalID string) *TaskUpdateBuilder {
	b.req.GoalID = &goalID

	return b
}

// WithNote replaces the task's Markdown note.
func (b *TaskUpdateBuilder) WithNote(note string) *TaskUpdateBuilder {
	b.req.Note = &note

	return b
}

// WithStatus sets the workflow status.
// Use one of the Status* constants (e.g., [StatusNext]).
func (b *TaskUpdateBuilder) WithStatus(status TaskStatus) *TaskUpdateBuilder {
	b.req.Status = &status

	return b
}

// WithMotivation sets why this task matters.
// Use one of the Motivation* constants (e.g., [MotivationMust]).
func (b *TaskUpdateBuilder) WithMotivation(motivation Motivation) *TaskUpdateBuilder {
	b.req.Motivation = &motivation

	return b
}

// WithEstimate sets the expected duration in minutes (0–720).
func (b *TaskUpdateBuilder) WithEstimate(minutes int) *TaskUpdateBuilder {
	b.req.Estimate = &minutes

	return b
}

// Priority sets the priority level. Use Priority* constants (e.g., [PriorityHigh]).
func (b *TaskUpdateBuilder) Priority(p Priority) *TaskUpdateBuilder {
	b.req.Priority = &p

	return b
}

// WithEisenhower sets the Eisenhower matrix quadrant directly.
// Prefer [TaskUpdateBuilder.Important] and [TaskUpdateBuilder.Urgent] for a more readable API.
func (b *TaskUpdateBuilder) WithEisenhower(eisenhower Eisenhower) *TaskUpdateBuilder {
	b.req.Eisenhower = &eisenhower

	return b
}

// Important marks the task as important. Produces [EisenhowerDoLater] alone,
// or [EisenhowerDoNow] when combined with [TaskUpdateBuilder.Urgent].
func (b *TaskUpdateBuilder) Important() *TaskUpdateBuilder {
	t := true
	b.important = &t

	return b
}

// NotImportant marks the task as not important. Use with [TaskUpdateBuilder.NotUrgent]
// to explicitly set [EisenhowerEliminate], or with [TaskUpdateBuilder.Urgent] for [EisenhowerDelegate].
// Calling neither Important nor NotImportant leaves Eisenhower unset.
func (b *TaskUpdateBuilder) NotImportant() *TaskUpdateBuilder {
	f := false
	b.important = &f

	return b
}

// Urgent marks the task as urgent. Produces [EisenhowerDelegate] alone,
// or [EisenhowerDoNow] when combined with [TaskUpdateBuilder.Important].
func (b *TaskUpdateBuilder) Urgent() *TaskUpdateBuilder {
	t := true
	b.urgent = &t

	return b
}

// NotUrgent marks the task as not urgent. Use with [TaskUpdateBuilder.NotImportant]
// to explicitly set [EisenhowerEliminate], or with [TaskUpdateBuilder.Important] for [EisenhowerDoLater].
// Calling neither Urgent nor NotUrgent leaves Eisenhower unset.
func (b *TaskUpdateBuilder) NotUrgent() *TaskUpdateBuilder {
	f := false
	b.urgent = &f

	return b
}

// ScheduledOn sets when the task should appear on your schedule.
func (b *TaskUpdateBuilder) ScheduledOn(date Date) *TaskUpdateBuilder {
	b.req.ScheduledOn = &date

	return b
}

// CompletedAt marks the task completed at a specific time.
func (b *TaskUpdateBuilder) CompletedAt(t time.Time) *TaskUpdateBuilder {
	b.req.CompletedAt = &t

	return b
}

// Update sends the changes to Lunatask.
func (b *TaskUpdateBuilder) Update(ctx context.Context) (*Task, error) {
	if b.important != nil || b.urgent != nil {
		important := b.important != nil && *b.important
		urgent := b.urgent != nil && *b.urgent
		e := NewEisenhower(important, urgent)
		b.req.Eisenhower = &e
	}

	return update(ctx, b.client, "/tasks", b.taskID, "task", b.req, func(r taskResponse) Task { return r.Task })
}
