package handlers

import (
	"context"
	"encoding/json"
	"errors"
	"io/fs"
	"os"
	"sort"
)

// DefaultListMaxEntries caps the number of directory entries returned to bound
// payload size for large directories.
const DefaultListMaxEntries = 1000

// FsListHandler handles fs/list: a bounded directory listing inside the worktree.
type FsListHandler struct {
	MaxEntries int
}

// NewFsListHandler constructs an FsListHandler with default caps.
func NewFsListHandler() *FsListHandler { return &FsListHandler{MaxEntries: DefaultListMaxEntries} }

func (h *FsListHandler) maxEntries() int {
	if h.MaxEntries > 0 {
		return h.MaxEntries
	}
	return DefaultListMaxEntries
}

type fsListParams struct {
	SessionID string `json:"sessionId"`
	Path      string `json:"path"`
}

type fsDirEntry struct {
	Name  string `json:"name"`
	IsDir bool   `json:"is_dir"`
	Size  int64  `json:"size"`
}

type fsListResult struct {
	Entries   []fsDirEntry `json:"entries"`
	Truncated bool         `json:"truncated"`
}

// Handle lists the directory at sess.CwdPath/path. Returns FsError for path
// escape (-32001) or read failure. Results are sorted (dirs first, then name).
func (h *FsListHandler) Handle(_ context.Context, sess SessionContext, paramsJSON json.RawMessage) FsResult {
	var p fsListParams
	if err := json.Unmarshal(paramsJSON, &p); err != nil {
		return FsResult{Err: &FsError{Code: -32602, Message: "invalid params: " + err.Error()}}
	}
	abs, err := resolveSafePath(sess.CwdPath, p.Path)
	if err != nil {
		return FsResult{Err: &FsError{Code: -32001, Message: err.Error()}}
	}
	dirents, err := os.ReadDir(abs)
	if err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			return FsResult{Err: &FsError{Code: -32603, Message: "directory not found"}}
		}
		return FsResult{Err: &FsError{Code: -32603, Message: "list: " + err.Error()}}
	}

	max := h.maxEntries()
	out := fsListResult{Entries: make([]fsDirEntry, 0, len(dirents))}
	for _, de := range dirents {
		if len(out.Entries) >= max {
			out.Truncated = true
			break
		}
		e := fsDirEntry{Name: de.Name(), IsDir: de.IsDir()}
		if info, ierr := de.Info(); ierr == nil && !de.IsDir() {
			e.Size = info.Size()
		}
		out.Entries = append(out.Entries, e)
	}
	sort.SliceStable(out.Entries, func(i, j int) bool {
		if out.Entries[i].IsDir != out.Entries[j].IsDir {
			return out.Entries[i].IsDir // dirs first
		}
		return out.Entries[i].Name < out.Entries[j].Name
	})

	res, _ := json.Marshal(out)
	return FsResult{OK: res}
}
