// *****************************************************************************
// Copyright (C) 2024 EclipseSource GmbH.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
// Partially copied from https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/workbench/contrib/chat/common/chatParserTypes.ts
// Partially copied from https://github.com/microsoft/vscode/blob/a2cab7255c0df424027be05d58e1b7b941f4ea60/src/vs/editor/common/core/offsetRange.ts

import { ResolvedAIVariable, ToolRequest, toolRequestToPromptText } from '@theia/ai-core';
import { ChatRequest } from './chat-model';
import {
    SerializableTextPart,
    SerializableVariablePart,
    SerializableFunctionPart,
    SerializableAgentPart,
    SerializableParsedRequest,
} from './chat-model-serialization';

export const chatVariableLeader = '#';
export const chatAgentLeader = '@';
export const chatFunctionLeader = '~';
export const chatSubcommandLeader = '/';

/**********************
 * CLASSES, INTERFACES AND TYPE GUARDS
 **********************/

export interface OffsetRange {
    readonly start: number;
    readonly endExclusive: number;
}

export interface ParsedChatRequest {
    readonly request: ChatRequest;
    readonly parts: ParsedChatRequestPart[];
    readonly toolRequests: Map<string, ToolRequest>;
    /**
     * Ids of tools referenced in the request that were marked as deferred,
     * e.g. `~?toolId` (chat format) or `~{?toolId}` (prompt format).
     * Deferred tools are not loaded into the model's context upfront and
     * may instead be discovered on-demand via the model provider's built-in
     * tool search mechanism (Anthropic, OpenAI) when supported.
     */
    readonly deferredToolIds?: Set<string>;
    readonly variables: ResolvedAIVariable[];
}

export interface ParsedChatRequestPart {
    readonly kind: string;
    /**
     * The text as represented in the ChatRequest
     */
    readonly text: string;
    /**
     * The text as will be sent to the LLM
     */
    readonly promptText: string;

    readonly range: OffsetRange;
}

export class ParsedChatRequestTextPart implements ParsedChatRequestPart {
    readonly kind = 'text';

    constructor(readonly range: OffsetRange, readonly text: string) { }

    get promptText(): string {
        return this.text;
    }

    toSerializable(): SerializableTextPart {
        return {
            kind: 'text',
            range: this.range,
            text: this.text
        };
    }
}

export class ParsedChatRequestVariablePart implements ParsedChatRequestPart {
    readonly kind = 'var';

    public resolution: ResolvedAIVariable;

    constructor(readonly range: OffsetRange, readonly variableName: string, readonly variableArg: string | undefined) { }

    get text(): string {
        const argPart = this.variableArg ? `:${this.variableArg}` : '';
        return `${chatVariableLeader}${this.variableName}${argPart}`;
    }

    get promptText(): string {
        return this.resolution?.value ?? this.text;
    }

    toSerializable(): SerializableVariablePart {
        return {
            kind: 'var',
            range: this.range,
            variableId: this.resolution?.variable.id ?? 'unresolved variable',
            variableName: this.variableName,
            variableArg: this.variableArg,
            variableValue: this.resolution?.value,
            variableDescription: this.resolution?.variable.description ?? 'unresolved variable'
        };
    }
}

export class ParsedChatRequestFunctionPart implements ParsedChatRequestPart {
    readonly kind = 'function';
    constructor(readonly range: OffsetRange, readonly toolRequest: ToolRequest, readonly deferred: boolean = false) { }

    get text(): string {
        const marker = this.deferred ? '?' : '';
        return `${chatFunctionLeader}${marker}${this.toolRequest.id}`;
    }

    get promptText(): string {
        return toolRequestToPromptText(this.toolRequest);
    }

    toSerializable(): SerializableFunctionPart {
        return {
            kind: 'function',
            range: this.range,
            toolRequestId: this.toolRequest.id,
            deferred: this.deferred ? true : undefined
        };
    }
}

export class ParsedChatRequestAgentPart implements ParsedChatRequestPart {
    readonly kind = 'agent';
    constructor(readonly range: OffsetRange, readonly agentId: string, readonly agentName: string) { }

    get text(): string {
        return `${chatAgentLeader}${this.agentName}`;
    }

    get promptText(): string {
        return '';
    }

    toSerializable(): SerializableAgentPart {
        return {
            kind: 'agent',
            range: this.range,
            agentId: this.agentId,
            agentName: this.agentName
        };
    }
}

export namespace ParsedChatRequest {
    export function toSerializable(parsed: ParsedChatRequest): SerializableParsedRequest {
        return {
            parts: parsed.parts.map(part => {
                if (part instanceof ParsedChatRequestTextPart ||
                    part instanceof ParsedChatRequestVariablePart ||
                    part instanceof ParsedChatRequestFunctionPart ||
                    part instanceof ParsedChatRequestAgentPart) {
                    return part.toSerializable();
                }
                throw new Error(`Unknown part type: ${part.kind}`);
            }),
            toolRequests: Array.from(parsed.toolRequests.keys()).map(toolId => ({
                id: toolId,
                deferred: parsed.deferredToolIds?.has(toolId) ? true : undefined
            })),
            variables: parsed.variables.map(variable => ({
                variableId: variable.variable.id,
                variableName: variable.variable.name,
                variableDescription: variable.variable.description,
                arg: variable.arg,
                value: variable.value
            }))
        };
    }
}
