/* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import { Code, Table, Plus, Trash2 } from 'lucide-react' import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { JsonCodeEditor } from '@/components/json-code-editor' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' type JsonEditorProps = { value: string onChange: (value: string) => void disabled?: boolean keyPlaceholder?: string valuePlaceholder?: string keyLabel?: string valueLabel?: string emptyMessage?: string template?: Record valueType?: 'string' | 'number' | 'any' } type EditorRow = { id: string key: string value: string } function parseJsonRows(json: string): EditorRow[] { try { if (!json.trim()) return [] const parsed = JSON.parse(json) if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { return [] } return Object.entries(parsed).map(([key, val], index) => ({ id: `${Date.now()}-${index}`, key, value: typeof val === 'object' ? JSON.stringify(val) : String(val), })) } catch { return [] } } export function JsonEditor({ value, onChange, disabled = false, keyPlaceholder, valuePlaceholder, keyLabel, valueLabel, emptyMessage, template, valueType = 'string', }: JsonEditorProps) { const { t } = useTranslation() const resolvedEmptyMessage = emptyMessage ?? t('No mappings configured. Click "Add Row" to get started.') const resolvedKeyPlaceholder = keyPlaceholder ?? t('Key') const resolvedValuePlaceholder = valuePlaceholder ?? t('Value') const resolvedKeyLabel = keyLabel ?? t('Key') const resolvedValueLabel = valueLabel ?? t('Value') const [mode, setMode] = useState<'visual' | 'json'>('visual') const [rows, setRows] = useState(() => parseJsonRows(value)) const [jsonValue, setJsonValue] = useState(value) const parseJsonToRows = (json: string) => { setRows(parseJsonRows(json)) } // Parse JSON to rows when value changes externally useEffect(() => { if (value !== jsonValue) { setJsonValue(value) parseJsonToRows(value) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [value]) const convertRowsToJson = (updatedRows: EditorRow[]): string => { if (updatedRows.length === 0) { return '' } const obj: Record = {} updatedRows.forEach((row) => { if (row.key.trim()) { let parsedValue: unknown = row.value.trim() // Try to parse value based on type if (valueType === 'number') { parsedValue = Number(parsedValue) || 0 } else if (valueType === 'any') { // Try to parse as JSON first try { parsedValue = JSON.parse(row.value) } catch { // If not valid JSON, keep as string parsedValue = row.value.trim() } } obj[row.key.trim()] = parsedValue } }) return JSON.stringify(obj, null, 2) } const handleAddRow = () => { const newRow: EditorRow = { id: `${Date.now()}`, key: '', value: '', } const updatedRows = [...rows, newRow] setRows(updatedRows) } const handleDeleteRow = (id: string) => { const updatedRows = rows.filter((row) => row.id !== id) setRows(updatedRows) const json = convertRowsToJson(updatedRows) setJsonValue(json) onChange(json) } const handleRowChange = ( id: string, field: 'key' | 'value', newValue: string ) => { const updatedRows = rows.map((row) => row.id === id ? { ...row, [field]: newValue } : row ) setRows(updatedRows) const json = convertRowsToJson(updatedRows) setJsonValue(json) onChange(json) } const handleJsonChange = (newJson: string) => { setJsonValue(newJson) onChange(newJson) parseJsonToRows(newJson) } const handleFillTemplate = () => { if (!template) return const templateJson = JSON.stringify(template, null, 2) setJsonValue(templateJson) onChange(templateJson) parseJsonToRows(templateJson) } const toggleMode = () => { if (mode === 'visual') { // Switching to JSON mode: sync rows to JSON const json = convertRowsToJson(rows) setJsonValue(json) onChange(json) setMode('json') } else { // Switching to visual mode: sync JSON to rows parseJsonToRows(jsonValue) setMode('visual') } } return (