package main

import (
	"bytes"
	"errors"
	"fmt"
	"image"
	"io"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

type screenCaptureSession struct {
	capture func() (image.Image, error)
	close   func()
}

func (s *screenCaptureSession) Capture() (image.Image, error) {
	return s.capture()
}

func (s *screenCaptureSession) Close() {
	if s.close != nil {
		s.close()
	}
}

// captureScreenRegion opens the native desktop region selector and returns the
// selected pixels. The command is deliberately kept outside the web build and
// uses tools commonly available on Ubuntu desktops.
func captureScreenRegion() (image.Image, error) {
	var attempts []error
	if !strings.Contains(strings.ToLower(os.Getenv("XDG_CURRENT_DESKTOP")), "gnome") {
		if _, err := exec.LookPath("grim"); err == nil {
			if _, err := exec.LookPath("slurp"); err == nil {
				img, captureErr := captureWaylandRegion()
				if captureErr == nil {
					return img, nil
				}
				attempts = append(attempts, fmt.Errorf("grim/slurp: %w", captureErr))
			}
		}
	}

	commands := []struct {
		name string
		args func(string) []string
	}{
		{name: "gnome-screenshot", args: func(path string) []string { return []string{"-a", "-f", path} }},
		{name: "scrot", args: func(path string) []string { return []string{"-s", path} }},
		{name: "import", args: func(path string) []string { return []string{path} }},
	}

	for _, candidate := range commands {
		if _, err := exec.LookPath(candidate.name); err != nil {
			continue
		}
		img, err := captureToFile(candidate.name, candidate.args)
		if err == nil {
			return img, nil
		}
		attempts = append(attempts, fmt.Errorf("%s: %w", candidate.name, err))
	}

	if _, err := exec.LookPath("flameshot"); err == nil {
		img, err := captureToFile("flameshot", func(path string) []string {
			return []string{"gui", "--path", path}
		})
		if err != nil {
			attempts = append(attempts, fmt.Errorf("flameshot: %w", err))
		} else {
			return img, nil
		}
	}

	if len(attempts) > 0 {
		return nil, fmt.Errorf("snímání oblasti selhalo: %w", errors.Join(attempts...))
	}
	return nil, errors.New("nenalezen nástroj pro výběr oblasti (grim+slurp, gnome-screenshot, scrot, import nebo flameshot)")
}

// openScreenCaptureSession selects a persistent backend once and returns a
// capture function that can be called repeatedly without reopening the picker.
func openScreenCaptureSession() (*screenCaptureSession, error) {
	var attempts []error
	isGNOME := strings.Contains(strings.ToLower(os.Getenv("XDG_CURRENT_DESKTOP")), "gnome")
	if isGNOME {
		session, sessionErr := openPortalScreenCastSession()
		if sessionErr == nil {
			return session, nil
		}
		attempts = append(attempts, fmt.Errorf("GNOME ScreenCast: %w", sessionErr))
	} else if _, err := exec.LookPath("flameshot"); err == nil {
		session, sessionErr := openFlameshotSession()
		if sessionErr == nil {
			return session, nil
		}
		attempts = append(attempts, fmt.Errorf("flameshot: %w", sessionErr))
	}

	if !isGNOME {
		if _, err := exec.LookPath("grim"); err == nil {
			if _, err := exec.LookPath("slurp"); err == nil {
				selection, selectErr := selectWaylandRegion()
				if selectErr == nil {
					session := &screenCaptureSession{
						capture: func() (image.Image, error) {
							return captureWaylandSelection(selection)
						},
					}
					if _, captureErr := session.Capture(); captureErr == nil {
						return session, nil
					} else {
						attempts = append(attempts, fmt.Errorf("grim/slurp: %w", captureErr))
					}
				}
				if selectErr != nil {
					attempts = append(attempts, fmt.Errorf("grim/slurp: %w", selectErr))
				}
			}
		}
	}

	if _, err := exec.LookPath("slop"); err == nil {
		if _, err := exec.LookPath("maim"); err == nil {
			selection, selectErr := selectX11Region()
			if selectErr == nil {
				session := &screenCaptureSession{
					capture: func() (image.Image, error) {
						return captureToFile("maim", func(path string) []string {
							return []string{"-g", selection, path}
						})
					},
				}
				if _, captureErr := session.Capture(); captureErr == nil {
					return session, nil
				} else {
					attempts = append(attempts, fmt.Errorf("slop/maim: %w", captureErr))
				}
			}
			if selectErr != nil {
				attempts = append(attempts, fmt.Errorf("slop/maim: %w", selectErr))
			}
		}
	}

	if len(attempts) > 0 {
		return nil, fmt.Errorf("živé snímání oblasti selhalo: %w", errors.Join(attempts...))
	}
	if isGNOME {
		return nil, errors.New("GNOME Wayland vyžaduje pro živé sledování desktopový ScreenCast portál a PipeWire. Jednorázové snímání oblasti zůstává dostupné.")
	}
	return nil, errors.New("nenalezen nástroj pro živé snímání vybrané oblasti (Flameshot, grim+slurp nebo slop+maim)")
}

func openFlameshotSession() (*screenCaptureSession, error) {
	selection, err := selectFlameshotRegion()
	if err != nil {
		return nil, fmt.Errorf("výběr oblasti: %w", err)
	}
	session := &screenCaptureSession{
		capture: func() (image.Image, error) {
			return captureFlameshotSelection(selection)
		},
	}
	if _, err := session.Capture(); err != nil {
		return nil, fmt.Errorf("opakované snímání oblasti: %w", err)
	}
	return session, nil
}

func selectFlameshotRegion() (string, error) {
	var stderr bytes.Buffer
	command := exec.Command("flameshot", "gui", "--print-geometry", "--accept-on-select")
	command.Stderr = &stderr
	data, err := command.Output()
	if err != nil {
		if detail := strings.TrimSpace(stderr.String()); detail != "" {
			return "", fmt.Errorf("%w (%s)", err, detail)
		}
		return "", err
	}
	return parseFlameshotRegion(string(data))
}

func captureFlameshotSelection(selection string) (image.Image, error) {
	return captureToFile("flameshot", func(path string) []string {
		return []string{"screen", "--region", selection, "--path", path}
	})
}

func parseFlameshotRegion(output string) (string, error) {
	value := strings.TrimSpace(output)
	var width, height, x, y int
	if n, _ := fmt.Sscanf(value, "%dx%d+%d+%d", &width, &height, &x, &y); n != 4 {
		values := strings.Fields(value)
		if len(values) != 4 {
			return "", errors.New("oblast nebyla vybrána")
		}
		var err error
		if width, err = strconv.Atoi(values[0]); err != nil {
			return "", fmt.Errorf("neplatná oblast Flameshotu: %q", value)
		}
		if height, err = strconv.Atoi(values[1]); err != nil {
			return "", fmt.Errorf("neplatná oblast Flameshotu: %q", value)
		}
		if x, err = strconv.Atoi(values[2]); err != nil {
			return "", fmt.Errorf("neplatná oblast Flameshotu: %q", value)
		}
		if y, err = strconv.Atoi(values[3]); err != nil {
			return "", fmt.Errorf("neplatná oblast Flameshotu: %q", value)
		}
	}
	if width <= 0 || height <= 0 {
		return "", fmt.Errorf("neplatná oblast Flameshotu: %q", value)
	}
	return fmt.Sprintf("%dx%d+%d+%d", width, height, x, y), nil
}

func captureWaylandRegion() (image.Image, error) {
	selection, err := selectWaylandRegion()
	if err != nil {
		return nil, err
	}
	return captureWaylandSelection(selection)
}

func selectWaylandRegion() (string, error) {
	var stderr bytes.Buffer
	slurp := exec.Command("slurp")
	slurp.Stderr = &stderr
	area, err := slurp.Output()
	if err != nil {
		if detail := strings.TrimSpace(stderr.String()); detail != "" {
			return "", fmt.Errorf("výběr oblasti: %w (%s)", err, detail)
		}
		return "", fmt.Errorf("výběr oblasti: %w", err)
	}
	selection := strings.TrimSpace(string(area))
	if selection == "" {
		return "", errors.New("oblast nebyla vybrána")
	}
	return selection, nil
}

func captureWaylandSelection(selection string) (image.Image, error) {
	var stderr bytes.Buffer
	tmp, err := os.CreateTemp("", "fckeuspy-screen-*.png")
	if err != nil {
		return nil, err
	}
	path := tmp.Name()
	if err := tmp.Close(); err != nil {
		_ = os.Remove(path)
		return nil, err
	}
	defer os.Remove(path)

	stderr.Reset()
	grim := exec.Command("grim", "-g", selection, path)
	grim.Stderr = &stderr
	if err := grim.Run(); err != nil {
		if detail := strings.TrimSpace(stderr.String()); detail != "" {
			return nil, fmt.Errorf("snímek oblasti: %w (%s)", err, detail)
		}
		return nil, fmt.Errorf("snímek oblasti: %w", err)
	}
	return decodeImageFile(path)
}

func selectX11Region() (string, error) {
	var stderr bytes.Buffer
	slop := exec.Command("slop", "-f", "%g")
	slop.Stderr = &stderr
	area, err := slop.Output()
	if err != nil {
		if detail := strings.TrimSpace(stderr.String()); detail != "" {
			return "", fmt.Errorf("výběr oblasti: %w (%s)", err, detail)
		}
		return "", fmt.Errorf("výběr oblasti: %w", err)
	}
	selection := strings.TrimSpace(string(area))
	if selection == "" {
		return "", errors.New("oblast nebyla vybrána")
	}
	return selection, nil
}

func captureToFile(name string, args func(string) []string) (image.Image, error) {
	tmp, err := os.CreateTemp("", "fckeuspy-screen-*.png")
	if err != nil {
		return nil, err
	}
	path := tmp.Name()
	if err := tmp.Close(); err != nil {
		_ = os.Remove(path)
		return nil, err
	}
	defer os.Remove(path)

	if err := exec.Command(name, args(path)...).Run(); err != nil {
		return nil, err
	}
	return decodeImageFile(path)
}

func decodeImageFile(path string) (image.Image, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	data, err := io.ReadAll(f)
	if err != nil {
		return nil, err
	}
	img, _, err := image.Decode(bytes.NewReader(data))
	if err != nil {
		return nil, err
	}
	return img, nil
}
