package fzf

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/junegunn/fzf/src/tui"
	"github.com/junegunn/fzf/src/util"
)

func escapeSingleQuote(str string) string {
	return "'" + strings.ReplaceAll(str, "'", "'\\''") + "'"
}

func runTmux(args []string, opts *Options) (int, error) {
	ns := time.Now().UnixNano()

	output := filepath.Join(os.TempDir(), fmt.Sprintf("fzf-tmux-output-%d", ns))
	if err := mkfifo(output, 0666); err != nil {
		return ExitError, err
	}
	defer os.Remove(output)

	// Find fzf executable
	fzf := "fzf"
	if found, err := os.Executable(); err == nil {
		fzf = found
	}

	// Prepare arguments
	args = append([]string{"--bind=ctrl-z:ignore"}, args...)
	if opts.BorderShape == tui.BorderUndefined {
		args = append(args, "--border")
	}
	args = append(args, "--no-height")
	args = append(args, "--no-tmux")
	argStr := ""
	for _, arg := range args {
		// %q formatting escapes $'foo\nbar' to "foo\nbar"
		argStr += " " + escapeSingleQuote(arg)
	}

	// Build command
	var command string
	if opts.Input == nil && util.IsTty() {
		command = fmt.Sprintf(`%q%s > %q`, fzf, argStr, output)
	} else {
		input := filepath.Join(os.TempDir(), fmt.Sprintf("fzf-tmux-input-%d", ns))
		if err := mkfifo(input, 0644); err != nil {
			return ExitError, err
		}
		defer os.Remove(input)

		go func() {
			inputFile, err := os.OpenFile(input, os.O_WRONLY, 0)
			if err != nil {
				return
			}
			if opts.Input == nil {
				io.Copy(inputFile, os.Stdin)
			} else {
				for item := range opts.Input {
					fmt.Fprint(inputFile, item+opts.PrintSep)
				}
			}
			inputFile.Close()
		}()

		command = fmt.Sprintf(`%q%s < %q > %q`, fzf, argStr, input, output)
	}

	// Get current directory
	dir, err := os.Getwd()
	if err != nil {
		dir = "."
	}

	// Set tmux options for popup placement
	// C        Both    The centre of the terminal
	// R        -x      The right side of the terminal
	// P        Both    The bottom left of the pane
	// M        Both    The mouse position
	// W        Both    The window position on the status line
	// S        -y      The line above or below the status line
	tmuxArgs := []string{"display-popup", "-E", "-B", "-d", dir}
	switch opts.Tmux.position {
	case posUp:
		tmuxArgs = append(tmuxArgs, "-xC", "-y0")
	case posDown:
		tmuxArgs = append(tmuxArgs, "-xC", "-yS")
	case posLeft:
		tmuxArgs = append(tmuxArgs, "-x0", "-yC")
	case posRight:
		tmuxArgs = append(tmuxArgs, "-xR", "-yC")
	case posCenter:
		tmuxArgs = append(tmuxArgs, "-xC", "-yC")
	}
	tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
	tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())

	// To ensure that the options are processed by a POSIX-compliant shell,
	// we need to write the command to a temporary file and execute it with sh.
	exports := os.Environ()
	for idx, pairStr := range exports {
		pair := strings.SplitN(pairStr, "=", 2)
		exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
	}
	temp := writeTemporaryFile(append(exports, command), "\n")
	defer os.Remove(temp)
	tmuxArgs = append(tmuxArgs, "sh", temp)

	// Take the output
	go func() {
		outputFile, err := os.OpenFile(output, os.O_RDONLY, 0)
		if err != nil {
			return
		}
		if opts.Output == nil {
			io.Copy(os.Stdout, outputFile)
		} else {
			reader := bufio.NewReader(outputFile)
			sep := opts.PrintSep[0]
			for {
				item, err := reader.ReadString(sep)
				if err != nil {
					break
				}
				opts.Output <- item
			}
		}

		outputFile.Close()
	}()

	cmd := exec.Command("tmux", tmuxArgs...)
	if err := cmd.Run(); err != nil {
		if exitError, ok := err.(*exec.ExitError); ok {
			return exitError.ExitCode(), err
		}
	}

	return ExitOk, nil
}
