// 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())
}
