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

package lunatask

import (
	"encoding/json"
	"fmt"
	"time"
)

// Source tracks where an entity originated, useful for syncing with external
// systems. The values are free-form strings you define.
type Source struct {
	Source   string `json:"source"`
	SourceID string `json:"source_id"`
}

// Date is a date without time, marshaled as "YYYY-MM-DD" in JSON.
type Date struct {
	time.Time
}

// NewDate creates a Date from a time.Time, discarding time-of-day.
func NewDate(t time.Time) Date {
	return Date{time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC)}
}

const dateFormat = "2006-01-02"

// MarshalJSON implements [json.Marshaler].
func (d Date) MarshalJSON() ([]byte, error) {
	if d.IsZero() {
		return []byte("null"), nil
	}

	data, err := json.Marshal(d.Format(dateFormat))
	if err != nil {
		return nil, fmt.Errorf("marshaling date: %w", err)
	}

	return data, nil
}

// UnmarshalJSON implements [json.Unmarshaler].
func (d *Date) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		return nil
	}

	var str string
	if err := json.Unmarshal(data, &str); err != nil {
		return fmt.Errorf("unmarshaling date string: %w", err)
	}

	t, err := time.Parse(dateFormat, str)
	if err != nil {
		return fmt.Errorf("parsing date %q: %w", str, err)
	}

	d.Time = t

	return nil
}

// String returns the date as "YYYY-MM-DD", or empty string if zero.
func (d *Date) String() string {
	if d.IsZero() {
		return ""
	}

	return d.Format(dateFormat)
}

// ParseDate parses "YYYY-MM-DD" into a Date.
func ParseDate(s string) (Date, error) {
	t, err := time.Parse(dateFormat, s)
	if err != nil {
		return Date{}, fmt.Errorf("parsing date %q: %w", s, err)
	}

	return Date{t}, nil
}

// Today returns the current date in local time.
func Today() Date {
	return NewDate(time.Now())
}

// RelationshipStrength categorizes the closeness of a relationship.
type RelationshipStrength string

// Valid relationship strength values.
const (
	RelationshipFamily         RelationshipStrength = "family"
	RelationshipIntimateFriend RelationshipStrength = "intimate-friends"
	RelationshipCloseFriend    RelationshipStrength = "close-friends"
	// RelationshipCasualFriend is the default if not specified.
	RelationshipCasualFriend   RelationshipStrength = "casual-friends"
	RelationshipAcquaintance   RelationshipStrength = "acquaintances"
	RelationshipBusiness       RelationshipStrength = "business-contacts"
	RelationshipAlmostStranger RelationshipStrength = "almost-strangers"
)

// TaskStatus represents the workflow state of a task.
type TaskStatus string

// Valid task status values.
const (
	// StatusLater is the default status for new tasks.
	StatusLater     TaskStatus = "later"
	StatusNext      TaskStatus = "next"
	StatusStarted   TaskStatus = "started"
	StatusWaiting   TaskStatus = "waiting"
	StatusCompleted TaskStatus = "completed"
)

// Motivation represents why a task matters.
type Motivation string

// Valid motivation values.
const (
	// MotivationUnknown clears/unsets the motivation (default).
	MotivationUnknown Motivation = "unknown"
	MotivationMust    Motivation = "must"
	MotivationShould  Motivation = "should"
	MotivationWant    Motivation = "want"
)

// Eisenhower represents a quadrant in the Eisenhower priority matrix, which
// categorizes tasks by whether they are urgent (time-sensitive) and/or
// important (valuable toward your goals).
//
// The four quadrants guide what action to take:
//
//	                  | Urgent        | Not Urgent
//	------------------|---------------|-------------
//	Important         | Do Now (1)    | Do Later (3)
//	Not Important     | Delegate (2)  | Eliminate (4)
//
// Use [NewEisenhower] to compute the quadrant from boolean flags, or the
// builder methods [TaskBuilder.Important] and [TaskBuilder.Urgent] for a
// fluent API.
type Eisenhower int

// Eisenhower matrix quadrants. See [Eisenhower] for the full matrix.
const (
	EisenhowerUncategorized Eisenhower = 0 // not yet categorized
	EisenhowerDoNow         Eisenhower = 1 // urgent + important
	EisenhowerDelegate      Eisenhower = 2 // urgent, not important
	EisenhowerDoLater       Eisenhower = 3 // important, not urgent
	EisenhowerEliminate     Eisenhower = 4 // neither urgent nor important
)

// NewEisenhower returns the quadrant for the given flags:
//
//	NewEisenhower(true, true)   // DoNow
//	NewEisenhower(true, false)  // DoLater
//	NewEisenhower(false, true)  // Delegate
//	NewEisenhower(false, false) // Eliminate
func NewEisenhower(important, urgent bool) Eisenhower {
	switch {
	case important && urgent:
		return EisenhowerDoNow
	case urgent:
		return EisenhowerDelegate
	case important:
		return EisenhowerDoLater
	default:
		return EisenhowerEliminate
	}
}

// IsUrgent reports whether e is in an urgent quadrant ([EisenhowerDoNow] or [EisenhowerDelegate]).
func (e Eisenhower) IsUrgent() bool {
	return e == EisenhowerDoNow || e == EisenhowerDelegate
}

// IsImportant reports whether e is in an important quadrant ([EisenhowerDoNow] or [EisenhowerDoLater]).
func (e Eisenhower) IsImportant() bool {
	return e == EisenhowerDoNow || e == EisenhowerDoLater
}
