'use strict';
/* global describe, it, beforeEach, after */
const checkUsage = require('./helpers/utils.cjs').checkOutput;
const yargs = require('../index.cjs');

require('chai').should();

describe('Completion', () => {
  beforeEach(() => {
    yargs.reset();
  });

  after(() => {
    yargs.reset();
  });

  describe('default completion behavior', () => {
    it('avoids repeating already included commands', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', 'apple'])
            .command('foo', 'bar')
            .command('apple', 'banana').argv
      );

      // should not suggest foo for completion unless foo is subcommand of apple
      r.logs.should.not.include('apple');
    });

    it('avoids repeating already included options', () => {
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            '--foo',
            '--',
          ])
            .options({
              foo: {describe: 'foo option'},
              bar: {describe: 'bar option'},
            })
            .completion().argv
      );

      r.logs.should.include('--bar');
      r.logs.should.not.include('--foo');
    });

    it('avoids repeating options whose aliases are already included', () => {
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            '--f',
            '--',
          ])
            .options({
              foo: {describe: 'foo option', alias: 'f'},
              bar: {describe: 'bar option'},
            })
            .completion().argv
      );

      r.logs.should.include('--bar');
      r.logs.should.not.include('--foo');
    });

    it('completes short options with a single dash when the user did not already enter two dashes', () => {
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            '',
          ]).options({
            f: {describe: 'f option', alias: 'foo'},
          }).argv
      );

      r.logs.should.include('-f');
      r.logs.should.not.include('--f');
    });

    it('completes short options with two dashes when the user already entered two dashes', () => {
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            '--',
          ]).options({
            f: {describe: 'f option', alias: 'foo'},
          }).argv
      );

      r.logs.should.include('--f');
      r.logs.should.not.include('-f');
    });

    it('completes single digit options with two dashes', () => {
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            '',
          ]).options({
            1: {describe: '1 option', alias: 'one'},
          }).argv
      );

      r.logs.should.include('--1');
      r.logs.should.not.include('-1');
    });

    it('completes with no- prefix flags defaulting to true when boolean-negation is set', () => {
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', './completion', ''])
            .options({
              foo: {describe: 'foo flag', type: 'boolean', default: true},
              bar: {describe: 'bar flag', type: 'boolean'},
            })
            .parserConfiguration({'boolean-negation': true}).argv
      );

      r.logs.should.include('--no-foo');
      r.logs.should.include('--foo');
      r.logs.should.not.include('--no-bar');
      r.logs.should.include('--bar');
    });

    it('avoids repeating flags whose negated counterparts are already included', () => {
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            '--no-foo',
            '--no-bar',
            '',
          ])
            .options({
              foo: {describe: 'foo flag', type: 'boolean', default: true},
              bar: {describe: 'bar flag', type: 'boolean'},
              baz: {describe: 'bar flag', type: 'boolean'},
            })
            .parserConfiguration({'boolean-negation': true}).argv
      );

      r.logs.should.not.include('--no-foo');
      r.logs.should.not.include('--foo');
      r.logs.should.not.include('--no-bar');
      r.logs.should.not.include('--bar');
      r.logs.should.include('--baz');
    });

    it('ignores no- prefix flags when boolean-negation is not set', () => {
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            '--no-bar',
            '',
          ]).options({
            foo: {describe: 'foo flag', type: 'boolean', default: true},
            bar: {describe: 'bar flag', type: 'boolean'},
          }).argv
      );

      r.logs.should.not.include('--no-foo');
      r.logs.should.include('--foo');
      r.logs.should.not.include('--no-bar');
      r.logs.should.include('--bar');
    });

    it('completes options for the correct command', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', 'cmd2', '--o'])
            .help(false)
            .version(false)
            .command('cmd1', 'first command', subYargs => {
              subYargs.options({
                opt1: {
                  describe: 'first option',
                },
              });
            })
            .command('cmd2', 'second command', subYargs => {
              subYargs.options({
                opt2: {
                  describe: 'second option',
                },
              });
            })
            .completion().argv
      );

      r.logs.should.have.length(1);
      r.logs.should.include('--opt2');
    });

    it('does not complete hidden commands', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', 'cmd'])
            .command('cmd1', 'first command')
            .command('cmd2', false)
            .completion('completion', false).argv
      );

      r.logs.should.have.length(1);
      r.logs.should.include('cmd1');
    });

    it('does not include positional arguments', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(() => {
        return yargs(['./completion', '--get-yargs-completions', 'cmd'])
          .command('cmd1 [arg]', 'first command')
          .command('cmd2 <arg>', 'second command')
          .completion('completion', false).argv;
      });

      r.logs.should.have.length(2);
      r.logs.should.include('cmd1');
      r.logs.should.include('cmd2');
    });

    it('works if command has no options', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', 'foo', '--b'])
            .help(false)
            .version(false)
            .command('foo', 'foo command', subYargs => {
              return subYargs.completion().parse();
            })
            .completion().argv
      );

      r.logs.should.have.length(0);
    });

    it("returns arguments as completion suggestion, if next contains '-'", () => {
      process.env.SHELL = '/bin/basg';
      const r = checkUsage(
        () =>
          yargs(['./usage', '--get-yargs-completions', '-f'])
            .option('foo', {
              describe: 'foo option',
            })
            .command('bar', 'bar command')
            .completion().argv
      );

      r.logs.should.include('--foo');
      r.logs.should.not.include('bar');
    });
  });

  describe('generateCompletionScript()', () => {
    it('replaces completion command variable with custom completion command in script', () => {
      const r = checkUsage(
        () => yargs([]).completion('flintlock').showCompletionScript(),
        ['ndm']
      );

      r.logs[0].should.match(/ndm flintlock >>/);
    });

    it('if $0 has a .js extension, a ./ prefix is added', () => {
      const r = checkUsage(() => yargs([]).showCompletionScript(), ['test.js']);

      r.logs[0].should.match(/\.\/test.js --get-yargs-completions/);
    });

    it('allows $0 and cmd to be set', () => {
      const r = checkUsage(() =>
        yargs([]).showCompletionScript(
          '/path/to/my/app',
          'show-completions-script'
        )
      );

      r.logs[0].should.match(
        /Installation: \/path\/to\/my\/app show-completions-script/
      );
    });
  });

  describe('completion()', () => {
    it('shows completion script if command registered with completion(cmd) is called', () => {
      const r = checkUsage(
        () => yargs(['completion']).completion('completion').argv,
        ['ndm']
      );

      r.logs[0].should.match(/ndm --get-yargs-completions/);
    });

    it('allows a custom function to be registered for completion', () => {
      const r = checkUsage(
        () =>
          yargs(['--get-yargs-completions'])
            .help('h')
            .completion('completion', (current, argv) => ['cat', 'bat']).argv
      );

      r.logs.should.include('cat');
      r.logs.should.include('bat');
    });

    it('passes current arg for completion and the parsed arguments thus far to custom function', () => {
      const r = checkUsage(
        () =>
          yargs(['ndm', '--get-yargs-completions', '--cool', 'ma']).completion(
            'completion',
            (current, argv) => {
              if (current === 'ma' && argv.cool) return ['success!'];
            }
          ).argv
      );

      r.logs.should.include('success!');
    });

    it('if a promise is returned, completions can be asynchronous', done => {
      checkUsage(
        cb => {
          yargs(['--get-yargs-completions'])
            .completion(
              'completion',
              (current, argv) =>
                new Promise((resolve, reject) => {
                  setTimeout(() => {
                    resolve(['apple', 'banana']);
                  }, 10);
                })
            )
            .parse();
        },
        null,
        (err, r) => {
          if (err) throw err;
          r.logs.should.include('apple');
          r.logs.should.include('banana');
          return done();
        }
      );
    });

    it('if a promise is returned, errors are handled', done => {
      checkUsage(
        () => {
          yargs(['--get-yargs-completions'])
            .completion(
              'completion',
              (current, argv) =>
                new Promise((resolve, reject) => {
                  setTimeout(() => {
                    reject(new Error('Test'));
                  }, 10);
                })
            )
            .parse();
        },
        null,
        err => {
          err.message.should.equal('Test');
          return done();
        }
      );
    });

    it('if a callback parameter is provided, completions can be asynchronous', done => {
      checkUsage(
        () => {
          yargs(['--get-yargs-completions'])
            .completion('completion', (current, argv, completion) => {
              setTimeout(() => {
                completion(['apple', 'banana']);
              }, 10);
            })
            .parse();
        },
        null,
        (err, r) => {
          if (err) throw err;
          r.logs.should.include('apple');
          r.logs.should.include('banana');
          return done();
        }
      );
    });
  });

  describe('bash', () => {
    it('returns a list of commands as completion suggestions', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', ''])
            .command('foo', 'bar')
            .command('apple', 'banana')
            .completion().argv
      );

      r.logs.should.include('apple');
      r.logs.should.include('foo');
    });

    it('avoids interruption from command recommendations', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            'a',
          ])
            .command('apple', 'fruit')
            .command('aardvark', 'animal')
            .recommendCommands()
            .completion().argv
      );

      r.errors.should.deep.equal([]);
      r.logs.should.include('apple');
      r.logs.should.include('aardvark');
    });

    it('avoids interruption from default command', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(
        () =>
          yargs(['./usage', '--get-yargs-completions', './usage', ''])
            .usage('$0 [thing]', 'skipped', subYargs => {
              subYargs.command('aardwolf', 'is a thing according to google');
            })
            .command('aardvark', 'animal')
            .completion().argv
      );

      r.errors.should.deep.equal([]);
      r.logs.should.not.include('aardwolf');
      r.logs.should.include('aardvark');
    });

    it('completes options for a command', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', 'foo', '--b'])
            .command('foo', 'foo command', subYargs =>
              subYargs
                .options({
                  bar: {
                    describe: 'bar option',
                  },
                })
                .help(true)
                .version(false)
            )
            .completion().argv
      );

      r.logs.should.have.length(2);
      r.logs.should.include('--bar');
      r.logs.should.include('--help');
    });

    describe('generateCompletionScript()', () => {
      it('replaces application variable with $0 in script', () => {
        process.env.SHELL = '/bin/bash';
        const r = checkUsage(() => yargs([]).showCompletionScript(), ['ndm']);

        r.logs[0].should.match(/bashrc/);
        r.logs[0].should.match(/ndm --get-yargs-completions/);
      });
    });

    // fixes for #177.
    it('does not apply validation when --get-yargs-completions is passed in', () => {
      process.env.SHELL = '/bin/bash';
      const r = checkUsage(() => {
        try {
          return yargs(['./completion', '--get-yargs-completions', '--'])
            .option('foo', {})
            .completion()
            .strict().argv;
        } catch (e) {
          console.log(e.message);
        }
      });

      r.errors.length.should.equal(0);
      r.logs.should.include('--foo');
    });

    describe('getCompletion()', () => {
      it('returns default completion to callback', () => {
        process.env.SHELL = '/bin/bash';
        const r = checkUsage(() => {
          yargs()
            .command('foo', 'bar')
            .command('apple', 'banana')
            .completion()
            .getCompletion([''], completions => {
              (completions || []).forEach(completion => {
                console.log(completion);
              });
            });
        });
        r.logs.should.include('apple');
        r.logs.should.include('foo');
      });
      it('returns default completion to callback for options', () => {
        process.env.SHELL = '/bin/bash';
        const r = checkUsage(() => {
          yargs()
            .option('apple')
            .option('foo')
            .completion()
            .getCompletion(['$0', '-'], completions => {
              (completions || []).forEach(completion => {
                console.log(completion);
              });
            });
        });
        r.logs.should.include('--apple');
        r.logs.should.include('--foo');
      });
    });
  });

  describe('zsh', () => {
    it('returns a list of commands as completion suggestions', () => {
      process.env.SHELL = '/bin/zsh';
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', ''])
            .command('foo', 'bar')
            .command('apple', 'banana')
            .completion().argv
      );

      r.logs.should.include('apple:banana');
      r.logs.should.include('foo:bar');
    });

    it('avoids interruption from command recommendations', () => {
      process.env.SHELL = '/bin/zsh';
      const r = checkUsage(
        () =>
          yargs([
            './completion',
            '--get-yargs-completions',
            './completion',
            'a',
          ])
            .command('apple', 'fruit')
            .command('aardvark', 'animal')
            .recommendCommands()
            .completion().argv
      );

      r.errors.should.deep.equal([]);
      r.logs.should.include('apple:fruit');
      r.logs.should.include('aardvark:animal');
    });

    it('avoids interruption from default command', () => {
      process.env.SHELL = '/bin/zsh';
      const r = checkUsage(
        () =>
          yargs(['./usage', '--get-yargs-completions', './usage', ''])
            .usage('$0 [thing]', 'skipped', subYargs => {
              subYargs.command('aardwolf', 'is a thing according to google');
            })
            .command('aardvark', 'animal')
            .completion().argv
      );

      r.errors.should.deep.equal([]);
      r.logs.should.not.include('aardwolf');
      r.logs.should.include('aardvark:animal');
    });

    it('completes options for a command', () => {
      process.env.SHELL = '/bin/zsh';
      const r = checkUsage(
        () =>
          yargs(['./completion', '--get-yargs-completions', 'foo', '--b'])
            .command('foo', 'foo command', subYargs =>
              subYargs
                .options({
                  bar: {
                    describe: 'bar option',
                  },
                })
                .help(true)
                .version(false)
            )
            .completion().argv
      );

      r.logs.should.have.length(2);
      r.logs.should.include('--bar:bar option');
      r.logs.should.include('--help:Show help');
    });

    it('replaces application variable with $0 in script', () => {
      process.env.SHELL = '/bin/zsh';
      const r = checkUsage(() => yargs([]).showCompletionScript(), ['ndm']);

      r.logs[0].should.match(/zshrc/);
      r.logs[0].should.match(/ndm --get-yargs-completions/);
    });

    describe('getCompletion()', () => {
      it('returns default completion to callback', () => {
        process.env.SHELL = '/bin/zsh';
        const r = checkUsage(() => {
          yargs()
            .command('foo', 'bar')
            .command('apple', 'banana')
            .completion()
            .getCompletion([''], completions => {
              (completions || []).forEach(completion => {
                console.log(completion);
              });
            });
        });

        r.logs.should.include('apple:banana');
        r.logs.should.include('foo:bar');
      });
    });

    it('does not apply validation when --get-yargs-completions is passed in', () => {
      process.env.SHELL = '/bin/zsh';
      const r = checkUsage(() => {
        try {
          return yargs(['./completion', '--get-yargs-completions', '--'])
            .option('foo', {describe: 'bar'})
            .completion()
            .strict().argv;
        } catch (e) {
          console.log(e.message);
        }
      });

      r.errors.length.should.equal(0);
      r.logs.should.include('--foo:bar');
    });

    it('bails out early when full command matches', () => {
      process.env.SHELL = '/bin/zsh';
      const r = checkUsage(() => {
        try {
          return yargs(['./completion', '--get-yargs-completions', 'dream'])
            .commandDir('./fixtures/cmddir', {recurse: true})
            .demand(1)
            .strict()
            .completion().argv;
        } catch (e) {
          console.log(e.message);
        }
      });
      r.errors.length.should.equal(0);
      r.logs.should.include('of-memory:Dream about a specific memory');
    });
  });
});
