var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { AdminForthDataTypes, logger, } from 'adminforth';
import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone.js';
import utc from 'dayjs/plugin/utc.js';
import YAML from 'yaml';
dayjs.extend(utc);
dayjs.extend(timezone);
const DEFAULT_USER_TIME_ZONE = 'UTC';
function hasRegisteredApiToolHandler(schema) {
    return typeof schema.handler === 'function';
}
function getInputString(inputs, key) {
    const value = inputs === null || inputs === void 0 ? void 0 : inputs[key];
    return typeof value === 'string' && value ? value : undefined;
}
function isHiddenResourceCall(hiddenResourceIds, inputs) {
    const resourceId = getInputString(inputs, 'resourceId');
    return resourceId ? hiddenResourceIds.has(resourceId) : false;
}
function getInputArrayLength(inputs, key) {
    const value = inputs === null || inputs === void 0 ? void 0 : inputs[key];
    return Array.isArray(value) ? value.length : undefined;
}
function resourceLabel(adminforth, inputs) {
    var _a, _b;
    const resourceId = getInputString(inputs, 'resourceId');
    const resource = adminforth.config.resources.find((res) => res.resourceId === resourceId);
    return (_b = (_a = resource === null || resource === void 0 ? void 0 : resource.label) !== null && _a !== void 0 ? _a : resourceId) !== null && _b !== void 0 ? _b : 'resource';
}
function getDataPrefix(inputs) {
    const offset = typeof (inputs === null || inputs === void 0 ? void 0 : inputs.offset) === 'number' ? inputs.offset : undefined;
    const limit = typeof (inputs === null || inputs === void 0 ? void 0 : inputs.limit) === 'number' ? inputs.limit : undefined;
    if (offset !== undefined && limit !== undefined) {
        return `${offset}-${offset + limit} `;
    }
    return limit === undefined ? '' : `${limit} `;
}
function actionText(inputs) {
    const actionId = getInputString(inputs, 'actionId');
    return actionId ? ` action ${actionId}` : ' action';
}
const TOOL_OVERRIDES = {
    get_resource: {
        wipe_frontend_specific_data: [
            'resource.columns[].filterOptions',
            'resource.columns[].components',
            'resource.options.actions[].customComponent',
            'resource.options.pageInjections',
        ],
        format_tool: ({ resourceLabel }) => `Get ${resourceLabel} resource`,
    },
    get_resource_data: {
        format_tool: ({ inputs, resourceLabel }) => (`Get ${getDataPrefix(inputs)}${resourceLabel}`),
        post_process_response: (_a) => __awaiter(void 0, [_a], void 0, function* ({ adminforth, output, inputs, userTimeZone }) {
            var _b;
            if (hasToolError(output)) {
                return output;
            }
            const dateTimeColumnNames = getDateTimeColumnNames(adminforth, inputs);
            if (dateTimeColumnNames.length === 0) {
                return output;
            }
            if (!hasGetResourceDataRows(output)) {
                logger.warn(`Skipping datetime formatting for get_resource_data because response.data is not an array for resource ${(_b = getInputString(inputs, 'resourceId')) !== null && _b !== void 0 ? _b : 'unknown'}`);
                return output;
            }
            const localizedTimeZone = userTimeZone !== null && userTimeZone !== void 0 ? userTimeZone : DEFAULT_USER_TIME_ZONE;
            formatDateTimeColumns(output.data, dateTimeColumnNames, localizedTimeZone);
            return output;
        }),
    },
    aggregate: {
        format_tool: ({ resourceLabel }) => `Aggregate ${resourceLabel}`,
    },
    start_custom_action: {
        format_tool: ({ inputs, resourceLabel }) => `Run ${resourceLabel}${actionText(inputs)}`,
    },
    start_custom_bulk_action: {
        format_tool: ({ inputs, resourceLabel }) => {
            const recordCount = getInputArrayLength(inputs, 'recordIds');
            const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
            return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
        },
    },
    start_bulk_action: {
        format_tool: ({ inputs, resourceLabel }) => {
            const recordCount = getInputArrayLength(inputs, 'recordIds');
            const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
            return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
        },
    },
    create_record: {
        format_tool: ({ resourceLabel }) => `Create ${resourceLabel}`,
    },
    update_record: {
        format_tool: ({ resourceLabel }) => `Update ${resourceLabel}`,
    },
    delete_record: {
        format_tool: ({ resourceLabel }) => `Delete ${resourceLabel}`,
    },
};
function sanitizeForYaml(value) {
    const traversalStack = [];
    const serialized = JSON.stringify(value, function (_key, nestedValue) {
        if (typeof nestedValue === 'function' || typeof nestedValue === 'symbol' || nestedValue === undefined) {
            return undefined;
        }
        if (typeof nestedValue === 'bigint') {
            return nestedValue.toString();
        }
        if (typeof nestedValue !== 'object' || nestedValue === null) {
            return nestedValue;
        }
        if (nestedValue instanceof Map) {
            return Object.fromEntries(nestedValue);
        }
        if (nestedValue instanceof Set) {
            return Array.from(nestedValue.values());
        }
        while (traversalStack.length > 0 && traversalStack[traversalStack.length - 1] !== this) {
            traversalStack.pop();
        }
        if (traversalStack.includes(nestedValue)) {
            return undefined;
        }
        traversalStack.push(nestedValue);
        return nestedValue;
    });
    if (serialized === undefined) {
        return null;
    }
    return JSON.parse(serialized);
}
function wipePath(target, pathParts) {
    if (!target || typeof target !== 'object' || pathParts.length === 0) {
        return;
    }
    const [currentPart, ...rest] = pathParts;
    const isArrayTraversal = currentPart.endsWith('[]');
    const key = isArrayTraversal ? currentPart.slice(0, -2) : currentPart;
    const targetRecord = target;
    if (!(key in targetRecord)) {
        return;
    }
    if (rest.length === 0) {
        delete targetRecord[key];
        return;
    }
    const nextValue = targetRecord[key];
    if (isArrayTraversal) {
        if (!Array.isArray(nextValue)) {
            return;
        }
        for (const item of nextValue) {
            wipePath(item, rest);
        }
        return;
    }
    wipePath(nextValue, rest);
}
function hasToolError(output) {
    return typeof output === 'object' && output !== null && 'error' in output;
}
function hasGetResourceDataRows(output) {
    if (typeof output !== 'object' || output === null || !('data' in output)) {
        return false;
    }
    return Array.isArray(output.data);
}
function getDateTimeColumnNames(adminforth, inputs) {
    const resourceId = getInputString(inputs, 'resourceId');
    const resource = adminforth.config.resources.find((res) => res.resourceId === resourceId);
    if (!resource) {
        return [];
    }
    return resource.dataSourceColumns
        .filter((column) => column.type === AdminForthDataTypes.DATETIME)
        .map((column) => column.name);
}
function formatGmtOffset(offsetMinutes) {
    const sign = offsetMinutes >= 0 ? '+' : '-';
    const absoluteOffsetMinutes = Math.abs(offsetMinutes);
    const hours = Math.floor(absoluteOffsetMinutes / 60);
    const minutes = absoluteOffsetMinutes % 60;
    if (minutes === 0) {
        return `GMT${sign}${hours}`;
    }
    return `GMT${sign}${hours}:${String(minutes).padStart(2, '0')}`;
}
function formatDateTimeValue(value, userTimeZone) {
    const localizedValue = dayjs.utc(value).tz(userTimeZone);
    return `${localizedValue.format('DD MMM YYYY, HH:mm:ss.SSS')} (${formatGmtOffset(localizedValue.utcOffset())})`;
}
function formatDateTimeColumns(rows, dateTimeColumnNames, userTimeZone) {
    for (const row of rows) {
        for (const columnName of dateTimeColumnNames) {
            const value = row[columnName];
            if (typeof value === 'string' && value) {
                row[columnName] = formatDateTimeValue(value, userTimeZone);
            }
        }
    }
}
function applyToolOverride(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a;
        const { adminforth, adminUser, inputs, output, toolName, userTimeZone } = params;
        const sanitizedOutput = sanitizeForYaml(output);
        const override = TOOL_OVERRIDES[toolName];
        if (!override) {
            return sanitizedOutput;
        }
        for (const path of (_a = override.wipe_frontend_specific_data) !== null && _a !== void 0 ? _a : []) {
            wipePath(sanitizedOutput, path.split('.'));
        }
        return override.post_process_response
            ? sanitizeForYaml(yield override.post_process_response({
                adminforth,
                adminUser,
                output: sanitizedOutput,
                inputs,
                userTimeZone,
            }))
            : sanitizedOutput;
    });
}
function endpointPathToToolName(path) {
    return path
        .replace(/^\/+/, '')
        .replace(/[^a-zA-Z0-9_]+/g, '_')
        .replace(/^_+|_+$/g, '');
}
function stripAdminApiPrefix(path, adminforth) {
    const configuredBaseUrl = adminforth.config.baseUrl || '';
    const normalizedBaseUrl = configuredBaseUrl.endsWith('/')
        ? configuredBaseUrl.slice(0, -1)
        : configuredBaseUrl;
    const apiPrefix = `${normalizedBaseUrl}/adminapi/v1`;
    if (path.startsWith(apiPrefix)) {
        const strippedPath = path.slice(apiPrefix.length);
        return strippedPath.startsWith('/') ? strippedPath : `/${strippedPath}`;
    }
    return path;
}
function openApiSchemaPathToToolName(path, adminforth) {
    return endpointPathToToolName(stripAdminApiPrefix(path, adminforth));
}
function formatLogNameList(names) {
    return names.length ? names.join(', ') : '(none)';
}
export function formatApiBasedToolCall(params) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b;
        return yield ((_b = (_a = TOOL_OVERRIDES[params.toolName]) === null || _a === void 0 ? void 0 : _a.format_tool) === null || _b === void 0 ? void 0 : _b.call(_a, {
            adminforth: params.adminforth,
            adminUser: params.adminUser,
            inputs: params.inputs,
            resourceLabel: resourceLabel(params.adminforth, params.inputs),
            userTimeZone: params.userTimeZone,
        }));
    });
}
function normalizeDateTimeInputsToUtc(body, adminforth, userTimeZone) {
    if (!userTimeZone || typeof body.resourceId !== 'string') {
        return body;
    }
    const resource = adminforth.config.resources.find((res) => res.resourceId === body.resourceId);
    if (!resource) {
        return body;
    }
    const columnsByName = new Map(resource.dataSourceColumns.map((column) => [column.name, column]));
    const normalizeColumnValue = (value, columnType) => {
        if (Array.isArray(value)) {
            return value.map((item) => normalizeColumnValue(item, columnType));
        }
        if (typeof value !== 'string' || value === '') {
            return value;
        }
        if (columnType === AdminForthDataTypes.DATETIME) {
            return dayjs.tz(value, userTimeZone).utc().toISOString();
        }
        const userDate = dayjs().tz(userTimeZone).format('YYYY-MM-DD');
        return dayjs.tz(`${userDate}T${value}`, userTimeZone).utc().format('HH:mm:ss');
    };
    const normalizeValue = (value, key) => {
        const column = key ? columnsByName.get(key) : undefined;
        if ((column === null || column === void 0 ? void 0 : column.type) === AdminForthDataTypes.DATETIME || (column === null || column === void 0 ? void 0 : column.type) === AdminForthDataTypes.TIME) {
            return normalizeColumnValue(value, column.type);
        }
        if (Array.isArray(value)) {
            return value.map((item) => normalizeValue(item));
        }
        if (!value || typeof value !== 'object') {
            return value;
        }
        const record = value;
        const filterColumn = typeof record.field === 'string' ? columnsByName.get(record.field) : undefined;
        if ('value' in record &&
            ((filterColumn === null || filterColumn === void 0 ? void 0 : filterColumn.type) === AdminForthDataTypes.DATETIME || (filterColumn === null || filterColumn === void 0 ? void 0 : filterColumn.type) === AdminForthDataTypes.TIME)) {
            return Object.assign(Object.assign({}, record), { value: normalizeColumnValue(record.value, filterColumn.type) });
        }
        return Object.fromEntries(Object.entries(record).map(([nestedKey, nestedValue]) => [
            nestedKey,
            normalizeValue(nestedValue, nestedKey),
        ]));
    };
    return normalizeValue(body);
}
const METHODS_WITHOUT_REQUEST_BODY = new Set(['GET', 'HEAD']);
function createDirectToolResponse() {
    const headers = [];
    return {
        headers,
        status: 200,
        setHeader(name, value) {
            headers.push([name, value]);
        },
        setStatus(code, message) {
            this.status = code;
            this.message = message;
        },
        blobStream() {
            throw new Error('blobStream is not available for API-based agent tools');
        },
    };
}
function validationErrorResponse(error, details) {
    return {
        error,
        details,
    };
}
function callOpenApiSchema(params) {
    return __awaiter(this, void 0, void 0, function* () {
        const { adminforth, adminUser, abortSignal, inputs, schema, toolName, userTimeZone } = params;
        const method = schema.method.toUpperCase();
        const normalizedInputs = normalizeDateTimeInputsToUtc((inputs !== null && inputs !== void 0 ? inputs : {}), adminforth, userTimeZone);
        const hasRequestBody = !METHODS_WITHOUT_REQUEST_BODY.has(method);
        const body = hasRequestBody ? normalizedInputs : {};
        const query = hasRequestBody ? {} : normalizedInputs;
        const requestValidation = adminforth.openApi.validateRequestSchema(schema, body);
        if (!requestValidation.valid) {
            return validationErrorResponse('REQUEST_VALIDATION_FAILED', requestValidation.errors);
        }
        const response = createDirectToolResponse();
        logger.info(`Calling OpenAPI tool "${toolName}" with direct handler`);
        const tr = (msg, category, trParams, pluralizationNumber) => adminforth.tr(msg, category, undefined, trParams, pluralizationNumber);
        const output = yield schema.handler({
            body,
            query,
            headers: {},
            cookies: [],
            adminUser,
            response,
            requestUrl: schema.path,
            abortSignal: abortSignal !== null && abortSignal !== void 0 ? abortSignal : new AbortController().signal,
            _raw_express_req: undefined,
            _raw_express_res: undefined,
            tr,
        });
        if (response.message) {
            return response.status >= 400
                ? {
                    error: 'HANDLER_ERROR',
                    status: response.status,
                    response: response.message,
                }
                : response.message;
        }
        if (output === null) {
            return { status: response.status };
        }
        const responseValidation = adminforth.openApi.validateResponseSchema(schema, output);
        if (!responseValidation.valid) {
            return validationErrorResponse('RESPONSE_VALIDATION_FAILED', responseValidation.errors);
        }
        return response.status >= 400
            ? {
                error: 'HANDLER_ERROR',
                status: response.status,
                response: output,
            }
            : output;
    });
}
export function prepareApiBasedTools(adminforth, hiddenResourceIds = []) {
    const apiBasedTools = {};
    const openApiSchemas = adminforth.openApi.registeredSchemas;
    const openApiSchemasByToolName = new Map();
    const hiddenResourceIdSet = new Set(hiddenResourceIds);
    for (const schema of openApiSchemas) {
        const toolName = openApiSchemaPathToToolName(schema.path, adminforth);
        if (hasRegisteredApiToolHandler(schema)) {
            openApiSchemasByToolName.set(toolName, schema);
        }
    }
    logger.info(`AdminForth Agent OpenAPI APIs: ${formatLogNameList(adminforth.openApi.registeredSchemas.map((schema) => openApiSchemaPathToToolName(schema.path, adminforth)))}`);
    logger.info(`AdminForth Agent OpenAPI tools connected: ${formatLogNameList([...openApiSchemasByToolName.keys()])}`);
    for (const [toolName, schema] of openApiSchemasByToolName.entries()) {
        apiBasedTools[toolName] = {
            description: schema.description,
            input_schema: schema.request_schema,
            output_schema: schema.response_schema,
            call: (...args_1) => __awaiter(this, [...args_1], void 0, function* ({ adminUser, adminuser, abortSignal, inputs, userTimeZone } = {}) {
                if (isHiddenResourceCall(hiddenResourceIdSet, inputs)) {
                    return YAML.stringify({
                        error: 'RESOURCE_NOT_AVAILABLE',
                        message: 'This resource is not available to the agent.',
                    });
                }
                const output = yield callOpenApiSchema({
                    adminforth,
                    adminUser: adminUser !== null && adminUser !== void 0 ? adminUser : adminuser,
                    abortSignal,
                    schema,
                    toolName,
                    inputs,
                    userTimeZone,
                });
                const processedOutput = yield applyToolOverride({
                    adminforth,
                    adminUser: adminUser !== null && adminUser !== void 0 ? adminUser : adminuser,
                    inputs,
                    output,
                    toolName,
                    userTimeZone,
                });
                return YAML.stringify(processedOutput);
            }),
        };
    }
    return apiBasedTools;
}
export function serializeApiBasedTool(tool) {
    if (!tool) {
        return null;
    }
    return {
        description: tool.description,
        input_schema: tool.input_schema,
        output_schema: tool.output_schema,
        call: '[Function]',
    };
}
