import { ColumnNode, ColumnUpdateNode, InsertQueryNode, OnConflictNode, OperationNodeTransformer, ValueListNode, ValueNode, ValuesNode, } from "kysely";
export function JSONColumnPlugin(jsonColumns) {
    // Build a flat list of all JSON column names for the result transformer
    const jsonColumnNames = Object.keys(jsonColumns).flatMap((table) => Object.keys(jsonColumns[table]));
    return {
        transformResult: async (args) => {
            for (const row of args.result.rows) {
                for (const col of jsonColumnNames) {
                    const val = row[col];
                    // Only parse when it's a string that looks like an object/array JSON.
                    if (typeof val === "string") {
                        const trimmed = val.trim();
                        if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
                            try {
                                row[col] = JSON.parse(val);
                            }
                            catch {
                                // leave as-is if parsing fails
                            }
                        }
                    }
                }
            }
            return args.result;
        },
        transformQuery(args) {
            if (args.node.kind === "InsertQueryNode") {
                const tableNode = args.node.into;
                const table = tableNode && tableNode.kind === "TableNode"
                    ? tableNode.table.identifier.name
                    : undefined;
                const columns = args.node.columns;
                const transformer = new SerializeJsonbTransformer(jsonColumns, table, columns);
                return transformer.transformNode(args.node);
            }
            if (args.node.kind === "UpdateQueryNode") {
                const tableNode = args.node.table;
                let table = undefined;
                if (tableNode && tableNode.kind === "TableNode") {
                    // @ts-expect-error - kysely type narrowing
                    table = tableNode.table.identifier.name;
                }
                const transformer = new SerializeJsonbTransformer(jsonColumns, table, undefined);
                return transformer.transformNode(args.node);
            }
            return args.node;
        },
    };
}
class SerializeJsonbTransformer extends OperationNodeTransformer {
    jsonbColumns;
    table;
    columns;
    constructor(jsonbColumns, table, columns) {
        super();
        this.jsonbColumns = jsonbColumns;
        this.table = table;
        this.columns = columns;
    }
    getJsonColumnConfig(columnName) {
        if (!this.table || !columnName)
            return undefined;
        return this.jsonbColumns[this.table]?.[columnName];
    }
    isJsonbColumn(columnName) {
        return this.getJsonColumnConfig(columnName) !== undefined;
    }
    isSqlExpression(node) {
        // Check if the node is a SQL expression that shouldn't be wrapped
        // Common SQL expression node types:
        // - RawNode: Created by sql`` template literals
        // - FunctionNode: SQL functions like json_set()
        // - BinaryOperationNode: SQL operations
        // - etc.
        // ValueNode is the only type we want to wrap in json()
        return node && node.kind !== "ValueNode";
    }
    transformOnConflict(node) {
        if (!node.updates) {
            return node;
        }
        // Map the updates array directly
        const newUpdates = node.updates.map((updateItem) => {
            // Check if it's a ColumnUpdateNode first
            if (updateItem.kind === "ColumnUpdateNode") {
                // @ts-expect-error - kysely type narrowing
                const columnName = updateItem.column.column.name;
                if (this.isJsonbColumn(columnName)) {
                    const valueNode = updateItem.value;
                    // Check if the value is a SQL expression - if so, don't wrap it
                    if (this.isSqlExpression(valueNode)) {
                        return updateItem;
                    }
                    // Check if the value part is specifically a ValueNode
                    if (valueNode.kind === "ValueNode") {
                        // If it is, serialize it
                        return {
                            ...updateItem,
                            value: this.serializeValue(
                            // @ts-expect-error - kysely type narrowing
                            valueNode, this.getJsonColumnConfig(columnName)),
                        };
                    }
                }
            }
            // Return unchanged if not a ColumnUpdateNode or not a JSONB column or value is not ValueNode
            return updateItem;
        });
        // Return a new node with the transformed updates
        return {
            ...node,
            updates: newUpdates,
        };
    }
    transformNode(node) {
        if (!node) {
            return node;
        }
        return super.transformNode(node);
    }
    // Serialize JSONB values in updates for ColumnUpdateNodes
    transformColumnUpdate(node) {
        // @ts-expect-error - kysely type narrowing
        const columnName = node.column.column.name;
        if (this.isJsonbColumn(columnName)) {
            // Check if the value is a SQL expression (RawNode, etc.) - if so, don't wrap it
            if (this.isSqlExpression(node.value)) {
                return node;
            }
            return {
                ...node,
                value: this.serializeValue(
                // @ts-expect-error - kysely type narrowing
                node.value, this.getJsonColumnConfig(columnName)),
            };
        }
        return super.transformColumnUpdate(node);
    }
    // Support .set("col", value) syntax (SetOperationNode)
    transformSetOperation(node) {
        // node.column.column.name is the column name for SetOperationNode
        const columnName = node.column?.column?.name;
        if (columnName && this.isJsonbColumn(columnName)) {
            // Check if the value is a SQL expression - if so, don't wrap it
            if (this.isSqlExpression(node.value)) {
                return node;
            }
            return {
                ...node,
                value: this.serializeValue(node.value, this.getJsonColumnConfig(columnName)),
            };
        }
        return super.transformSetOperation(node);
    }
    transformValues(node) {
        const newValues = node.values.map((row) => {
            // Handle literal JS values row
            if (row.kind === "PrimitiveValueListNode") {
                const newValueNodes = row.values.map((val, idx) => {
                    const colNode = this.columns?.[idx];
                    const valNode = { kind: "ValueNode", value: val };
                    if (colNode?.kind === "ColumnNode" &&
                        this.isJsonbColumn(colNode.column.name)) {
                        const config = this.getJsonColumnConfig(colNode.column.name);
                        return this.serializeValue(valNode, config);
                    }
                    return valNode;
                });
                return {
                    kind: "ValueListNode",
                    values: newValueNodes,
                };
            }
            // Handle AST rows
            if (row.kind === "ValueListNode") {
                const newValuesList = row.values.map((valNode, idx) => {
                    const colNode = this.columns?.[idx];
                    if (colNode?.kind === "ColumnNode" &&
                        this.isJsonbColumn(colNode.column.name)) {
                        const config = this.getJsonColumnConfig(colNode.column.name);
                        return this.serializeValue(valNode, config);
                    }
                    return valNode;
                });
                return { ...row, values: newValuesList };
            }
            return row;
        });
        return { ...node, values: newValues };
    }
    serializeValue(node, config) {
        const val = node.value;
        if (val instanceof ArrayBuffer || ArrayBuffer.isView(val) || val === null) {
            return node;
        }
        // Check if this column only accepts objects and we have a string
        // that might be pre-serialized JSON
        if (config?.type === "object" && typeof val === "string") {
            // For object-only columns, assume string values are pre-serialized JSON
            // to avoid double serialization
            return {
                kind: "FunctionNode",
                func: "json",
                arguments: [{ kind: "ValueNode", value: val }],
            };
        }
        // For all other cases, stringify the value
        const jsonText = JSON.stringify(val);
        return {
            kind: "FunctionNode",
            func: "json",
            arguments: [{ kind: "ValueNode", value: jsonText }],
        };
    }
}
//# sourceMappingURL=json-column-plugin.js.map