import { SnippetParser, Variable, Placeholder, Text } from '../../../../editor/contrib/snippet/snippetParser.js';
import { KnownSnippetVariableNames } from '../../../../editor/contrib/snippet/snippetVariables.js';
import { isFalsyOrWhitespace } from '../../../../base/common/strings.js';
import { IdleValue } from '../../../../base/common/async.js';
import { Iterable } from '../../../../base/common/iterator.js';
class SnippetBodyInsights {
    constructor(body) {
        // init with defaults
        this.isBogous = false;
        this.needsClipboard = false;
        this.codeSnippet = body;
        // check snippet...
        const textmateSnippet = new SnippetParser().parse(body, false);
        let placeholders = new Map();
        let placeholderMax = 0;
        for (const placeholder of textmateSnippet.placeholders) {
            placeholderMax = Math.max(placeholderMax, placeholder.index);
        }
        let stack = [...textmateSnippet.children];
        while (stack.length > 0) {
            const marker = stack.shift();
            if (marker instanceof Variable) {
                if (marker.children.length === 0 && !KnownSnippetVariableNames[marker.name]) {
                    // a 'variable' without a default value and not being one of our supported
                    // variables is automatically turned into a placeholder. This is to restore
                    // a bug we had before. So `${foo}` becomes `${N:foo}`
                    const index = placeholders.has(marker.name) ? placeholders.get(marker.name) : ++placeholderMax;
                    placeholders.set(marker.name, index);
                    const synthetic = new Placeholder(index).appendChild(new Text(marker.name));
                    textmateSnippet.replace(marker, [synthetic]);
                    this.isBogous = true;
                }
                if (marker.name === 'CLIPBOARD') {
                    this.needsClipboard = true;
                }
            }
            else {
                // recurse
                stack.push(...marker.children);
            }
        }
        if (this.isBogous) {
            this.codeSnippet = textmateSnippet.toTextmateString();
        }
    }
}
export class Snippet {
    constructor(scopes, name, prefix, description, body, source, snippetSource, snippetIdentifier) {
        this.scopes = scopes;
        this.name = name;
        this.prefix = prefix;
        this.description = description;
        this.body = body;
        this.source = source;
        this.snippetSource = snippetSource;
        this.snippetIdentifier = snippetIdentifier;
        this.prefixLow = prefix.toLowerCase();
        this._bodyInsights = new IdleValue(() => new SnippetBodyInsights(this.body));
    }
    get codeSnippet() {
        return this._bodyInsights.value.codeSnippet;
    }
    get isBogous() {
        return this._bodyInsights.value.isBogous;
    }
    get needsClipboard() {
        return this._bodyInsights.value.needsClipboard;
    }
    static compare(a, b) {
        if (a.snippetSource < b.snippetSource) {
            return -1;
        }
        else if (a.snippetSource > b.snippetSource) {
            return 1;
        }
        else if (a.name > b.name) {
            return 1;
        }
        else if (a.name < b.name) {
            return -1;
        }
        else {
            return 0;
        }
    }
}
export function parseSnippet(name, snippet, defaultScopes, source, snippetSource, snippetIdentifier) {
    let { prefix, body, description } = snippet;
    if (!prefix) {
        prefix = '';
    }
    if (Array.isArray(body)) {
        body = body.join('\n');
    }
    if (typeof body !== 'string') {
        return [];
    }
    if (Array.isArray(description)) {
        description = description.join('\n');
    }
    let scopes;
    if (defaultScopes) {
        scopes = defaultScopes;
    }
    else if (typeof snippet.scope === 'string') {
        scopes = snippet.scope.split(',').map(s => s.trim()).filter(s => !isFalsyOrWhitespace(s));
    }
    else {
        scopes = [];
    }
    const bucket = [];
    for (const _prefix of Array.isArray(prefix) ? prefix : Iterable.single(prefix)) {
        bucket.push(new Snippet(scopes, name, _prefix, description, body, source, snippetSource, snippetIdentifier));
    }
    return bucket;
}
export function snippetScopeSelect(data, selector, bucket) {
    // for `my.code-snippets` files we need to look at each snippet
    for (const snippet of data) {
        const len = snippet.scopes.length;
        if (len === 0) {
            // always accept
            bucket.push(snippet);
        }
        else {
            for (let i = 0; i < len; i++) {
                // match
                if (snippet.scopes[i] === selector) {
                    bucket.push(snippet);
                    break; // match only once!
                }
            }
        }
    }
    let idx = selector.lastIndexOf('.');
    if (idx >= 0) {
        snippetScopeSelect(data, selector.substring(0, idx), bucket);
    }
}
