package choose_fonts

import (
	"fmt"
	"os"
	"strconv"
	"sync"

	"github.com/kovidgoyal/kitty/tools/tui"
	"github.com/kovidgoyal/kitty/tools/tui/graphics"
	"github.com/kovidgoyal/kitty/tools/tui/loop"
	"github.com/kovidgoyal/kitty/tools/utils"
)

var _ = fmt.Print

type State int

const (
	SCANNING_FAMILIES State = iota
	LISTING_FAMILIES
	CHOOSING_FACES
)

type TextStyle struct {
	Font_sz    float64 `json:"font_size"`
	Dpi_x      float64 `json:"dpi_x"`
	Dpi_y      float64 `json:"dpi_y"`
	Foreground string  `json:"foreground"`
	Background string  `json:"background"`
}

type pane interface {
	initialize(*handler) error
	draw_screen() error
	on_wakeup() error
	on_key_event(event *loop.KeyEvent) error
	on_text(text string, from_key_event bool, in_bracketed_paste bool) error
	on_click(id string) error
}

type handler struct {
	opts                 *Options
	lp                   *loop.Loop
	state                State
	err_mutex            sync.Mutex
	err_in_worker_thread error
	mouse_state          tui.MouseState
	render_count         uint
	render_lines         tui.RenderLines
	text_style           TextStyle
	graphics_manager     graphics_manager
	temp_dir             string

	listing    FontList
	faces      faces
	face_pane  face_panel
	if_pane    if_panel
	final_pane final_pane

	panes        []pane
	current_pane pane
}

func (h *handler) set_worker_error(err error) {
	h.err_mutex.Lock()
	defer h.err_mutex.Unlock()
	h.err_in_worker_thread = err
}

func (h *handler) get_worker_error() error {
	h.err_mutex.Lock()
	defer h.err_mutex.Unlock()
	return h.err_in_worker_thread
}

// Events {{{
func (h *handler) initialize() (err error) {
	h.lp.SetCursorVisible(false)
	h.lp.OnQueryResponse = h.on_query_response
	h.lp.QueryTerminal("font_size", "dpi_x", "dpi_y", "foreground", "background")
	h.panes = []pane{&h.listing, &h.faces, &h.face_pane, &h.if_pane, &h.final_pane}
	for _, pane := range h.panes {
		if err = pane.initialize(h); err != nil {
			return err
		}
	}
	// dont use /tmp as it may be mounted in RAM, Le Sigh
	if h.temp_dir, err = os.MkdirTemp(utils.CacheDir(), "kitten-choose-fonts-*"); err != nil {
		return
	}
	initialize_variable_data_cache()
	h.graphics_manager.initialize(h.lp)
	go func() {
		var r ListResult
		h.set_worker_error(kitty_font_backend.query("list_monospaced_fonts", nil, &r))
		h.listing.fonts = r.Fonts
		h.listing.resolved_faces_from_kitty_conf = r.Resolved_faces
		h.lp.WakeupMainThread()
	}()
	h.draw_screen()
	return
}

func (h *handler) finalize() {
	if h.temp_dir != "" {
		os.RemoveAll(h.temp_dir)
		h.temp_dir = ""
	}
	h.lp.SetCursorVisible(true)
	h.lp.SetCursorShape(loop.BLOCK_CURSOR, true)
	h.graphics_manager.finalize()
}

func (h *handler) on_query_response(key, val string, valid bool) error {
	if !valid {
		return fmt.Errorf("Terminal does not support querying the: %s", key)
	}
	set_float := func(k, v string, dest *float64) error {
		if fs, err := strconv.ParseFloat(v, 64); err == nil {
			*dest = fs
		} else {
			return fmt.Errorf("Invalid response from terminal to %s query: %#v", k, v)
		}
		return nil
	}
	switch key {
	case "font_size":
		if err := set_float(key, val, &h.text_style.Font_sz); err != nil {
			return err
		}
	case "dpi_x":
		if err := set_float(key, val, &h.text_style.Dpi_x); err != nil {
			return err
		}
	case "dpi_y":
		if err := set_float(key, val, &h.text_style.Dpi_y); err != nil {
			return err
		}
	case "foreground":
		h.text_style.Foreground = val
		if h.text_style.Background != "" {
			return h.draw_screen()
		}
	case "background":
		h.text_style.Background = val
		if h.text_style.Foreground != "" {
			return h.draw_screen()
		}
	}
	return nil
}

func (h *handler) draw_screen() (err error) {
	h.render_count++
	h.lp.StartAtomicUpdate()
	defer func() {
		h.mouse_state.UpdateHoveredIds()
		h.mouse_state.ApplyHoverStyles(h.lp)
		h.lp.EndAtomicUpdate()
	}()
	h.graphics_manager.clear_placements()
	h.lp.ClearScreenButNotGraphics()
	h.lp.AllowLineWrapping(false)
	h.mouse_state.ClearCellRegions()
	if h.current_pane == nil {
		h.lp.Println("Scanning system for fonts, please wait...")
	} else {
		return h.current_pane.draw_screen()
	}
	return
}

func (h *handler) on_wakeup() (err error) {
	if err = h.get_worker_error(); err != nil {
		return
	}
	if h.current_pane == nil {
		h.current_pane = &h.listing
	}
	return h.listing.on_wakeup()
}

func (h *handler) on_mouse_event(event *loop.MouseEvent) (err error) {
	rc := h.render_count
	redraw_needed := false
	if h.mouse_state.UpdateState(event) {
		redraw_needed = true
	}
	if event.Event_type == loop.MOUSE_CLICK && event.Buttons&loop.LEFT_MOUSE_BUTTON != 0 {
		if err = h.mouse_state.ClickHoveredRegions(); err != nil {
			return
		}
	}
	if redraw_needed && rc == h.render_count {
		err = h.draw_screen()
	}
	return
}

func (h *handler) on_key_event(event *loop.KeyEvent) (err error) {
	if event.MatchesPressOrRepeat("ctrl+c") {
		event.Handled = true
		return fmt.Errorf("canceled by user")
	}
	if h.current_pane != nil {
		err = h.current_pane.on_key_event(event)
	}
	return
}

func (h *handler) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) {
	if h.current_pane != nil {
		err = h.current_pane.on_text(text, from_key_event, in_bracketed_paste)
	}
	return
}

func (h *handler) on_escape_code(etype loop.EscapeCodeType, payload []byte) error {
	switch etype {
	case loop.APC:
		gc := graphics.GraphicsCommandFromAPC(payload)
		if gc != nil {
			return h.graphics_manager.on_response(gc)
		}
	}
	return nil
}

// }}}
