// Copyright 2013-2022 The Cobra Authors
//
// 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.

package cobra

import (
	"bytes"
	"fmt"
	"io"
	"os"
)

func (c *Command) GenNushellCompletion(w io.Writer) error {
	buf := new(bytes.Buffer)
	WriteStringAndCheck(buf, "# nushell completion -*- shell-script -*- \n")
	WriteStringAndCheck(buf, fmt.Sprintf(`
let cobra_completer = {|spans|
    let ShellCompDirectiveError = %[1]d
    let ShellCompDirectiveNoSpace = %[2]d
    let ShellCompDirectiveNoFileComp = %[3]d
    let ShellCompDirectiveFilterFileExt = %[4]d
    let ShellCompDirectiveFilterDirs = %[5]d
    let ShellCompDirectiveKeepOrder = %[6]d

    let cmd = $spans | first 
    let rest = $spans | skip

    def cobra_log [message] {
        let file = do -i {$env | get NUSHELL_COMP_DEBUG_FILE}
        if $file != null {
            echo $"($message)\n" | save $file --append
        }
    }

    cobra_log $"External Completer called for cmd ($cmd)"

    def exec_complete [
        spans: list<string>
    ] {
        # This will catch the stderr message related to the directive and any other errors,
        # such as the command not being a cobra based command
        let result = do --ignore-errors { COBRA_ACTIVE_HELP=0 run-external $cmd "__complete" ...$spans | complete }

        if $result != null and $result.exit_code == 0 {
            let completions = $result.stdout | lines

            # the directive is the last line
            let directive = do -i { $completions | last | str replace ':' '' | into int }

            let completions = $completions | drop | each { |it| 
                # the first word is the command, the rest is the description
                let words = $it | split row -r '\s{1}'

                # If the last span contains a hypen and equals, attach it to the name
                let last_span = $spans | last
                let words = if ($last_span =~ '^-') and ($last_span =~ '=$') {
                    $words | each {|it| $"($last_span)($it)" }
                } else {
                    $words
                }

                {value: ($words | first | str trim), description: ($words | skip | str join ' ')}
            }

            {completions: $completions, directive: $directive}
        } else {
            {completions: [], directive: -1}
        }
    }

    if (not ($rest | is-empty)) {
        let result = exec_complete $rest
        let completions = $result.completions
        let directive = $result.directive

        # Add space at the end of each completion
        let completions = if $directive != $ShellCompDirectiveNoSpace {
            $completions | each {|it| {value: $"($it.value) ", description: $it.description}}
        } else {
            $completions
        }

        # Cobra returns a list of completions that are supported with this directive
        # There is no way to currently support this in a nushell external completer
        let completions = if $directive == $ShellCompDirectiveFilterFileExt {
            []
        } else {
            $completions
        }

        if $directive == $ShellCompDirectiveNoFileComp {
            # Allow empty results as this will stop file completion
            $completions
        } else if ($completions | is-empty)  or  $directive == $ShellCompDirectiveError {
            # Not returning null causes file completions to break
            # Return null if there are no completions or ShellCompDirectiveError
             null
          } else {
            $completions
        }
    } else {
        null
    }
}
`, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp,
		ShellCompDirectiveFilterFileExt, ShellCompDirectiveFilterDirs, ShellCompDirectiveKeepOrder))

	_, err := buf.WriteTo(w)
	return err
}

func (c *Command) GenNushellCompletionFile(filename string) error {
	outFile, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer outFile.Close()

	return c.GenNushellCompletion(outFile)
}