package giu

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io/fs"
	"log"
	"os"
	"path/filepath"

	"github.com/AllenDang/cimgui-go/imgui"
	"github.com/pkg/browser"
)

// ID is an alias type. If some method uses ID it means, that this string will NOT be proceeded anyhow and will be passed
// as-is to cimgui-go. This also means that it deffinitly needs to be
// unique because else it will cause strange UI behavior.
// see: https://github.com/AllenDang/giu/wiki#id
type ID string

func (i ID) String() string {
	return string(i)
}

// PNGToRgba loads image file interface and assume caller takes care of interface proper closing.
func PNGToRgba(file fs.File) (*image.RGBA, error) {
	img, err := png.Decode(file)
	if err != nil {
		return nil, fmt.Errorf("LoadImage: error decoding png image: %w", err)
	}

	return ImageToRgba(img), nil
}

// LoadImage loads image from file and returns *image.RGBA.
func LoadImage(imgPath string) (*image.RGBA, error) {
	imgFile, err := os.Open(filepath.Clean(imgPath))
	if err != nil {
		return nil, fmt.Errorf("LoadImage: error opening image file %s: %w", imgPath, err)
	}

	defer func() {
		if err := imgFile.Close(); err != nil {
			panic(fmt.Sprintf("error closing image file: %s", imgPath))
		}
	}()

	return PNGToRgba(imgFile)
}

// ImageToRgba converts image.Image to *image.RGBA.
func ImageToRgba(img image.Image) *image.RGBA {
	switch trueImg := img.(type) {
	case *image.RGBA:
		return trueImg
	default:
		rgba := image.NewRGBA(trueImg.Bounds())
		draw.Draw(rgba, trueImg.Bounds(), trueImg, image.Pt(0, 0), draw.Src)

		return rgba
	}
}

// ToVec4Color converts rgba color to imgui.Vec4.
func ToVec4Color(col color.Color) imgui.Vec4 {
	const mask = 0xffff

	r, g, b, a := col.RGBA()

	return imgui.Vec4{
		X: float32(r) / mask,
		Y: float32(g) / mask,
		Z: float32(b) / mask,
		W: float32(a) / mask,
	}
}

// ToVec2 converts image.Point to imgui.Vec2.
func ToVec2(pt image.Point) imgui.Vec2 {
	return imgui.Vec2{
		X: float32(pt.X),
		Y: float32(pt.Y),
	}
}

// Vec4ToRGBA converts imgui's Vec4 to golang rgba color.
func Vec4ToRGBA(vec4 imgui.Vec4) color.RGBA {
	return color.RGBA{
		R: uint8(vec4.X * 255),
		G: uint8(vec4.Y * 255),
		B: uint8(vec4.Z * 255),
		A: uint8(vec4.W * 255),
	}
}

// Update updates giu app
// it is done by default after each frame.
// However because frames stops rendering, when no user
// action is done, it may be necessary to
// Update ui manually at some point.
func Update() {
	Context.m.Lock()
	defer Context.m.Unlock()

	if Context.isAlive {
		Context.backend.Refresh()
	}
}

// GetCursorScreenPos returns imgui drawing cursor on the screen.
func GetCursorScreenPos() image.Point {
	pos := imgui.CursorScreenPos()
	return image.Pt(int(pos.X), int(pos.Y))
}

// SetCursorScreenPos sets imgui drawing cursor on the screen.
func SetCursorScreenPos(pos image.Point) {
	imgui.SetCursorScreenPos(imgui.Vec2{X: float32(pos.X), Y: float32(pos.Y)})
}

// GetCursorPos gets imgui drawing cursor inside of current window.
func GetCursorPos() image.Point {
	pos := imgui.CursorPos()
	return image.Pt(int(pos.X), int(pos.Y))
}

// SetCursorPos sets imgui drawing cursor inside of current window.
func SetCursorPos(pos image.Point) {
	imgui.SetCursorPos(imgui.Vec2{X: float32(pos.X), Y: float32(pos.Y)})
}

// GetMousePos returns mouse position.
func GetMousePos() image.Point {
	pos := imgui.MousePos()
	return image.Pt(int(pos.X), int(pos.Y))
}

// GetAvailableRegion returns region available for rendering.
// it is always WindowSize-WindowPadding*2.
func GetAvailableRegion() (width, height float32) {
	region := imgui.ContentRegionAvail()
	return region.X, region.Y
}

// CalcTextSize calls CalcTextSizeV(text, false, -1).
func CalcTextSize(text string) (width, height float32) {
	return CalcTextSizeV(text, false, -1)
}

// CalcTextSizeV calculates text dimensions.
func CalcTextSizeV(text string, hideAfterDoubleHash bool, wrapWidth float32) (w, h float32) {
	size := imgui.CalcTextSizeV(
		text,
		hideAfterDoubleHash,
		wrapWidth,
	)

	return size.X, size.Y
}

// SetNextWindowSize sets size of the next window.
func SetNextWindowSize(width, height float32) {
	imgui.SetNextWindowSize(imgui.Vec2{X: width, Y: height})
}

// ExecCondition represents imgui.Cond.
type ExecCondition imgui.Cond

// imgui conditions.
const (
	ConditionAlways       ExecCondition = ExecCondition(imgui.CondAlways)
	ConditionOnce         ExecCondition = ExecCondition(imgui.CondOnce)
	ConditionFirstUseEver ExecCondition = ExecCondition(imgui.CondFirstUseEver)
	ConditionAppearing    ExecCondition = ExecCondition(imgui.CondAppearing)
)

// SetNextWindowPos sets position of next window.
func SetNextWindowPos(x, y float32) {
	imgui.SetNextWindowPos(imgui.Vec2{X: x, Y: y})
}

// SetNextWindowSizeV does similar to SetNextWIndowSize but allows to specify imgui.Cond.
func SetNextWindowSizeV(width, height float32, condition ExecCondition) {
	imgui.SetNextWindowSizeV(
		imgui.Vec2{
			X: width,
			Y: height,
		},
		imgui.Cond(condition),
	)
}

// SetItemDefaultFocus set the item focused by default.
func SetItemDefaultFocus() {
	imgui.SetItemDefaultFocus()
}

// SetKeyboardFocusHere sets keyboard focus at *NEXT* widget.
func SetKeyboardFocusHere() {
	SetKeyboardFocusHereV(0)
}

// SetKeyboardFocusHereV sets keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget.
func SetKeyboardFocusHereV(i int) {
	imgui.SetKeyboardFocusHereV(int32(i))
}

// PushClipRect pushes a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering.
func PushClipRect(clipRectMin, clipRectMax image.Point, intersectWithClipRect bool) {
	imgui.PushClipRect(ToVec2(clipRectMin), ToVec2(clipRectMax), intersectWithClipRect)
}

// PopClipRect should be called to end PushClipRect.
func PopClipRect() {
	imgui.PopClipRect()
}

// Assert checks if cond. If not cond, it alls golang panic.
func Assert(cond bool, t, method, msg string, args ...any) {
	if !cond {
		fatal(t, method, msg, args...)
	}
}

func fatal(widgetName, method, message string, args ...any) {
	if widgetName != "" {
		widgetName = fmt.Sprintf("(*%s)", widgetName)
	}

	log.Panicf("giu: %s.%s: %s", widgetName, method, fmt.Sprintf(message, args...))
}

// OpenURL opens `url` in default browser.
func OpenURL(url string) {
	if err := browser.OpenURL(url); err != nil {
		log.Printf("Error opening %s: %v", url, err)
	}
}

// ColorToUint converts GO color into Uint32 color
// it is 0xRRGGBBAA.
func ColorToUint(col color.Color) uint32 {
	r, g, b, a := col.RGBA()
	mask := uint32(0xff)

	return r&mask +
		g&mask<<8 +
		b&mask<<16 +
		a&mask<<24
}

// UintToColor converts uint32 of form 0xRRGGBB into color.RGBA.
func UintToColor(col uint32) *color.RGBA {
	mask := 0xff
	r := byte(col >> 24 & uint32(mask))
	g := byte(col >> 16 & uint32(mask))
	b := byte(col >> 8 & uint32(mask))
	a := byte(col >> 0 & uint32(mask))

	return &color.RGBA{
		R: r,
		G: g,
		B: b,
		A: a,
	}
}

// Deg2Rad converts degrees to radians.
func Deg2Rad(deg float32) float32 {
	return deg * 0.01745329252 // deg * 2pi / 360
}
