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

package lunatask

import (
	"context"
	"time"
)

// Note is a note in Lunatask. Name and Content are encrypted client-side
// and will be null when read back from the API.
type Note struct {
	ID         string    `json:"id"`
	NotebookID *string   `json:"notebook_id"`
	DateOn     *Date     `json:"date_on"`
	Pinned     bool      `json:"pinned"`
	Sources    []Source  `json:"sources"`
	CreatedAt  time.Time `json:"created_at"`
	UpdatedAt  time.Time `json:"updated_at"`
}

// createNoteRequest defines a new note for JSON serialization.
type createNoteRequest struct {
	Name       *string `json:"name,omitempty"`
	Content    *string `json:"content,omitempty"`
	NotebookID *string `json:"notebook_id,omitempty"`
	Source     *string `json:"source,omitempty"`
	SourceID   *string `json:"source_id,omitempty"`
}

// updateNoteRequest specifies which fields to change on a note.
type updateNoteRequest struct {
	Name       *string `json:"name,omitempty"`
	Content    *string `json:"content,omitempty"`
	NotebookID *string `json:"notebook_id,omitempty"`
	DateOn     *Date   `json:"date_on,omitempty"`
}

// noteResponse wraps a single note from the API.
type noteResponse struct {
	Note Note `json:"note"`
}

// notesResponse wraps a list of notes from the API.
type notesResponse struct {
	Notes []Note `json:"notes"`
}

// ListNotesOptions filters notes by source integration.
type ListNotesOptions struct {
	Source   *string
	SourceID *string
}

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

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

// ListNotes returns all notes, optionally filtered. Pass nil for all notes.
func (c *Client) ListNotes(ctx context.Context, opts *ListNotesOptions) ([]Note, error) {
	var filter SourceFilter
	if opts != nil {
		filter = opts
	}

	return list(ctx, c, "/notes", filter, func(r notesResponse) []Note { return r.Notes })
}

// GetNote fetches a note by ID. Name and Content will be null (E2EE).
func (c *Client) GetNote(ctx context.Context, noteID string) (*Note, error) {
	return get(ctx, c, "/notes", noteID, "note", func(r noteResponse) Note { return r.Note })
}

// DeleteNote removes a note and returns its final state.
func (c *Client) DeleteNote(ctx context.Context, noteID string) (*Note, error) {
	return del(ctx, c, "/notes", noteID, "note", func(r noteResponse) Note { return r.Note })
}

// NoteBuilder constructs and creates a note via method chaining.
// Note fields are encrypted client-side by Lunatask; the API accepts them
// on create but returns null on read.
//
//	note, err := client.NewNote().
//		WithName("Meeting notes").
//		WithContent("# Summary\n\n...").
//		InNotebook(notebookID).
//		Create(ctx)
type NoteBuilder struct {
	client *Client
	req    createNoteRequest
}

// NewNote starts building a note.
func (c *Client) NewNote() *NoteBuilder {
	return &NoteBuilder{client: c}
}

// WithName sets the note's title.
func (b *NoteBuilder) WithName(name string) *NoteBuilder {
	b.req.Name = &name

	return b
}

// WithContent sets the Markdown body.
func (b *NoteBuilder) WithContent(content string) *NoteBuilder {
	b.req.Content = &content

	return b
}

// InNotebook places the note in a notebook. IDs are in the notebook's settings in the app.
func (b *NoteBuilder) InNotebook(notebookID string) *NoteBuilder {
	b.req.NotebookID = &notebookID

	return b
}

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

	return b
}

// Create sends the note to Lunatask. Returns (nil, nil) if a duplicate exists
// in the same notebook with matching source/source_id.
func (b *NoteBuilder) Create(ctx context.Context) (*Note, error) {
	return create(ctx, b.client, "/notes", b.req, func(r noteResponse) Note { return r.Note })
}

// NoteUpdateBuilder constructs and updates a note via method chaining.
// Only fields you set will be modified; others remain unchanged.
//
//	note, err := client.NewNoteUpdate(noteID).
//		WithContent("# Updated content").
//		Update(ctx)
type NoteUpdateBuilder struct {
	client *Client
	noteID string
	req    updateNoteRequest
}

// NewNoteUpdate starts building a note update for the given note ID.
func (c *Client) NewNoteUpdate(noteID string) *NoteUpdateBuilder {
	return &NoteUpdateBuilder{client: c, noteID: noteID}
}

// WithName sets the note's title.
func (b *NoteUpdateBuilder) WithName(name string) *NoteUpdateBuilder {
	b.req.Name = &name

	return b
}

// WithContent sets the Markdown body.
func (b *NoteUpdateBuilder) WithContent(content string) *NoteUpdateBuilder {
	b.req.Content = &content

	return b
}

// InNotebook moves the note to a notebook.
func (b *NoteUpdateBuilder) InNotebook(notebookID string) *NoteUpdateBuilder {
	b.req.NotebookID = &notebookID

	return b
}

// OnDate sets the date for the note.
func (b *NoteUpdateBuilder) OnDate(date Date) *NoteUpdateBuilder {
	b.req.DateOn = &date

	return b
}

// Update sends the changes to Lunatask.
func (b *NoteUpdateBuilder) Update(ctx context.Context) (*Note, error) {
	return update(ctx, b.client, "/notes", b.noteID, "note", b.req, func(r noteResponse) Note { return r.Note })
}
