package lsp

import (
	"fmt"
	"path/filepath"
	"time"

	"github.com/zk-org/zk/internal/cli"
	"github.com/zk-org/zk/internal/core"
	"github.com/zk-org/zk/internal/util"
	strutil "github.com/zk-org/zk/internal/util/strings"
)

const cmdList = "zk.list"

type cmdListOpts struct {
	Select []string `json:"select"`
	cli.Filtering
}

func executeCommandList(logger util.Logger, notebook *core.Notebook, args []any) (any, error) {
	var opts cmdListOpts
	if len(args) > 1 {
		arg, ok := args[1].(map[string]any)
		if !ok {
			return nil, fmt.Errorf("%s expects a dictionary of options as second argument, got: %v", cmdList, args[1])
		}
		err := unmarshalJSON(arg, &opts)
		if err != nil {
			return nil, fmt.Errorf("failed to parse %s args, got: %v: %w", cmdList, arg, err)
		}
	}

	if len(opts.Select) == 0 {
		return nil, fmt.Errorf("%s expects a `select` option with the list of fields to return", cmdList)
	}
	selection := newListSelection(opts.Select)

	findOpts, err := opts.NewNoteFindOpts(notebook)
	if err != nil {
		return nil, err
	}

	notes, err := notebook.FindNotes(findOpts)
	if err != nil {
		return nil, err
	}

	listNotes := []listNote{}
	for _, note := range notes {
		listNotes = append(listNotes, newListNote(note, selection, notebook.Path))
	}

	return listNotes, nil
}

type listSelection struct {
	Filename     bool
	FilenameStem bool
	Path         bool
	AbsPath      bool
	Title        bool
	Lead         bool
	Body         bool
	Snippets     bool
	RawContent   bool
	WordCount    bool
	Tags         bool
	Metadata     bool
	Created      bool
	Modified     bool
	Checksum     bool
}

func newListSelection(fields []string) listSelection {
	return listSelection{
		Filename:     strutil.Contains(fields, "filename"),
		FilenameStem: strutil.Contains(fields, "filenameStem"),
		Path:         strutil.Contains(fields, "path"),
		AbsPath:      strutil.Contains(fields, "absPath"),
		Title:        strutil.Contains(fields, "title"),
		Lead:         strutil.Contains(fields, "lead"),
		Body:         strutil.Contains(fields, "body"),
		Snippets:     strutil.Contains(fields, "snippets"),
		RawContent:   strutil.Contains(fields, "rawContent"),
		WordCount:    strutil.Contains(fields, "wordCount"),
		Tags:         strutil.Contains(fields, "tags"),
		Metadata:     strutil.Contains(fields, "metadata"),
		Created:      strutil.Contains(fields, "created"),
		Modified:     strutil.Contains(fields, "modified"),
		Checksum:     strutil.Contains(fields, "checksum"),
	}
}

func newListNote(note core.ContextualNote, selection listSelection, basePath string) listNote {
	var res listNote
	if selection.Filename {
		res.Filename = note.Filename()
	}
	if selection.FilenameStem {
		res.FilenameStem = note.FilenameStem()
	}
	if selection.Path {
		res.Path = note.Path
	}
	if selection.AbsPath {
		res.AbsPath = filepath.Join(basePath, note.Path)
	}
	if selection.Title {
		res.Title = note.Title
	}
	if selection.Lead {
		res.Lead = note.Lead
	}
	if selection.Body {
		res.Body = note.Body
	}
	if selection.Snippets {
		res.Snippets = note.Snippets
	}
	if selection.RawContent {
		res.RawContent = note.RawContent
	}
	if selection.WordCount {
		res.WordCount = note.WordCount
	}
	if selection.Tags {
		res.Tags = note.Tags
	}
	if selection.Metadata {
		res.Metadata = note.Metadata
	}
	if selection.Created {
		res.Created = &note.Created
	}
	if selection.Modified {
		res.Modified = &note.Modified
	}
	if selection.Checksum {
		res.Checksum = note.Checksum
	}
	return res
}

type listNote struct {
	Filename     string         `json:"filename,omitempty"`
	FilenameStem string         `json:"filenameStem,omitempty"`
	Path         string         `json:"path,omitempty"`
	AbsPath      string         `json:"absPath,omitempty"`
	Title        string         `json:"title,omitempty"`
	Lead         string         `json:"lead,omitempty"`
	Body         string         `json:"body,omitempty"`
	Snippets     []string       `json:"snippets,omitempty"`
	RawContent   string         `json:"rawContent,omitempty"`
	WordCount    int            `json:"wordCount,omitempty"`
	Tags         []string       `json:"tags,omitempty"`
	Metadata     map[string]any `json:"metadata,omitempty"`
	Created      *time.Time     `json:"created,omitempty"`
	Modified     *time.Time     `json:"modified,omitempty"`
	Checksum     string         `json:"checksum,omitempty"`
}
