package core

import (
	"encoding/json"
	"log/slog"
	"os"
	"path/filepath"
	"sync"
)

const (
	defaultDirHistorySize = 10
	dirHistoryFileName    = "dir_history.json"
)

// DirHistory manages directory switch history per project.
type DirHistory struct {
	mu        sync.RWMutex
	storePath string
	entries   map[string][]string // project name -> dir list (most recent first)
	maxSize   int
}

// NewDirHistory creates a new DirHistory with the given data directory.
func NewDirHistory(dataDir string) *DirHistory {
	dh := &DirHistory{
		storePath: filepath.Join(dataDir, dirHistoryFileName),
		entries:   make(map[string][]string),
		maxSize:   defaultDirHistorySize,
	}
	dh.load()
	return dh
}

// Add adds a directory to the history for the given project.
// If the directory already exists, it's moved to the front.
func (dh *DirHistory) Add(project, dir string) {
	if dir == "" {
		return
	}

	dh.mu.Lock()
	defer dh.mu.Unlock()

	entries := dh.entries[project]

	// Remove if exists
	for i, d := range entries {
		if d == dir {
			entries = append(entries[:i], entries[i+1:]...)
			break
		}
	}

	// Add to front
	entries = append([]string{dir}, entries...)

	// Trim to max size
	if len(entries) > dh.maxSize {
		entries = entries[:dh.maxSize]
	}

	dh.entries[project] = entries
	dh.saveLocked()
}

// List returns the history for the given project.
func (dh *DirHistory) List(project string) []string {
	dh.mu.RLock()
	defer dh.mu.RUnlock()

	entries := dh.entries[project]
	if entries == nil {
		return nil
	}

	// Return a copy
	result := make([]string, len(entries))
	copy(result, entries)
	return result
}

// Get returns the directory at the given index (1-based) for the project.
// Returns empty string if index is out of range.
func (dh *DirHistory) Get(project string, index int) string {
	dh.mu.RLock()
	defer dh.mu.RUnlock()

	entries := dh.entries[project]
	if index < 1 || index > len(entries) {
		return ""
	}
	return entries[index-1]
}

// Previous returns the previous directory (index 2, since index 1 is current).
func (dh *DirHistory) Previous(project string) string {
	return dh.Get(project, 2)
}

// Contains checks if a directory is in the history for the given project.
func (dh *DirHistory) Contains(project, dir string) bool {
	dh.mu.RLock()
	defer dh.mu.RUnlock()

	entries := dh.entries[project]
	for _, d := range entries {
		if d == dir {
			return true
		}
	}
	return false
}

// SetMaxSize sets the maximum history size.
func (dh *DirHistory) SetMaxSize(size int) {
	if size < 1 {
		size = 1
	}
	dh.mu.Lock()
	defer dh.mu.Unlock()
	dh.maxSize = size
}

func (dh *DirHistory) load() {
	if dh.storePath == "" {
		return
	}

	data, err := os.ReadFile(dh.storePath)
	if err != nil {
		if !os.IsNotExist(err) {
			slog.Error("dir_history: failed to read", "path", dh.storePath, "error", err)
		}
		return
	}

	var entries map[string][]string
	if err := json.Unmarshal(data, &entries); err != nil {
		slog.Error("dir_history: failed to unmarshal", "path", dh.storePath, "error", err)
		return
	}

	if entries != nil {
		dh.entries = entries
	}
}

func (dh *DirHistory) saveLocked() {
	if dh.storePath == "" {
		return
	}

	data, err := json.MarshalIndent(dh.entries, "", "  ")
	if err != nil {
		slog.Error("dir_history: failed to marshal", "error", err)
		return
	}

	if err := os.MkdirAll(filepath.Dir(dh.storePath), 0700); err != nil {
		slog.Error("dir_history: failed to create dir", "error", err)
		return
	}

	if err := AtomicWriteFile(dh.storePath, data, 0644); err != nil {
		slog.Error("dir_history: failed to write", "path", dh.storePath, "error", err)
	}
}