package main

import (
	"context"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/shirou/gopsutil/v4/mem"
)

func collectMemory() (MemoryStatus, error) {
	vm, err := mem.VirtualMemory()
	if err != nil {
		return MemoryStatus{}, err
	}

	swap, _ := mem.SwapMemory()
	if swap == nil {
		swap = &mem.SwapMemoryStat{}
	}
	pressure := getMemoryPressure()

	// On macOS, vm.Cached is 0, so we calculate from file-backed pages.
	cached := vm.Cached
	if runtime.GOOS == "darwin" && cached == 0 {
		cached = getFileBackedMemory()
	}

	return MemoryStatus{
		Used:        vm.Used,
		Total:       vm.Total,
		UsedPercent: vm.UsedPercent,
		SwapUsed:    swap.Used,
		SwapTotal:   swap.Total,
		Cached:      cached,
		Pressure:    pressure,
	}, nil
}

func getFileBackedMemory() uint64 {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	out, err := runCmd(ctx, "vm_stat")
	if err != nil {
		return 0
	}

	// Parse page size from first line: "Mach Virtual Memory Statistics: (page size of 16384 bytes)"
	var pageSize uint64 = 4096 // Default
	firstLine := true
	for line := range strings.Lines(out) {
		if firstLine {
			firstLine = false
			if strings.Contains(line, "page size of") {
				if _, after, found := strings.Cut(line, "page size of "); found {
					if before, _, found := strings.Cut(after, " bytes"); found {
						if size, err := strconv.ParseUint(strings.TrimSpace(before), 10, 64); err == nil {
							pageSize = size
						}
					}
				}
			}
		}

		// Parse "File-backed pages: 388975."
		if strings.Contains(line, "File-backed pages:") {
			if _, after, found := strings.Cut(line, ":"); found {
				numStr := strings.TrimSpace(after)
				numStr = strings.TrimSuffix(numStr, ".")
				if pages, err := strconv.ParseUint(numStr, 10, 64); err == nil {
					return pages * pageSize
				}
			}
		}
	}
	return 0
}

func getMemoryPressure() string {
	if runtime.GOOS != "darwin" {
		return ""
	}
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	out, err := runCmd(ctx, "memory_pressure")
	if err != nil {
		return ""
	}
	lower := strings.ToLower(out)
	if strings.Contains(lower, "critical") {
		return "critical"
	}
	if strings.Contains(lower, "warn") {
		return "warn"
	}
	if strings.Contains(lower, "normal") {
		return "normal"
	}
	return ""
}
