package main

import (
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"golang.org/x/sys/unix"
)

const fatalRebootDelay = 5 * time.Minute

var fatalCh = make(chan any, 1)

// Deferred in every goroutine that can panic: as PID 1 the message is shown by
// the run loop instead of killing init; anywhere else the panic keeps its stack.
func holdPanic() {
	p := recover()
	if p == nil {
		return
	}
	if os.Getpid() != 1 {
		panic(p)
	}
	select {
	case fatalCh <- p:
	default:
	}
}

// Exiting would take init with it and the kernel's panic screen would cover
// the cause, so it is drawn and held instead, with a reboot to retry.
func fatal(cause error) {
	if os.Getpid() != 1 {
		panic(cause)
	}
	if err := drawFatal(cause); err != nil {
		panic(fmt.Sprintf("%v; drawing it: %v", cause, err))
	}
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	select {
	case <-sig:
	case <-time.After(fatalRebootDelay):
	}
	if err := unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART); err != nil {
		panic(err)
	}
}

func drawFatal(cause error) error {
	fb, err := openFramebuffer()
	if err != nil {
		return err
	}
	title, err := loadFace(true, 40)
	if err != nil {
		return err
	}
	body, err := loadFace(false, 34)
	if err != nil {
		return err
	}

	fb.fill(uiBg)
	noteY := fb.h - spaceRow - body.lineH
	y := spaceRow
	title.draw(fb, spaceRow, y-title.capTop, "FATAL", uiRed)
	y += title.lineH + spaceRow
	for _, line := range wrapWords(cause.Error(), (fb.w-2*spaceRow)/body.cellW) {
		if y+body.lineH > noteY-spaceRow {
			body.draw(fb, spaceRow, y-body.capTop, "...", uiFg)
			break
		}
		body.draw(fb, spaceRow, y-body.capTop, line, uiFg)
		y += body.lineH + spaceTight
	}
	note := fmt.Sprintf("rebooting in %d minutes", int(fatalRebootDelay.Minutes()))
	body.draw(fb, spaceRow, noteY-body.capTop, note, uiDim)
	return fb.flush()
}

func wrapWords(s string, cols int) []string {
	var lines []string
	line := ""
	for _, w := range strings.Fields(s) {
		for len(w) > cols {
			if line != "" {
				lines = append(lines, line)
				line = ""
			}
			lines = append(lines, w[:cols])
			w = w[cols:]
		}
		switch {
		case line == "":
			line = w
		case len(line)+1+len(w) <= cols:
			line += " " + w
		default:
			lines = append(lines, line)
			line = w
		}
	}
	if line != "" {
		lines = append(lines, line)
	}
	return lines
}
