/* Essentials YOOtheme Pro 2.4.12 build 1202.1125; ZOOlanders https://www.zoolanders.com; Copyright (C) Joolanders, SL; http://www.gnu.org/licenses/gpl.html GNU/GPL */

/**
 * Validates the syntax of a template string.
 * The syntax is valid when:
 * - Every opening tag {{ conditions.id }} has a matching closing tag {{ / }}
 * - Every {{ else }} tag is between an opening and closing tag (optional)
 * - Tags are properly nested
 * - All tags from the same conditional group (opening, else, closing) must be on the same line
 *
 * @param {string} template - The template string to validate
 * @returns {boolean} - True if the syntax is valid, false otherwise
 */
export default function validateTemplateSyntax(template) {
    // First, check if all tags in a conditional block are on the same line
    // This is a simpler check we can do before the full validation
    const ifOpenRegex = /{{ conditions\.(\w{4}) }}/g;
    const elseRegex = /{{ else }}/g;
    const ifCloseRegex = /{{ \/ }}/g;

    // Find all opening tags and track their IDs
    const openTags = [...template.matchAll(ifOpenRegex)];
    const elseTags = [...template.matchAll(elseRegex)];
    const closeTags = [...template.matchAll(ifCloseRegex)];

    // Check if tags are on different lines
    const lines = template.split('\n');
    const getLineNumber = (index) => {
        let charCount = 0;
        for (let i = 0; i < lines.length; i++) {
            charCount += lines[i].length + 1; // +1 for newline
            if (charCount > index) return i;
        }
        return lines.length - 1;
    };

    // Create a map of opening tags with their line numbers
    const openTagsMap = new Map();
    for (const match of openTags) {
        const lineNum = getLineNumber(match.index);
        openTagsMap.set(match.index, { line: lineNum, id: match[1] });
    }

    // Check if else tags are on the same line as their opening tag
    for (const match of elseTags) {
        // Find the closest opening tag before this else tag
        let closestOpenTag = null;
        let maxIndex = -1;

        for (const [index, tag] of openTagsMap.entries()) {
            if (index < match.index && index > maxIndex) {
                closestOpenTag = tag;
                maxIndex = index;
            }
        }

        if (closestOpenTag && getLineNumber(match.index) !== closestOpenTag.line) {
            return false; // Else tag on different line than opening tag
        }
    }

    // Check if closing tags are on the same line as their opening tag
    for (const match of closeTags) {
        // Find the closest opening tag before this closing tag
        let closestOpenTag = null;
        let maxIndex = -1;

        for (const [index, tag] of openTagsMap.entries()) {
            if (index < match.index && index > maxIndex) {
                closestOpenTag = tag;
                maxIndex = index;
            }
        }

        if (closestOpenTag && getLineNumber(match.index) !== closestOpenTag.line) {
            return false; // Closing tag on different line than opening tag
        }
    }

    // Now do the full validation for proper nesting and matching tags
    const tagRegex = /{{ (conditions\.(\w{4})|else|\/) }}/g;
    const allTags = [...template.matchAll(tagRegex)];
    const stack = [];

    // Track if we're inside a conditional block to detect nesting
    let insideConditional = false;

    for (const match of allTags) {
        const [, tagType] = match;

        if (tagType.startsWith('conditions.')) {
            // Opening tag
            if (insideConditional) {
                return false; // Nested if tags are not allowed
            }
            insideConditional = true;
            stack.push({ type: 'conditions', hasElse: false });
        } else if (tagType === 'else') {
            // Else tag
            if (stack.length === 0) {
                return false; // Else without matching if
            }

            const lastTag = stack[stack.length - 1];
            if (lastTag.hasElse) {
                return false; // Multiple else tags for the same if
            }

            lastTag.hasElse = true;
        } else if (tagType === '/') {
            // Closing tag
            if (stack.length === 0) {
                return false; // Closing tag without matching opening tag
            }

            insideConditional = false;
            stack.pop();
        }
    }

    // If the stack is empty, all tags are properly matched
    return stack.length === 0;
}

/**
 * Run tests for the template syntax validator
 * Command: node /path/to/test.js
 * @returns {Object} - Test results with counts of passed and failed tests
 */
export function test() {
    const tests = [
        // Valid examples
        { name: 'Condition', template: '{{ conditions.x34y }}{{ / }}', expected: true },
        {
            name: 'Condition/else',
            template: '{{ conditions.x35y }}{{ else }}{{ / }}',
            expected: true
        },
        {
            name: 'Condition with content',
            template: '{{ conditions.x34y }}a{{ / }}',
            expected: true
        },
        {
            name: 'Condition/else with content',
            template: '{{ conditions.x35y }}a{{ else }}{{ / }}',
            expected: true
        },
        {
            name: 'Condition/else with true content',
            template: '{{ conditions.x35y }}a{{ else }}{{ / }}',
            expected: true
        },
        {
            name: 'Condition/else with false content',
            template: '{{ conditions.x35y }}{{ else }}a{{ / }}',
            expected: true
        },
        {
            name: 'Condition/else with both content',
            template: '{{ conditions.x35y }}a{{ else }}b{{ / }}',
            expected: true
        },
        {
            name: 'Multiple on same line',
            template:
                '{{ conditions.x36y }}a{{ / }}{{ conditions.x37y }}b{{ / }}{{ conditions.x38y }}c{{ else }}d{{ / }}{{ conditions.x39y }}e{{ else }}f{{ / }}',
            expected: true
        },
        {
            name: 'With nested sources',
            template:
                '{{ conditions.x36y }}{{ sources.x38y }}{{ / }}{{ conditions.x37y }}{{ sources.x38y }}{{ else }}{{ sources.x39y }}{{ / }}',
            expected: true
        },

        // Invalid examples
        { name: 'Condition missing end tag', template: '{{ conditions.x35y }}', expected: false },
        {
            name: 'Condition/else missing end tag',
            template: '{{ conditions.x35y }}{{ else }}',
            expected: false
        },
        { name: 'Condition missing open tag', template: '{{ / }}', expected: false },
        {
            name: 'Condition/else missing open tag',
            template: '{{ else }}{{ / }}',
            expected: false
        },
        {
            name: 'Multiple else tags',
            template:
                '{{ conditions.x35y }}a{{ else }}b{{ else }}c{{ / }}{{ conditions.x35y }}{{ else }}{{ else }}{{ / }}',
            expected: false
        },
        {
            name: 'Nested condition',
            template: '{{ conditions.x35y }}{{ conditions.x35y }}{{ / }}{{ / }}',
            expected: false
        },
        {
            name: 'Nested condition/else',
            template:
                '{{ conditions.x35y }}{{ conditions.x35y }}{{ else }}{{ / }}{{ else }}{{ / }}',
            expected: false
        },
        {
            name: 'Tags on different lines',
            template: '{{ conditions.x35y }}a\n{{ else }}b{{ / }}',
            expected: false
        },
        {
            name: 'Else and end on different lines',
            template: '{{ conditions.x35y }}a{{ else }}b\n{{ / }}',
            expected: false
        }
    ];

    const results = { passed: 0, failed: 0, details: [] };

    tests.forEach((test) => {
        const result = validateTemplateSyntax(test.template);
        const passed = result === test.expected;

        if (passed) {
            results.passed++;
        } else {
            results.failed++;
        }

        results.details.push({
            name: test.name,
            passed,
            expected: test.expected,
            actual: result
        });

        console.log(
            `${passed ? 'PASSED' : 'FAILED'} - ${test.name} (expected: ${test.expected}, got: ${result})`
        );
    });

    console.log(`\nSummary: ${results.passed} passed, ${results.failed} failed`);
    return results;
}

// enable when testing with terminal cmd `node file/path.js`
// if (import.meta.url === `file://${process.argv[1]}`) {
//     test();
// }
