package screenshot

import (
	"bufio"
	"encoding/json"
	"fmt"
	"image"
	"image/png"
	"net"
	"os"
	"path/filepath"
	"time"
)

const niriScreenshotTimeout = 5 * time.Second

// CaptureNiriWindowImage captures the focused window through niri's
// ScreenshotWindow action; niri replies before writing the file, so a second
// event-stream connection waits for ScreenshotCaptured. niri also copies the
// capture to its own clipboard, which cannot be disabled.
func CaptureNiriWindowImage(showPointer bool) (image.Image, error) {
	socket := os.Getenv("NIRI_SOCKET")
	if socket == "" {
		return nil, fmt.Errorf("NIRI_SOCKET not set")
	}

	dir := os.Getenv("XDG_RUNTIME_DIR")
	if dir == "" {
		dir = os.TempDir()
	}
	path := filepath.Join(dir, fmt.Sprintf("dms-window-%d.png", os.Getpid()))

	events, err := subscribeNiriEvents(socket)
	if err != nil {
		return nil, err
	}
	defer events.Close()

	if err := requestNiriWindowScreenshot(socket, path, showPointer); err != nil {
		return nil, err
	}
	defer os.Remove(path)

	if err := awaitNiriScreenshot(events, path); err != nil {
		return nil, err
	}

	f, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("open niri screenshot: %w", err)
	}
	defer f.Close()

	img, err := png.Decode(f)
	if err != nil {
		return nil, fmt.Errorf("decode niri screenshot: %w", err)
	}
	return img, nil
}

func subscribeNiriEvents(socket string) (net.Conn, error) {
	conn, err := net.DialTimeout("unix", socket, 2*time.Second)
	if err != nil {
		return nil, fmt.Errorf("connect niri socket: %w", err)
	}
	_ = conn.SetDeadline(time.Now().Add(niriScreenshotTimeout))

	if _, err := conn.Write([]byte("\"EventStream\"\n")); err != nil {
		conn.Close()
		return nil, fmt.Errorf("subscribe niri events: %w", err)
	}
	return conn, nil
}

func awaitNiriScreenshot(events net.Conn, path string) error {
	scanner := bufio.NewScanner(events)
	scanner.Buffer(make([]byte, 0, 64<<10), 1<<20)

	for scanner.Scan() {
		var event struct {
			ScreenshotCaptured *struct {
				Path string `json:"path"`
			} `json:"ScreenshotCaptured"`
		}
		if json.Unmarshal(scanner.Bytes(), &event) != nil {
			continue
		}
		if event.ScreenshotCaptured != nil && event.ScreenshotCaptured.Path == path {
			return nil
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("await niri screenshot: %w", err)
	}
	return fmt.Errorf("niri event stream closed before screenshot completed")
}

func requestNiriWindowScreenshot(socket, path string, showPointer bool) error {
	conn, err := net.DialTimeout("unix", socket, 2*time.Second)
	if err != nil {
		return fmt.Errorf("connect niri socket: %w", err)
	}
	defer conn.Close()
	_ = conn.SetDeadline(time.Now().Add(3 * time.Second))

	request := map[string]any{
		"Action": map[string]any{
			"ScreenshotWindow": map[string]any{
				"id":            nil,
				"write_to_disk": true,
				"show_pointer":  showPointer,
				"path":          path,
			},
		},
	}
	payload, err := json.Marshal(request)
	if err != nil {
		return err
	}
	if _, err := conn.Write(append(payload, '\n')); err != nil {
		return fmt.Errorf("niri request: %w", err)
	}

	line, err := bufio.NewReader(conn).ReadBytes('\n')
	if err != nil {
		return fmt.Errorf("niri reply: %w", err)
	}

	var reply map[string]json.RawMessage
	if err := json.Unmarshal(line, &reply); err != nil {
		return fmt.Errorf("parse niri reply: %w", err)
	}
	if raw, ok := reply["Err"]; ok {
		var msg string
		_ = json.Unmarshal(raw, &msg)
		return fmt.Errorf("niri screenshot: %s", msg)
	}
	return nil
}

func (s *Screenshoter) captureNiriWindow() (*CaptureResult, error) {
	img, err := CaptureNiriWindowImage(s.config.Cursor == CursorOn)
	if err != nil {
		return nil, err
	}

	buf, err := ImageToBuffer(img)
	if err != nil {
		return nil, err
	}

	scale := 1.0
	if output := s.findOutputByName(GetFocusedMonitor()); output != nil {
		scale = output.effectiveScale()
	}

	return &CaptureResult{
		Buffer:    buf,
		YInverted: false,
		Format:    uint32(FormatARGB8888),
		Scale:     scale,
	}, nil
}
