import {expect} from "chai"
import {Formatter} from "../../../source/text/formatter.mjs";


describe('Formatter', function () {

    // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/275
    describe('change empty handling', function () {

        it('modification 1', function () {

            const  formatter = new Formatter({

                a: null,
                b: undefined,
                c : 0


            })

            expect(formatter.format("${a | tostring}")).to.be.equal('null');
            expect(formatter.format("${b | tostring}")).to.be.equal('undefined');
            expect(formatter.format("${c | tostring}")).to.be.equal('0');
        })
    })

        // https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/47
    describe('examples', function () {

        it('rfc example should run', function () {
            expect(new Formatter({

                a: {
                    b: {
                        c: "Hello"
                    },
                    d: "World",
                    e: 1
                }

            }).format("${a.b.c} ${a.d | toupper}!")).to.be.equal('Hello WORLD!');
        })

        it('doc example should run', function () {
            expect(new Formatter({

                a: {
                    b: {
                        c: "Hello"
                    },
                    d: "world",
                }

            }).format("${a.b.c} ${a.d | ucfirst}!")).to.be.equal('Hello World!');
        })


    })

    describe('set marker()', function () {


        [
            ['#a#', '#', undefined, 'test'],
            ['{a}', '{', '}', 'test'],
            ['i18n{a}', 'i18n{', '}', 'test'],

        ].forEach(function (data) {

            let a = data.shift()
            let b = data.shift()
            let c = data.shift()
            let d = data.shift()

            it('format ' + a + ' with marker ' + b + ' and ' + c + ' should return  ' + b, function () {

                expect(
                    new Formatter({
                        a: "test"
                    }).setMarker(b, c).format(a)
                ).to.equal(d)
            });
        });


    })

    describe('examples()', function () {

        [
            ['${a | tojson}', "{\"b\":{\"c\":\"Hello\"},\"d\":\"World\",\"e\":1}"],
            ['click ${a.d} times', "click World times"],
            [' ${a.b.c} ', ' Hello '],
            [' ${a.b.c}', ' Hello'],
            ['${a.b.c} ', 'Hello '],
            ['${a.b.c}', 'Hello'],
            ['${a.b.c}${a.b.c}', 'HelloHello'],
            ['${a.b.c} ${a.b.c}', 'Hello Hello'],
            ['${a.b.c} ${a.b.c} ', 'Hello Hello '],
            [' ${a.b.c} ${a.b.c} ', ' Hello Hello '],
            [' ${a.b.c} ${a.d} ', ' Hello World '],
            [' ${a.b.c} ${a.b.c | toupper | length | tostring} ', ' Hello 5 '],

        ].forEach(function (data) {

            let a = data.shift()
            let b = data.shift()

            it('format ' + a + ' should return ' + b, function () {

                let obj = {
                    a: {
                        b: {
                            c: "Hello"
                        },
                        d: "World",
                        e: 1
                    }
                }


                expect(new Formatter(obj).format(a)).is.equal(b)
            });
        });

    });

    describe('Marker in marker', function () {

        let text = '${mykey${subkey}}';
        let expected = '1';

        it('format ' + text + ' should ' + expected, function () {

            let obj = {
                mykey2: "1",
                subkey: "2"
            };

            expect(new Formatter(obj).format(text)).is.equal(expected)

        });


    });

    describe('setParameterChars()', function () {

        it('setParameterChars() should return Instance', function () {
            expect(new Formatter({}).setParameterChars('a', 'b')).is.instanceof(Formatter);

        });


    });


    describe('with callbacks', function () {

        it('add callback', function () {
            const formatter = new Formatter({
                x: '1'
            }, {
                callbacks: {
                    my: (value) => {
                        return "!" + value + "!"
                    }
                }
            });

            expect(formatter.format('${x | call:my}')).is.equal('!1!');

        });


    });

    describe('Marker in marker with parameter', function () {

        let text = '${mykey::mykey=${subkey}}';
        let expected = '2';

        it('format ' + text + ' should ' + expected, function () {

            let obj = {
                subkey: "2"
            };

            expect(new Formatter(obj).format(text)).is.equal(expected)

        });
    });

    describe('exceptions', function () {

        [
            ['${a.b.x}', TypeError],
            ['${a.b.d | toupper | length}', TypeError],
            ['${a.b.d}', TypeError],  // a.b.d return undefined by pathfinder 
            ['${a.b.d | tolower}', TypeError],  // a.b.d return undefined by pathfinder 
            ['${a | }', Error],

        ].forEach(function (data) {

            let a = data.shift()
            let b = data.shift()

            it('format ' + a + ' should throw ' + typeof b, function () {

                expect(() => {
                        new Formatter({
                            a: {
                                b: {
                                    c: "test",
                                    d: 4
                                }
                            }
                        }).format(a)
                    }
                ).to.throw(b);
            });
        });

    });




    describe('Formatter', () => {
        it('should format a basic string with object values', () => {
            const formatter = new Formatter({name: 'John', age: 30});
            const result = formatter.format('My name is ${name} and I am ${age | tostring} years old.');

            expect(result).to.equal('My name is John and I am 30 years old.');
        });

        it('should format a string with nested markers', () => {
            const text = '${mykey${subkey}}';
            const obj = {mykey2: '1', subkey: '2'};
            const formatter = new Formatter(obj);

            expect(formatter.format(text)).to.equal('1');
        });

        it('should format a string with custom markers', () => {
            const formatter = new Formatter({name: 'John', age: 30});
            formatter.setMarker('[', ']');
            const result = formatter.format('My name is [name] and I am [age | tostring] years old.');

            expect(result).to.equal('My name is John and I am 30 years old.');
        });

        it('should format a string using callback', () => {
            const formatter = new Formatter({x: '1'}, {
                callbacks: {
                    quote: (value) => {
                        return '"' + value + '"';
                    },
                },
            });

            expect(formatter.format('${x | call:quote}')).to.equal('"1"');
        });

        it('should format a string with parameters', () => {
            const obj = {
                a: {
                    b: {
                        c: 'Hello',
                    },
                    d: 'world',
                },
            };
            const formatter = new Formatter(obj);
            const result = formatter.format('${a.b.c} ${a.d | ucfirst}!');

            expect(result).to.equal('Hello World!');
        });
        
        it('should throw a too deep nesting error', () => {
            const formatter = new Formatter({name: 'John'});
            const nestedText = '${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name${name}}}}}}}}}}}}}}}}}}';
            expect(() => formatter.format(nestedText)).to.throw('syntax error in formatter template');
        });

        it('should throw a too deep nesting error', () => {
            const inputObj = {
                mykey: '${mykey}',
            };

            const formatter = new Formatter(inputObj, {
                recursion: {
                    detectCycles: false,
                },
            });

            const text = '${mykey}';
            let formattedText = text;

            // Create a string with 21 levels of nesting
            for (let i = 0; i < 21; i++) {
                formattedText = '${' + formattedText + '}';
            }

            expect(() => formatter.format(formattedText)).to.throw('too deep nesting');
        });

        it('should detect recursion cycles by default', () => {
            const formatter = new Formatter({
                a: '${b}',
                b: '${a}',
            });

            expect(() => formatter.format('${a}')).to.throw('cycle detected in formatter');
        });

        it('should allow configuring max depth', () => {
            const formatter = new Formatter({value: '${value}'}, {
                recursion: {
                    maxDepth: 3,
                    detectCycles: false,
                },
            });

            expect(() => formatter.format('${value}')).to.throw('too deep nesting');
        });
        
    });


});
