package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/widget"

	"nymdrop/internal/reader"
)

// submissionEntry is one row in the submissions list: enough to render the
// row (label) and to load the full body on demand (path) without keeping
// every decrypted body in memory at once.
type submissionEntry struct {
	label string
	path  string
}

// runGUI is the non-technical front-end for nymdrop-reader: a journalist
// running this from a USB stick should never need a terminal. It drives the
// same reader.Instance the CLI uses, through the same Hooks contract, so
// there is exactly one implementation of the receive/decrypt/save logic.
func runGUI(cfg reader.Config) {
	a := app.NewWithID("art.virebent.nymdrop-reader")
	w := a.NewWindow("NymDrop Reader")
	w.Resize(fyne.NewSize(900, 560))

	statusLabel := widget.NewLabel("Starting...")
	statusLabel.Wrapping = fyne.TextWrapWord

	addrEntry := widget.NewEntry()
	addrEntry.Disable()
	addrEntry.SetPlaceHolder("Nym inbox address will appear here once connected")

	outDirLabel := widget.NewLabel("")

	var submissions []submissionEntry

	content := widget.NewMultiLineEntry()
	content.Wrapping = fyne.TextWrapWord
	content.Disable()

	list := widget.NewList(
		func() int { return len(submissions) },
		func() fyne.CanvasObject { return widget.NewLabel("template") },
		func(id widget.ListItemID, obj fyne.CanvasObject) {
			obj.(*widget.Label).SetText(submissions[id].label)
		},
	)
	list.OnSelected = func(id widget.ListItemID) {
		if id < 0 || id >= len(submissions) {
			return
		}
		data, err := os.ReadFile(submissions[id].path)
		if err != nil {
			content.SetText(fmt.Sprintf("error reading %s: %v", submissions[id].path, err))
			return
		}
		content.SetText(string(data))
	}

	hooks := reader.Hooks{
		Log: func(format string, args ...any) {
			msg := fmt.Sprintf(format, args...)
			fyne.Do(func() { statusLabel.SetText(msg) })
		},
		SelfAddress: func(addr string) {
			fyne.Do(func() {
				addrEntry.SetText(addr)
				statusLabel.SetText("Connected -- waiting for submissions.")
			})
		},
		Submission: func(path, preview string) {
			ts := strings.TrimSuffix(filepath.Base(path), ".txt")
			oneLine := strings.ReplaceAll(preview, "\n", " ")
			if len(oneLine) > 80 {
				oneLine = oneLine[:80] + "..."
			}
			entry := submissionEntry{label: ts + "  " + oneLine, path: path}
			fyne.Do(func() {
				// Newest first: a journalist checking the box wants the
				// latest tip on top, not scrolled to the bottom.
				submissions = append([]submissionEntry{entry}, submissions...)
				list.Refresh()
				statusLabel.SetText(fmt.Sprintf("%d submission(s) received.", len(submissions)))
			})
		},
		Attachment: func(path string, size int) {
			fyne.Do(func() {
				statusLabel.SetText(fmt.Sprintf("attachment saved: %s (%d bytes)", path, size))
			})
		},
		Error: func(err error) {
			fyne.Do(func() { statusLabel.SetText("error: " + err.Error()) })
		},
	}

	in, err := reader.Start(cfg, hooks)
	if err != nil {
		statusLabel.SetText("failed to start: " + err.Error())
	} else {
		outDirLabel.SetText("Submissions saved to: " + in.OutDir())
		go in.ReceiveLoop()
	}

	top := container.NewVBox(
		widget.NewLabelWithStyle("NymDrop Reader", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
		statusLabel,
		widget.NewForm(widget.NewFormItem("Nym inbox address", addrEntry)),
		outDirLabel,
	)

	split := container.NewHSplit(
		container.NewBorder(widget.NewLabel("Submissions"), nil, nil, nil, list),
		container.NewBorder(widget.NewLabel("Content"), nil, nil, nil, content),
	)
	split.Offset = 0.35

	w.SetContent(container.NewBorder(top, nil, nil, nil, split))

	w.SetCloseIntercept(func() {
		if in != nil {
			in.Close()
		}
		w.Close()
	})

	w.ShowAndRun()
}
