// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build !js
// +build !js

package cmdutil

import (
	"io"

	"github.com/charmbracelet/bubbles/textinput"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"

	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)

func readConsoleFancy(stdout io.Writer, stdin io.Reader, prompt string, secret bool) (string, error) {
	final, err := tea.NewProgram(
		newReadConsoleModel(prompt, secret),
		tea.WithInput(stdin),
		tea.WithOutput(stdout),
	).Run()
	if err != nil {
		return "", err
	}

	model, ok := final.(readConsoleModel)
	contract.Assertf(ok, "expected readConsoleModel, got %T", final)
	if model.Canceled {
		return "", io.EOF
	}

	return model.Value, nil
}

// readConsoleModel drives a bubbletea widget that reads from the console.
type readConsoleModel struct {
	input  textinput.Model
	secret bool

	// Canceled is set to true when the model finishes
	// if the user canceled the operation by pressing Ctrl-C or Esc.
	Canceled bool

	// Value is the user's response to the prompt.
	Value string
}

var _ tea.Model = readConsoleModel{}

func newReadConsoleModel(prompt string, secret bool) readConsoleModel {
	input := textinput.New()
	input.Cursor.Style = lipgloss.NewStyle().
		Foreground(lipgloss.Color("205")) // 205 = hot pink cursor
	if secret {
		input.EchoMode = textinput.EchoPassword
	}
	if prompt != "" {
		input.Prompt = prompt + ": "
	}
	input.Focus() // required to receive input

	return readConsoleModel{
		input:  input,
		secret: secret,
	}
}

// Init initializes the model.
// We don't have any initialization to do, so we just return nil.
func (readConsoleModel) Init() tea.Cmd { return nil }

// Update handles a single tick of the bubbletea loop.
func (m readConsoleModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		// If the user pressed enter, Ctrl-C, or Esc,
		// it's time to stop the bubbletea loop.
		//
		// Only Enter is considered a success.
		//nolint:exhaustive // We only want special handling for these keys.
		switch msg.Type {
		case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc:
			m.Value = m.input.Value()
			m.Canceled = msg.Type != tea.KeyEnter

			m.input.Blur() // hide the cursor
			if m.secret {
				// If we're in secret mode, don't include
				// the '*' characters in the final output
				// so as not to leak the length of the input.
				m.input.EchoMode = textinput.EchoNone
			}

			var cmds []tea.Cmd
			if !m.Canceled {
				// If the user accepts the input,
				// we'll primnt the prompt to the terminal
				// before exiting this loop.
				cmds = append(cmds, tea.Println(m.input.View()))
			}
			cmds = append(cmds, tea.Quit)

			return m, tea.Sequence(cmds...)
		}
	}

	var cmd tea.Cmd
	m.input, cmd = m.input.Update(msg)
	return m, cmd
}

// View renders the prompt.
func (m readConsoleModel) View() string {
	return m.input.View()
}