import { positionForItem, keyForItem, transform, normalizePathOriginal } from './hbs-transform';
import { builders as b, preprocess } from '@glimmer/syntax';

function t(a,b = keyForItem(a),c?) {
    return transform.transform(a, b, c);
}
function pt(a, b = '', c?) {
    const el = preprocess(a).body[0];
    if (b === '') {
        b = keyForItem(el)
    }
    return transform.transform(el, b, c);
}
function w(a: string) {
    const p = b.path(a);
    return transform.wrapToFunction(normalizePathOriginal(p), keyForItem(p));
}

describe('positionForItem', () => {
    it('return correct position from given location', ()=>{
        expect(positionForItem({
            loc: {
                start: {line: 0, column: 3},
                end: {line: 2, column: 5}
            }
        })).toEqual('0,3:2,5');
    });
});

describe('keyForItem', () => {
    it('return correct key from given node', ()=>{
        expect(keyForItem({
            loc: {
                start: {line: 0, column: 3},
                end: {line: 2, column: 5}
            },
            type: 'FooBar'
        })).toEqual('0,3:2,5 - FooBar');
    });
});

describe('transform', () => {
    describe('NumberLiteral', () => {
        it('+ 42', () => {
            expect(t(b.number(42))).toEqual('(): 42 { return 42; /*@path-mark 1,0:1,0*/}');
        });
        it('- 42', () => {
            expect(t(b.number(-42))).toEqual('(): -42 { return -42; /*@path-mark 1,0:1,0*/}');
        });
    });
    describe('StringLiteral', () => {
        it('"foo"', () => {
            expect(t(b.string("foo"))).toEqual("(): \"foo\" { return \"foo\"; /*@path-mark 1,0:1,0*/}");
        });
        it('"foo.bar"', () => {
            expect(t(b.string("foo.bar"))).toEqual("(): \"foo.bar\" { return \"foo.bar\"; /*@path-mark 1,0:1,0*/}");
        });
        it('"foo-bar"', () => {
            expect(t(b.string("foo-bar"))).toEqual("(): \"foo-bar\" { return \"foo-bar\"; /*@path-mark 1,0:1,0*/}");
        });
        it('empty: "', () => {
            expect(t(b.string(""))).toEqual("(): \"\" { return \"\"; /*@path-mark 1,0:1,0*/}");
        });
        it('escaped string literal', () => {
            expect(t(b.string("\"asd\""))).toEqual("(): \"\\\"asd\\\"\" { return \"\\\"asd\\\"\"; /*@path-mark 1,0:1,0*/}");
        });
    });
    describe('TextLiteral', () => {
        it('"foo"', () => {
            expect(t(b.text("foo"))).toEqual("(): \"foo\" { return \"foo\"; /*@path-mark 1,0:1,0*/}");
        });
        it('"foo.bar"', () => {
            expect(t(b.text("foo.bar"))).toEqual("(): \"foo.bar\" { return \"foo.bar\"; /*@path-mark 1,0:1,0*/}");
        });
        it('"foo-bar"', () => {
            expect(t(b.text("foo-bar"))).toEqual("(): \"foo-bar\" { return \"foo-bar\"; /*@path-mark 1,0:1,0*/}");
        });
        it('empty: "', () => {
            expect(t(b.text(""))).toEqual("(): \"\" { return \"\"; /*@path-mark 1,0:1,0*/}");
        });
    });
    describe('NullLiteral', () => {
        it('transform it', () => {
            expect(t(b.null())).toEqual("(): null { return null; /*@path-mark 1,0:1,0*/}");
        });
    });
    describe('UndefinedLiteral', () => {
        it('transform it', () => {
            expect(t(b.undefined())).toEqual("(): undefined { return undefined; /*@path-mark 1,0:1,0*/}");
        });
    })
    describe('BooleanLiteral', () => {
        it('transform true', () => {
            expect(t(b.boolean(true))).toEqual("(): boolean { return true; /*@path-mark 1,0:1,0*/}");
        });
        it('transform false', () => {
            expect(t(b.boolean(false))).toEqual("(): boolean { return false; /*@path-mark 1,0:1,0*/}");
        });
    })
    describe('BlockStatement', () => {
        it('works with local blocks without hash and params', () => {
            expect(t(b.block(b.path('this.foo'), [], b.hash([]), b.blockItself()))).toEqual('() { return this[\"1,0:1,0 - PathExpression\"]([], {}); /*@path-mark 1,0:1,0*/}');
        });
        it('works with local blocks with hash and without params', () => {
            expect(t(b.block(b.path('this.foo'), [], b.hash([b.pair('foo', b.number(1))]), b.blockItself()))).toEqual("() { return this[\"1,0:1,0 - PathExpression\"]([],{'foo':this[\"1,0:1,0 - NumberLiteral\"]()}); /*@path-mark 1,0:1,0*/}");
        });
        it('works with local blocks without hash and with params', () => {
            expect(t(b.block(b.path('this.foo'), [b.string('foo')], b.hash(), b.blockItself()))).toEqual("() { return this[\"1,0:1,0 - PathExpression\"]([this[\"1,0:1,0 - StringLiteral\"]()]); /*@path-mark 1,0:1,0*/}");
        });
        it('works with local blocks with hash and params', () => {
            expect(t(b.block(b.path('this.foo'), [b.string('foo')], b.hash([b.pair('foo', b.number(1))]), b.blockItself()))).toEqual("() { return this[\"1,0:1,0 - PathExpression\"]([this[\"1,0:1,0 - StringLiteral\"]()],{'foo':this[\"1,0:1,0 - NumberLiteral\"]()}); /*@path-mark 1,0:1,0*/}");
        });
        
        it('works with data blocks without hash and params', () => {
            expect(t(b.block(b.path('@foo'), [], b.hash([]), b.blockItself()))).toEqual('() { return this[\"1,0:1,0 - PathExpression\"]([], {}); /*@path-mark 1,0:1,0*/}');
        });
        it('works with data blocks with hash and without params', () => {
            expect(t(b.block(b.path('@foo'), [], b.hash([b.pair('foo', b.number(1))]), b.blockItself()))).toEqual("() { return this[\"1,0:1,0 - PathExpression\"]([],{'foo':this[\"1,0:1,0 - NumberLiteral\"]()}); /*@path-mark 1,0:1,0*/}");
        });
        it('works with data blocks without hash and with params', () => {
            expect(t(b.block(b.path('@foo'), [b.string('foo')], b.hash(), b.blockItself()))).toEqual("() { return this[\"1,0:1,0 - PathExpression\"]([this[\"1,0:1,0 - StringLiteral\"]()]); /*@path-mark 1,0:1,0*/}");
        });
        it('works with data blocks with hash and params', () => {
            expect(t(b.block(b.path('@foo'), [b.string('foo')], b.hash([b.pair('foo', b.number(1))]), b.blockItself()))).toEqual("() { return this[\"1,0:1,0 - PathExpression\"]([this[\"1,0:1,0 - StringLiteral\"]()],{'foo':this[\"1,0:1,0 - NumberLiteral\"]()}); /*@path-mark 1,0:1,0*/}");
        });
    });
    describe('if inline case', () => {
        it('works for inline if', () => {
            expect(pt("{{if this.a this.b this.c}}")).toEqual("() { return this[\"1,5:1,11 - PathExpression\"]() ? this[\"1,12:1,18 - PathExpression\"]() : this[\"1,19:1,25 - PathExpression\"](); /*@path-mark 1,0:1,27*/}");
        });
        it('works for same inline if', () => {
            expect(pt("{{if this.a this.a this.b}}")).toEqual("() { return this[\"1,5:1,11 - PathExpression\"]() ? this[\"1,12:1,18 - PathExpression\"]() : this[\"1,19:1,25 - PathExpression\"](); /*@path-mark 1,0:1,27*/}");
        });
        it('works for short inline if', () => {
            expect(pt("{{if this.a this.b}}")).toEqual("() { return this[\"1,5:1,11 - PathExpression\"]() ? this[\"1,12:1,18 - PathExpression\"]() : undefined; /*@path-mark 1,0:1,20*/}");
        });
        it('works for inline unless', () => {
            expect(pt("{{unless this.a this.b this.c}}")).toEqual("() { return !this[\"1,9:1,15 - PathExpression\"]() ? this[\"1,16:1,22 - PathExpression\"]() : this[\"1,23:1,29 - PathExpression\"](); /*@path-mark 1,0:1,31*/}");
        });
        it('works for short inline unless', () => {
            expect(pt("{{unless this.a this.b}}")).toEqual("() { return !this[\"1,9:1,15 - PathExpression\"]() ? this[\"1,16:1,22 - PathExpression\"]() : undefined; /*@path-mark 1,0:1,24*/}");
        });
    });
    describe('Concat Statement', () => {
        it('support text concat text foo=" name {{bar}}"', () => {
            let node = b.concat([b.text('foo'), b.mustache(b.path('this.foo'))]);
            expect(t(node)).toEqual("(): string { return `${\"foo\"}${this[\"1,0:1,0 - PathExpression\"]()}`; /*@path-mark 1,0:1,0*/}");
        });
        it('support expressions in text concat', () => {
            let node = b.concat([b.text('my-value'), b.mustache(b.path('ifs'), [b.path('this.otherValue'),b.path('this.otherValue'),b.string('missing')])]);
            expect(t(node)).toEqual("(): string { return `${\"my-value\"}${this[\"1,0:1,0 - PathExpression\"]([this[\"1,0:1,0 - PathExpression\"](),this[\"1,0:1,0 - PathExpression\"](),this[\"1,0:1,0 - StringLiteral\"]()])}`; /*@path-mark 1,0:1,0*/}");
        });
    });
    describe('wrapToFunction', () => {
        it('works with paths', () => {
            expect(w('foo')).toEqual('() { return foo; /*@path-mark 1,0:1,0*/}');
        });
        it('works with local', () => {
            expect(w('this.foo')).toEqual('() { return this.foo; /*@path-mark 1,0:1,0*/}');
        });
        it('works with data', () => {
            expect(w('@foo')).toEqual('() { return this.args.foo; /*@path-mark 1,0:1,0*/}');
        });
        it('works with nested data', () => {
            expect(w('@foo.bar')).toEqual('() { return this.args.foo.bar; /*@path-mark 1,0:1,0*/}');
        });
    });
});

describe('normalizePathOriginal', () => {
    let p = (str) => normalizePathOriginal(b.path(str));
    it('works for global paths', () => {
        expect(p('foo')).toEqual('foo');
    });
    it('works for data paths', () => {
        expect(p('@foo')).toEqual('this.args.foo');
    });
    it('works for local paths', () => {
        expect(p('this.foo')).toEqual('this.foo');
    });
    it('works for paths with non-js syntax "foo-baz"', () => {
        expect(p('@foo.bar-baz')).toEqual('this.args.foo["bar-baz"]');
    });
    it('works for paths with non-js syntax nested "foo-baz"', () => {
        expect(p('@foo.bar-baz.foo-bar')).toEqual('this.args.foo["bar-baz"]["foo-bar"]');
    });
    it('works for paths with non-js syntax nested "foo-baz" and mixed cases', () => {
        expect(p('@foo.bar-baz.boo.foo-bar')).toEqual('this.args.foo["bar-baz"].boo["foo-bar"]');
    });
    it('works for specific paths [firstObject]', () => {
        expect(p('this.firstObject')).toEqual('this[0]');
    });
    it('works for specific paths [lastObject]', () => {
        expect(p('this.lastObject')).toEqual('this[0]');
    });
    it('works for specific paths with nesting [lastObject]', () => {
        expect(p('this.lastObject.key.lastObject.boo')).toEqual('this[0].key[0].boo');
    });
});