// Package display provides framebuffer display functionality
package display

import (
	"fmt"
	"image"
	"log/slog"
	"os"
	"syscall"
	"unsafe"
)

// Display interface for showing images
type Display interface {
	Show(img *image.RGBA) error
	Clear() error
	Close() error
}

// FramebufferDisplay implements Display for Linux framebuffer
type FramebufferDisplay struct {
	device string
	logger *slog.Logger
	// Cached screen info
	width  uint32
	height uint32
	bpp    uint32
}

type fbVarScreenInfo struct {
	XRes         uint32
	YRes         uint32
	XResVirtual  uint32
	YResVirtual  uint32
	XOffset      uint32
	YOffset      uint32
	BitsPerPixel uint32
	Grayscale    uint32
	Red          fbBitfield
	Green        fbBitfield
	Blue         fbBitfield
	Transp       fbBitfield
	_            [4]byte
}

type fbBitfield struct {
	Offset uint32
	Length uint32
	Right  uint32
}

const (
	fbiogetVscreeninfo = 0x4600
	bitsPerByte        = 8
	bpp32              = 32
	bpp24              = 24
	colorShift         = 8
)

// NewFramebufferDisplay creates a new framebuffer display
func NewFramebufferDisplay(device string, logger *slog.Logger) (*FramebufferDisplay, error) {
	// Open framebuffer device temporarily to get screen info
	file, err := os.OpenFile(device, os.O_RDWR, 0) // #nosec G304 - device path is controlled
	if err != nil {
		return nil, fmt.Errorf("opening framebuffer: %w", err)
	}
	defer func() { _ = file.Close() }()

	var info fbVarScreenInfo
	// #nosec G103 - required for framebuffer ioctl
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), fbiogetVscreeninfo,
		uintptr(unsafe.Pointer(&info)))
	if errno != 0 {
		return nil, fmt.Errorf("getting screen info: %v", errno)
	}

	logger.Info("framebuffer initialized",
		"device", device,
		"width", info.XRes,
		"height", info.YRes,
		"bpp", info.BitsPerPixel)

	return &FramebufferDisplay{
		device: device,
		logger: logger,
		width:  info.XRes,
		height: info.YRes,
		bpp:    info.BitsPerPixel,
	}, nil
}

// Show displays an image on the framebuffer
func (d *FramebufferDisplay) Show(img *image.RGBA) error {
	if d == nil || d.device == "" {
		return fmt.Errorf("invalid display")
	}

	// Open framebuffer device
	file, err := os.OpenFile(d.device, os.O_RDWR, 0) // #nosec G304 - device path is controlled
	if err != nil {
		return fmt.Errorf("opening framebuffer: %w", err)
	}
	defer func() { _ = file.Close() }()

	// Get screen information (in case it changed)
	var info fbVarScreenInfo
	// #nosec G103 - required for framebuffer ioctl
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), fbiogetVscreeninfo,
		uintptr(unsafe.Pointer(&info)))
	if errno != 0 {
		return fmt.Errorf("getting screen info: %v", errno)
	}

	bounds := img.Bounds()
	width := bounds.Dx()
	height := bounds.Dy()

	if width > int(info.XRes) {
		width = int(info.XRes)
	}
	if height > int(info.YRes) {
		height = int(info.YRes)
	}

	// Create buffer for one line at a time
	lineSize := int(info.XRes * info.BitsPerPixel / bitsPerByte)
	line := make([]byte, lineSize)

	// Write image data line by line
	for y := 0; y < height; y++ {
		// Clear line buffer
		for i := range line {
			line[i] = 0
		}

		// Fill line buffer with pixel data
		for x := 0; x < width; x++ {
			r, g, b, a := img.At(x, y).RGBA()
			r, g, b = r>>colorShift, g>>colorShift, b>>colorShift

			offset := x * int(info.BitsPerPixel/bitsPerByte)
			if offset+3 < len(line) {
				switch info.BitsPerPixel {
				case bpp32:
					line[offset] = byte(b)
					line[offset+1] = byte(g)
					line[offset+2] = byte(r)
					line[offset+3] = byte(a >> colorShift)
				case bpp24:
					line[offset] = byte(b)
					line[offset+1] = byte(g)
					line[offset+2] = byte(r)
				}
			}
		}

		// Seek to correct position and write line
		seekPos := int64(y) * int64(lineSize)
		if _, err := file.Seek(seekPos, 0); err != nil {
			return fmt.Errorf("seeking in framebuffer: %w", err)
		}
		if _, err := file.Write(line); err != nil {
			return fmt.Errorf("writing to framebuffer: %w", err)
		}
	}

	return nil
}

// Clear clears the framebuffer
func (d *FramebufferDisplay) Clear() error {
	if d == nil || d.device == "" {
		return fmt.Errorf("invalid display")
	}

	// Open framebuffer device
	file, err := os.OpenFile(d.device, os.O_RDWR, 0) // #nosec G304 - device path is controlled
	if err != nil {
		return fmt.Errorf("opening framebuffer: %w", err)
	}
	defer func() { _ = file.Close() }()

	// Get screen information
	var info fbVarScreenInfo
	// #nosec G103 - required for framebuffer ioctl
	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, file.Fd(), fbiogetVscreeninfo,
		uintptr(unsafe.Pointer(&info)))
	if errno != 0 {
		return fmt.Errorf("getting screen info: %v", errno)
	}

	// Create empty buffer
	lineSize := int(info.XRes * info.BitsPerPixel / bitsPerByte)
	emptyLine := make([]byte, lineSize)

	// Write empty lines
	for y := 0; y < int(info.YRes); y++ {
		seekPos := int64(y) * int64(lineSize)
		if _, err := file.Seek(seekPos, 0); err != nil {
			return fmt.Errorf("seeking in framebuffer: %w", err)
		}
		if _, err := file.Write(emptyLine); err != nil {
			return fmt.Errorf("writing to framebuffer: %w", err)
		}
	}

	return nil
}

// Close closes the framebuffer
func (d *FramebufferDisplay) Close() error {
	// Nothing to close since we open/close on each operation
	return nil
}

// GetWidth returns the framebuffer width
func (d *FramebufferDisplay) GetWidth() uint32 {
	return d.width
}

// GetHeight returns the framebuffer height
func (d *FramebufferDisplay) GetHeight() uint32 {
	return d.height
}
