
import { Iterable } from '../../../../../base/common/iterator.js';
import { dirname, joinPath } from '../../../../../base/common/resources.js';
import { splitLinesIncludeSeparators } from '../../../../../base/common/strings.js';
import { URI } from '../../../../../base/common/uri.js';
import { parse } from '../../../../../base/common/yaml.js';
import { Range } from '../../../../../editor/common/core/range.js';
import { Target } from './service/promptsService.js';

class PromptFileParser {
    constructor() {}
    parse(uri, content) {
        const linesWithEOL = splitLinesIncludeSeparators(content);
        if (linesWithEOL.length === 0) {
            return ( new ParsedPromptFile(uri, undefined, undefined));
        }
        let header = undefined;
        let body = undefined;
        let bodyStartLine = 0;
        if (linesWithEOL[0].match(/^---[\s\r\n]*$/)) {
            let headerEndLine = linesWithEOL.findIndex((line, index) => index > 0 && line.match(/^---[\s\r\n]*$/));
            if (headerEndLine === -1) {
                headerEndLine = linesWithEOL.length;
                bodyStartLine = linesWithEOL.length;
            } else {
                bodyStartLine = headerEndLine + 1;
            }
            const range = ( new Range(2, 1, headerEndLine + 1, 1));
            header = ( new PromptHeader(range, uri, linesWithEOL));
        }
        if (bodyStartLine < linesWithEOL.length) {
            const range = ( new Range(bodyStartLine + 1, 1, linesWithEOL.length + 1, 1));
            body = ( new PromptBody(range, linesWithEOL, uri));
        }
        return ( new ParsedPromptFile(uri, header, body));
    }
}
class ParsedPromptFile {
    constructor(uri, header, body) {
        this.uri = uri;
        this.header = header;
        this.body = body;
    }
}
var PromptHeaderAttributes;
(function(PromptHeaderAttributes) {
    PromptHeaderAttributes.name = "name";
    PromptHeaderAttributes.description = "description";
    PromptHeaderAttributes.agent = "agent";
    PromptHeaderAttributes.mode = "mode";
    PromptHeaderAttributes.model = "model";
    PromptHeaderAttributes.applyTo = "applyTo";
    PromptHeaderAttributes.paths = "paths";
    PromptHeaderAttributes.tools = "tools";
    PromptHeaderAttributes.handOffs = "handoffs";
    PromptHeaderAttributes.advancedOptions = "advancedOptions";
    PromptHeaderAttributes.argumentHint = "argument-hint";
    PromptHeaderAttributes.excludeAgent = "excludeAgent";
    PromptHeaderAttributes.target = "target";
    PromptHeaderAttributes.infer = "infer";
    PromptHeaderAttributes.license = "license";
    PromptHeaderAttributes.compatibility = "compatibility";
    PromptHeaderAttributes.metadata = "metadata";
    PromptHeaderAttributes.agents = "agents";
    PromptHeaderAttributes.userInvokable = "user-invokable";
    PromptHeaderAttributes.disableModelInvocation = "disable-model-invocation";
})(PromptHeaderAttributes || (PromptHeaderAttributes = {}));
var GithubPromptHeaderAttributes;
(function(GithubPromptHeaderAttributes) {
    GithubPromptHeaderAttributes.mcpServers = "mcp-servers";
})(GithubPromptHeaderAttributes || (GithubPromptHeaderAttributes = {}));
var ClaudeHeaderAttributes;
(function(ClaudeHeaderAttributes) {
    ClaudeHeaderAttributes.disallowedTools = "disallowedTools";
})(ClaudeHeaderAttributes || (ClaudeHeaderAttributes = {}));
function isTarget(value) {
    return value === Target.VSCode || value === Target.GitHubCopilot || value === Target.Claude || value === Target.Undefined;
}
class PromptHeader {
    constructor(range, uri, linesWithEOL) {
        this.range = range;
        this.uri = uri;
        this.linesWithEOL = linesWithEOL;
    }
    get _parsedHeader() {
        if (this._parsed === undefined) {
            const yamlErrors = [];
            const lines = this.linesWithEOL.slice(this.range.startLineNumber - 1, this.range.endLineNumber - 1).join("");
            const node = parse(lines, yamlErrors);
            const attributes = [];
            const errors = ( yamlErrors.map(err => ({
                message: err.message,
                range: this.asRange(err),
                code: err.code
            })));
            if (node) {
                if (node.type !== "object") {
                    errors.push({
                        message: "Invalid header, expecting <key: value> pairs",
                        range: this.range,
                        code: "INVALID_YAML"
                    });
                } else {
                    for (const property of node.properties) {
                        attributes.push({
                            key: property.key.value,
                            range: this.asRange({
                                start: property.key.start,
                                end: property.value.end
                            }),
                            value: this.asValue(property.value)
                        });
                    }
                }
            }
            this._parsed = {
                node,
                attributes,
                errors
            };
        }
        return this._parsed;
    }
    asRange(
        {
            start,
            end
        }
    ) {
        return ( new Range(
            this.range.startLineNumber + start.line,
            start.character + 1,
            this.range.startLineNumber + end.line,
            end.character + 1
        ));
    }
    asValue(node) {
        switch (node.type) {
        case "string":
            return {
                type: "string",
                value: node.value,
                range: this.asRange(node)
            };
        case "number":
            return {
                type: "number",
                value: node.value,
                range: this.asRange(node)
            };
        case "boolean":
            return {
                type: "boolean",
                value: node.value,
                range: this.asRange(node)
            };
        case "null":
            return {
                type: "null",
                value: node.value,
                range: this.asRange(node)
            };
        case "array":
            return {
                type: "array",
                items: ( node.items.map(item => this.asValue(item))),
                range: this.asRange(node)
            };
        case "object":
            {
                const properties = ( node.properties.map(property => ({
                    key: this.asValue(property.key),
                    value: this.asValue(property.value)
                })));
                return {
                    type: "object",
                    properties,
                    range: this.asRange(node)
                };
            }
        }
    }
    get attributes() {
        return this._parsedHeader.attributes;
    }
    getAttribute(key) {
        return this._parsedHeader.attributes.find(attr => attr.key === key);
    }
    get errors() {
        return this._parsedHeader.errors;
    }
    getStringAttribute(key) {
        const attribute = this._parsedHeader.attributes.find(attr => attr.key === key);
        if (attribute?.value.type === "string") {
            return attribute.value.value;
        }
        return undefined;
    }
    get name() {
        return this.getStringAttribute(PromptHeaderAttributes.name);
    }
    get description() {
        return this.getStringAttribute(PromptHeaderAttributes.description);
    }
    get agent() {
        return this.getStringAttribute(PromptHeaderAttributes.agent) ?? this.getStringAttribute(PromptHeaderAttributes.mode);
    }
    get model() {
        return this.getStringOrStringArrayAttribute(PromptHeaderAttributes.model);
    }
    get applyTo() {
        return this.getStringAttribute(PromptHeaderAttributes.applyTo);
    }
    get paths() {
        return this.getStringOrStringArrayAttribute(PromptHeaderAttributes.paths);
    }
    get argumentHint() {
        return this.getStringAttribute(PromptHeaderAttributes.argumentHint);
    }
    get target() {
        return this.getStringAttribute(PromptHeaderAttributes.target);
    }
    get infer() {
        const attribute = this._parsedHeader.attributes.find(attr => attr.key === PromptHeaderAttributes.infer);
        if (attribute?.value.type === "boolean") {
            return attribute.value.value;
        }
        return undefined;
    }
    get tools() {
        const toolsAttribute = this._parsedHeader.attributes.find(attr => attr.key === PromptHeaderAttributes.tools);
        if (!toolsAttribute) {
            return undefined;
        }
        let value = toolsAttribute.value;
        if (value.type === "string") {
            value = parseCommaSeparatedList(value);
        }
        if (value.type === "array") {
            const tools = [];
            for (const item of value.items) {
                if (item.type === "string" && item.value) {
                    tools.push(item.value);
                }
            }
            return tools;
        }
        return undefined;
    }
    get handOffs() {
        const handoffsAttribute = this._parsedHeader.attributes.find(attr => attr.key === PromptHeaderAttributes.handOffs);
        if (!handoffsAttribute) {
            return undefined;
        }
        if (handoffsAttribute.value.type === "array") {
            const handoffs = [];
            for (const item of handoffsAttribute.value.items) {
                if (item.type === "object") {
                    let agent;
                    let label;
                    let prompt;
                    let send;
                    let showContinueOn;
                    let model;
                    for (const prop of item.properties) {
                        if (prop.key.value === "agent" && prop.value.type === "string") {
                            agent = prop.value.value;
                        } else if (prop.key.value === "label" && prop.value.type === "string") {
                            label = prop.value.value;
                        } else if (prop.key.value === "prompt" && prop.value.type === "string") {
                            prompt = prop.value.value;
                        } else if (prop.key.value === "send" && prop.value.type === "boolean") {
                            send = prop.value.value;
                        } else if (prop.key.value === "showContinueOn" && prop.value.type === "boolean") {
                            showContinueOn = prop.value.value;
                        } else if (prop.key.value === "model" && prop.value.type === "string") {
                            model = prop.value.value;
                        }
                    }
                    if (agent && label && prompt !== undefined) {
                        const handoff = {
                            agent,
                            label,
                            prompt,
                            ...(send !== undefined ? {
                                send
                            } : {}),
                            ...(showContinueOn !== undefined ? {
                                showContinueOn
                            } : {}),
                            ...(model !== undefined ? {
                                model
                            } : {})
                        };
                        handoffs.push(handoff);
                    }
                }
            }
            return handoffs;
        }
        return undefined;
    }
    getStringArrayAttribute(key) {
        const attribute = this._parsedHeader.attributes.find(attr => attr.key === key);
        if (!attribute) {
            return undefined;
        }
        if (attribute.value.type === "array") {
            const result = [];
            for (const item of attribute.value.items) {
                if (item.type === "string" && item.value) {
                    result.push(item.value);
                }
            }
            return result;
        }
        return undefined;
    }
    getStringOrStringArrayAttribute(key) {
        const attribute = this._parsedHeader.attributes.find(attr => attr.key === key);
        if (!attribute) {
            return undefined;
        }
        if (attribute.value.type === "string") {
            return [attribute.value.value];
        }
        if (attribute.value.type === "array") {
            const result = [];
            for (const item of attribute.value.items) {
                if (item.type === "string") {
                    result.push(item.value);
                }
            }
            return result;
        }
        return undefined;
    }
    get agents() {
        return this.getStringArrayAttribute(PromptHeaderAttributes.agents);
    }
    get userInvokable() {
        return this.getBooleanAttribute(PromptHeaderAttributes.userInvokable);
    }
    get disableModelInvocation() {
        return this.getBooleanAttribute(PromptHeaderAttributes.disableModelInvocation);
    }
    getBooleanAttribute(key) {
        const attribute = this._parsedHeader.attributes.find(attr => attr.key === key);
        if (attribute?.value.type === "boolean") {
            return attribute.value.value;
        }
        return undefined;
    }
}
class PromptBody {
    constructor(range, linesWithEOL, uri) {
        this.range = range;
        this.linesWithEOL = linesWithEOL;
        this.uri = uri;
    }
    get fileReferences() {
        return this.getParsedBody().fileReferences;
    }
    get variableReferences() {
        return this.getParsedBody().variableReferences;
    }
    get offset() {
        return this.getParsedBody().bodyOffset;
    }
    getParsedBody() {
        if (this._parsed === undefined) {
            const markdownLinkRanges = [];
            const fileReferences = [];
            const variableReferences = [];
            const bodyOffset = Iterable.reduce(
                Iterable.slice(this.linesWithEOL, 0, this.range.startLineNumber - 1),
                (len, line) => line.length + len,
                0
            );
            for (let i = this.range.startLineNumber - 1, lineStartOffset = bodyOffset; i < this.range.endLineNumber - 1; i++) {
                const line = this.linesWithEOL[i];
                const linkMatch = line.matchAll(/\[(.*?)\]\((.+?)\)/g);
                for (const match of linkMatch) {
                    if (match.index > 0 && line[match.index - 1] === "!") {
                        continue;
                    }
                    const linkEndOffset = match.index + match[0].length - 1;
                    const linkStartOffset = match.index + match[0].length - match[2].length - 1;
                    const range = ( new Range(i + 1, linkStartOffset + 1, i + 1, linkEndOffset + 1));
                    fileReferences.push({
                        content: match[2],
                        range,
                        isMarkdownLink: true
                    });
                    markdownLinkRanges.push(( new Range(i + 1, match.index + 1, i + 1, match.index + match[0].length + 1)));
                }
                const reg = /#file:(?<filePath>[^\s#]+)|#tool:(?<toolName>[\w_\-\.\/]+)/gi;
                const matches = line.matchAll(reg);
                for (const match of matches) {
                    const fullMatch = match[0];
                    const fullRange = ( new Range(i + 1, match.index + 1, i + 1, match.index + fullMatch.length + 1));
                    if (( markdownLinkRanges.some(mdRange => Range.areIntersectingOrTouching(mdRange, fullRange)))) {
                        continue;
                    }
                    const contentMatch = match.groups?.["filePath"] || match.groups?.["toolName"];
                    if (!contentMatch) {
                        continue;
                    }
                    const startOffset = match.index + fullMatch.length - contentMatch.length;
                    const endOffset = match.index + fullMatch.length;
                    const range = ( new Range(i + 1, startOffset + 1, i + 1, endOffset + 1));
                    if (match.groups?.["filePath"]) {
                        fileReferences.push({
                            content: match.groups?.["filePath"],
                            range,
                            isMarkdownLink: false
                        });
                    } else if (match.groups?.["toolName"]) {
                        variableReferences.push({
                            name: match.groups?.["toolName"],
                            range,
                            offset: lineStartOffset + match.index
                        });
                    }
                }
                lineStartOffset += line.length;
            }
            this._parsed = {
                fileReferences: fileReferences.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)),
                variableReferences,
                bodyOffset
            };
        }
        return this._parsed;
    }
    getContent() {
        return this.linesWithEOL.slice(this.range.startLineNumber - 1, this.range.endLineNumber - 1).join("");
    }
    resolveFilePath(path) {
        try {
            if (path.startsWith("/")) {
                return this.uri.with({
                    path
                });
            } else if (path.match(/^[a-zA-Z]+:\//)) {
                return ( URI.parse(path));
            } else {
                const dirName = dirname(this.uri);
                return joinPath(dirName, path);
            }
        } catch {
            return undefined;
        }
    }
}
function parseCommaSeparatedList(stringValue) {
    const result = [];
    const input = stringValue.value;
    const positionOffset = stringValue.range.getStartPosition();
    let pos = 0;
    const isWhitespace = char => char === " " || char === "\t";
    while (pos < input.length) {
        while (pos < input.length && isWhitespace(input[pos])) {
            pos++;
        }
        if (pos >= input.length) {
            break;
        }
        const startPos = pos;
        let value = "";
        let endPos;
        const char = input[pos];
        if (char === "\"" || char === `'`) {
            const quote = char;
            pos++;
            while (pos < input.length && input[pos] !== quote) {
                value += input[pos];
                pos++;
            }
            endPos = pos + 1;
            if (pos < input.length) {
                pos++;
            }
        } else {
            const startPos = pos;
            while (pos < input.length && input[pos] !== ",") {
                value += input[pos];
                pos++;
            }
            value = value.trimEnd();
            endPos = startPos + value.length;
        }
        result.push({
            type: "string",
            value: value,
            range: ( new Range(
                positionOffset.lineNumber,
                positionOffset.column + startPos,
                positionOffset.lineNumber,
                positionOffset.column + endPos
            ))
        });
        while (pos < input.length && isWhitespace(input[pos])) {
            pos++;
        }
        if (pos < input.length && input[pos] === ",") {
            pos++;
        }
    }
    return {
        type: "array",
        items: result,
        range: stringValue.range
    };
}

export { ClaudeHeaderAttributes, GithubPromptHeaderAttributes, ParsedPromptFile, PromptBody, PromptFileParser, PromptHeader, PromptHeaderAttributes, isTarget, parseCommaSeparatedList };
