import { Comparators, Comparison, Operation, Operators, StructuredQuery, } from "@langchain/core/structured_query";
/**
 * Utility class used to duplicate parameters for a proxy object,
 * specifically designed to work with `SupabaseFilter` objects. It
 * contains methods to handle different types of operations such as "or",
 * "filter", "in", "contains", "textSearch", "match", "not", and default
 * operations.
 */
export class ProxyParamsDuplicator {
    constructor() {
        Object.defineProperty(this, "duplicationAllowedOps", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: [
                "eq",
                "neq",
                "lt",
                "lte",
                "gt",
                "gte",
                "like",
                "ilike",
                "or",
                "in",
                "contains",
                "match",
                "not",
                "textSearch",
                "filter",
            ]
        });
        Object.defineProperty(this, "values", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: []
        });
    }
    /**
     * Creates a proxy handler for a `SupabaseFilter` object. The handler
     * intercepts get operations and applies specific logic based on the
     * property being accessed.
     * @returns A proxy handler for a `SupabaseFilter` object.
     */
    buildProxyHandler() {
        const proxyHandler = {
            get: (target, prop, receiver) => {
                if (typeof target[prop] === "function") {
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    return (...args) => {
                        if (this.duplicationAllowedOps.includes(String(prop))) {
                            switch (String(prop)) {
                                case "or":
                                    // args[0]: filters, args[1]: { foreignTable }
                                    this.addOrClause(args[0], args[1]);
                                    break;
                                case "filter":
                                    // args[0]: column, args[1]: operator, args[2]: value
                                    this.addFilterClause(args[0], args[1], args[2]);
                                    break;
                                case "in":
                                    // args[0]: column, args[1]: values
                                    this.addInClause(args[0], args[1]);
                                    break;
                                case "contains":
                                    // args[0]: column, args[1]: value
                                    this.addContainsClause(args[0], args[1]);
                                    break;
                                case "textSearch":
                                    // args[0]: column, args[1]: query, args[2]: { config, type }
                                    this.addTextSearchClause(args[0], args[1], args[2]);
                                    break;
                                case "match":
                                    // args[0]: query
                                    this.addMatchClause(args[0]);
                                    break;
                                case "not":
                                    // args[0]: column, args[1]: operator, args[2]: value
                                    this.addNotClause(args[0], args[1], args[2]);
                                    break;
                                default:
                                    // args[0]: column, args[1]: value
                                    this.addDefaultOpClause(prop, args[0], args[1]);
                            }
                            return new Proxy(target, proxyHandler);
                        }
                        else {
                            throw new Error("Filter operation not supported for 'or' mergeFiltersOperator");
                        }
                    };
                }
                else {
                    return Reflect.get(target, prop, receiver);
                }
            },
        };
        return proxyHandler;
    }
    /**
     * Removes type annotations from a value string.
     * @param value The value string to clean.
     * @returns The cleaned value string.
     */
    removeType(value) {
        let cleanedValue = value;
        if (cleanedValue.includes("::float")) {
            cleanedValue = cleanedValue.replace("::float", "");
        }
        if (cleanedValue.includes("::int")) {
            cleanedValue = cleanedValue.replace("::int", "");
        }
        return cleanedValue;
    }
    /**
     * Adds a default operation clause to the values array.
     * @param prop The operation property.
     * @param column The column to apply the operation to.
     * @param value The value for the operation.
     */
    addDefaultOpClause(prop, column, value) {
        this.values.push([this.removeType(column), `${String(prop)}.${value}`]);
    }
    /**
     * Adds an 'or' clause to the values array.
     * @param filters The filters for the 'or' clause.
     * @param foreignTable Optional foreign table for the 'or' clause.
     */
    addOrClause(filters, { foreignTable } = {}) {
        const key = foreignTable ? `${foreignTable}.or` : "or";
        this.values.push([this.removeType(key), `(${filters})`]);
    }
    /**
     * Adds a 'filter' clause to the values array.
     * @param column The column to apply the filter to.
     * @param operator The operator for the filter.
     * @param value The value for the filter.
     */
    addFilterClause(column, operator, value) {
        this.values.push([this.removeType(column), `${operator}.${value}`]);
    }
    /**
     * Adds an 'in' clause to the values array.
     * @param column The column to apply the 'in' clause to.
     * @param values The values for the 'in' clause.
     */
    addInClause(column, values) {
        const cleanedValues = values
            .map((s) => {
            if (typeof s === "string" && /[,()]/.test(s))
                return `"${s}"`;
            else
                return `${s}`;
        })
            .join(",");
        this.values.push([this.removeType(column), `in.(${cleanedValues})`]);
    }
    /**
     * Adds a 'contains' clause to the values array.
     * @param column The column to apply the 'contains' clause to.
     * @param value The value for the 'contains' clause.
     */
    addContainsClause(column, value) {
        if (typeof value === "string") {
            this.values.push([this.removeType(column), `cs.${value}`]);
        }
        else if (Array.isArray(value)) {
            this.values.push([this.removeType(column), `cs.{${value.join(",")}}`]);
        }
        else {
            this.values.push([
                this.removeType(column),
                `cs.${JSON.stringify(value)}`,
            ]);
        }
    }
    /**
     * Adds a 'textSearch' clause to the values array.
     * @param column The column to apply the 'textSearch' clause to.
     * @param query The query for the 'textSearch' clause.
     * @param config Optional configuration for the 'textSearch' clause.
     * @param type Optional type for the 'textSearch' clause.
     */
    addTextSearchClause(column, query, { config, type, } = {}) {
        let typePart = "";
        if (type === "plain") {
            typePart = "pl";
        }
        else if (type === "phrase") {
            typePart = "ph";
        }
        else if (type === "websearch") {
            typePart = "w";
        }
        const configPart = config === undefined ? "" : `(${config})`;
        this.values.push([
            this.removeType(column),
            `${typePart}fts${configPart}.${query}`,
        ]);
    }
    /**
     * Adds a 'not' clause to the values array.
     * @param column The column to apply the 'not' clause to.
     * @param operator The operator for the 'not' clause.
     * @param value The value for the 'not' clause.
     */
    addNotClause(column, operator, value) {
        this.values.push([column, `not.${operator}.${value}`]);
    }
    /**
     * Adds a 'match' clause to the values array.
     * @param query The query for the 'match' clause.
     */
    addMatchClause(query) {
        Object.entries(query).forEach(([column, value]) => {
            this.values.push([column, `eq.${value}`]);
        });
    }
    /**
     * Returns the flattened parameters as a string.
     * @returns The flattened parameters as a string.
     */
    flattenedParams() {
        const mapped = this.values.map(([k, v]) => `${k}.${v}`);
        if (mapped.length === 1)
            return mapped[0];
        return `and(${mapped.join(",")})`;
    }
    /**
     * Gets flattened parameters from a `SupabaseFilter` and a
     * `SupabaseFilterRPCCall`.
     * @param rpc The `SupabaseFilter` object.
     * @param filter The `SupabaseFilterRPCCall` object.
     * @returns The flattened parameters as a string.
     */
    static getFlattenedParams(rpc, filter) {
        const proxiedDuplicator = new ProxyParamsDuplicator();
        const proxiedRpc = new Proxy(rpc, proxiedDuplicator.buildProxyHandler());
        void filter(proxiedRpc);
        return proxiedDuplicator.flattenedParams();
    }
}
/**
 * Converts a `SupabaseMetadata` object into a `StructuredQuery` object.
 * The function creates a new `StructuredQuery` object and uses the
 * `Operation` and `Comparison` classes to build the query.
 */
export function convertObjectFilterToStructuredQuery(objFilter) {
    return new StructuredQuery("", new Operation(Operators.and, Object.entries(objFilter).map(([column, value]) => new Comparison(Comparators.eq, column, value))));
}
