package main

import (
	"log"
	"os"
	"sync"

	"gioui.org/app"
	"gioui.org/op"
	"gioui.org/text"
	"gioui.org/unit"
	"gioui.org/font/gofont"

	"pad/internal/editor"
	"pad/internal/ui"
)

func main() {
	go func() {
		w := new(app.Window)
		w.Option(app.Title("Pad"))
		w.Option(app.Size(unit.Dp(390), unit.Dp(844)))
		if err := run(w); err != nil {
			log.Fatal(err)
		}
		os.Exit(0)
	}()
	app.Main()
}

func run(w *app.Window) error {
	var ops op.Ops

	shaper := text.NewShaper(text.WithCollection(gofont.Collection()))
	renderer := ui.New(ui.Theme{FontSize: 14}, shaper)

	// Track window dimensions in pixels (from ConfigEvent) and DP (for logic layer)
	var pixelW, pixelH int
	var dpW, dpH ui.Dp
	var scale float32 = 1.0 // updated on first FrameEvent

	// Create logic instance with initial DP size
	logic := editor.NewLogic(dpW, dpH)

	// Shared state protected by mutex
	var mu sync.Mutex
	var elems []ui.Element

	// Start logic goroutine
	go logic.Run()

	// Start frame receiver goroutine
	go frameReceiver(w, &mu, &elems, logic.FrameChan())

	// Main event loop
	for {
		switch e := w.Event().(type) {
		case app.DestroyEvent:
			return e.Err
		case app.ConfigEvent:
			// Store pixel dimensions from ConfigEvent
			pixelW = e.Config.Size.X
			pixelH = e.Config.Size.Y
			// Convert to DP using current scale (default 1.0 until first FrameEvent)
			dpW = ui.ToDp(ui.Px(pixelW), scale)
			dpH = ui.ToDp(ui.Px(pixelH), scale)
			// Send config event to logic goroutine (in DP)
			logic.ConfigChan() <- editor.ConfigEvent{
				Width:  dpW,
				Height: dpH,
			}
		case app.FrameEvent:
			// Update scale from the frame context
			gtx := app.NewContext(&ops, e)
			scale = gtx.Metric.PxPerDp

			// Reconvert pixel dimensions to DP with actual scale
			dpW = ui.ToDp(ui.Px(pixelW), scale)
			dpH = ui.ToDp(ui.Px(pixelH), scale)

			// Send updated config to logic goroutine
			logic.ConfigChan() <- editor.ConfigEvent{
				Width:  dpW,
				Height: dpH,
			}

			// Acquire mutex, read frame, draw, release mutex
			mu.Lock()
			currentElems := elems
			mu.Unlock()

			renderer.Draw(gtx, currentElems)
			e.Frame(&ops)
		}
	}
}

// frameReceiver runs in a separate goroutine and bridges frameChan to the main loop.
func frameReceiver(w *app.Window, mu *sync.Mutex, elems *[]ui.Element, frameChan <-chan []ui.Element) {
	for {
		frame := <-frameChan
		mu.Lock()
		*elems = frame
		w.Invalidate()
		mu.Unlock()
	}
}
