import { importer } from '@dbml/core';
import { exportBaseSQL } from '@/lib/data/sql-export/export-sql-script';
import type { Diagram } from '@/lib/domain/diagram';
import { DatabaseType } from '@/lib/domain/database-type';
import type { DBTable } from '@/lib/domain/db-table';
import { type DBField } from '@/lib/domain/db-field';
import type { DBCustomType } from '@/lib/domain/db-custom-type';
import { DBCustomTypeKind } from '@/lib/domain/db-custom-type';

// Use DBCustomType for generating Enum DBML
const generateEnumsDBML = (customTypes: DBCustomType[] | undefined): string => {
    if (!customTypes || customTypes.length === 0) {
        return '';
    }

    // Filter for enum types and map them
    return customTypes
        .filter((ct) => ct.kind === DBCustomTypeKind.enum)
        .map((enumDef) => {
            const enumIdentifier = enumDef.schema
                ? `"${enumDef.schema}"."${enumDef.name.replace(/"/g, '\\"')}"`
                : `"${enumDef.name.replace(/"/g, '\\"')}"`;

            const valuesString = (enumDef.values || []) // Ensure values array exists
                .map((valueName) => {
                    // valueName is a string as per DBCustomType
                    const valLine = `    "${valueName.replace(/"/g, '\\"')}"`;
                    // If you have notes per enum value, you'd need to adjust DBCustomType
                    // For now, assuming no notes per value in DBCustomType
                    return valLine;
                })
                .join('\n');
            return `Enum ${enumIdentifier} {\n${valuesString}\n}\n`;
        })
        .join('\n');
};

const databaseTypeToImportFormat = (
    type: DatabaseType
): 'mysql' | 'postgres' | 'mssql' => {
    switch (type) {
        case DatabaseType.SQL_SERVER:
            return 'mssql';
        case DatabaseType.MYSQL:
        case DatabaseType.MARIADB:
            return 'mysql';
        case DatabaseType.POSTGRESQL:
        case DatabaseType.COCKROACHDB:
        case DatabaseType.SQLITE:
        case DatabaseType.ORACLE:
            return 'postgres';
        default:
            return 'postgres';
    }
};

// Fix problematic field names in the diagram before passing to SQL generator
const fixProblematicFieldNames = (diagram: Diagram): Diagram => {
    const fixedTables =
        diagram.tables?.map((table) => {
            // Deep clone the table to avoid modifying the original
            const newTable = { ...table };

            // Fix field names if this is the "relation" table
            if (table.name === 'relation') {
                newTable.fields = table.fields.map((field) => {
                    // Create a new field to avoid modifying the original
                    const newField = { ...field };

                    // Fix the 'from' and 'to' fields which are SQL keywords
                    if (field.name === 'from') {
                        newField.name = 'source';
                    } else if (field.name === 'to') {
                        newField.name = 'target';
                    }

                    return newField;
                });
            }

            return newTable;
        }) || [];

    // Update relationships to point to the renamed fields
    const fixedRelationships =
        diagram.relationships?.map((rel) => {
            const relationTable = diagram.tables?.find(
                (t) => t.name === 'relation'
            );
            if (!relationTable) return rel;

            const newRel = { ...rel };

            // Fix relationships that were pointing to the 'from' field
            const fromField = relationTable.fields.find(
                (f) => f.name === 'from'
            );
            if (fromField && rel.targetFieldId === fromField.id) {
                // We need to look up the renamed field in our fixed tables
                const fixedRelationTable = fixedTables.find(
                    (t) => t.name === 'relation'
                );
                const sourceField = fixedRelationTable?.fields.find(
                    (f) => f.name === 'source'
                );
                if (sourceField) {
                    newRel.targetFieldId = sourceField.id;
                }
            }

            // Fix relationships that were pointing to the 'to' field
            const toField = relationTable.fields.find((f) => f.name === 'to');
            if (toField && rel.targetFieldId === toField.id) {
                // We need to look up the renamed field in our fixed tables
                const fixedRelationTable = fixedTables.find(
                    (t) => t.name === 'relation'
                );
                const targetField = fixedRelationTable?.fields.find(
                    (f) => f.name === 'target'
                );
                if (targetField) {
                    newRel.targetFieldId = targetField.id;
                }
            }

            return newRel;
        }) || [];

    // Return a new diagram with the fixes
    return {
        ...diagram,
        tables: fixedTables,
        relationships: fixedRelationships,
    };
};

// Function to sanitize SQL before passing to the importer
export const sanitizeSQLforDBML = (sql: string): string => {
    // Replace special characters in identifiers
    let sanitized = sql;

    // Handle duplicate constraint names
    const constraintNames = new Set<string>();
    let constraintCounter = 0;

    sanitized = sanitized.replace(
        /ADD CONSTRAINT (\w+) FOREIGN KEY/g,
        (match, name) => {
            if (constraintNames.has(name)) {
                return `ADD CONSTRAINT ${name}_${++constraintCounter} FOREIGN KEY`;
            } else {
                constraintNames.add(name);
                return match;
            }
        }
    );

    // Comment out invalid self-referencing foreign keys where the same field references itself
    // Example: ALTER TABLE table ADD CONSTRAINT ... FOREIGN KEY (field_a) REFERENCES table (field_a);
    // But keep valid self-references like: FOREIGN KEY (field_a) REFERENCES table (field_b);
    const lines = sanitized.split('\n');
    const processedLines = lines.map((line) => {
        // Match pattern: ALTER TABLE [schema.]table ADD CONSTRAINT ... FOREIGN KEY(field) REFERENCES [schema.]table(field)
        // Capture the table name, source field, and target field
        const selfRefFKPattern =
            /ALTER\s+TABLE\s+(?:["[]?(\S+?)[\]"]?\.)?["[]?(\S+?)[\]"]?\s+ADD\s+CONSTRAINT\s+\S+\s+FOREIGN\s+KEY\s*\(["[]?([^)"]+)[\]"]?\)\s+REFERENCES\s+(?:["[]?\S+?[\]"]?\.)?"?[[]?\2[\]]?"?\s*\(["[]?([^)"]+)[\]"]?\)\s*;/i;
        const match = selfRefFKPattern.exec(line);

        if (match) {
            const sourceField = match[3].trim();
            const targetField = match[4].trim();

            // Only comment out if source and target fields are the same
            if (sourceField === targetField) {
                return `-- ${line}`; // Comment out invalid self-reference
            }
        }
        return line;
    });
    sanitized = processedLines.join('\n');

    // Fix PostgreSQL type casting syntax that the DBML parser doesn't understand
    sanitized = sanitized.replace(/::regclass/g, '');
    sanitized = sanitized.replace(/: :regclass/g, ''); // Fix corrupted version

    // Fix duplicate columns in index definitions
    sanitized = sanitized.replace(
        /CREATE\s+(?:UNIQUE\s+)?INDEX\s+\S+\s+ON\s+\S+\s*\(([^)]+)\)/gi,
        (match, columnList) => {
            const columns = columnList
                .split(',')
                .map((col: string) => col.trim());
            const uniqueColumns = [...new Set(columns)]; // Remove duplicates
            return match.replace(columnList, uniqueColumns.join(', '));
        }
    );

    // Fix char type with space before parenthesis
    sanitized = sanitized.replace(/char\s+\(/g, 'char(');
    sanitized = sanitized.replace(/character\s+\(/g, 'character(');

    // Fix DEFAULT EUR and similar cases by quoting them
    sanitized = sanitized.replace(
        /DEFAULT\s+([A-Z]{3})(?=\s|,|$)/g,
        "DEFAULT '$1'"
    );
    // Also handle single letter defaults
    sanitized = sanitized.replace(
        /DEFAULT\s+([A-Z])(?=\s|,|$)/g,
        "DEFAULT '$1'"
    );

    // Fix DEFAULT NOW by replacing with NOW()
    sanitized = sanitized.replace(/DEFAULT\s+NOW(?=\s|,|$)/gi, 'DEFAULT NOW()');

    // Replace any remaining problematic characters
    sanitized = sanitized.replace(/\?\?/g, '__');

    return sanitized;
};

// Post-process DBML to convert separate Ref statements to inline refs
const convertToInlineRefs = (dbml: string): string => {
    // Extract all Ref statements - Updated pattern to handle schema.table.field format
    // Matches both "table"."field" and "schema"."table"."field" formats
    const refPattern =
        /Ref\s+"([^"]+)"\s*:\s*(?:"([^"]+)"\.)?"([^"]+)"\."([^"]+)"\s*([<>*])\s*(?:"([^"]+)"\.)?"([^"]+)"\."([^"]+)"/g;
    const refs: Array<{
        refName: string;
        sourceSchema?: string;
        sourceTable: string;
        sourceField: string;
        direction: string;
        targetSchema?: string;
        targetTable: string;
        targetField: string;
    }> = [];

    let match;
    while ((match = refPattern.exec(dbml)) !== null) {
        refs.push({
            refName: match[1], // Reference name
            sourceSchema: match[2] || undefined, // Source schema (optional)
            sourceTable: match[3], // Source table
            sourceField: match[4], // Source field
            direction: match[5], // Direction (<, >)
            targetSchema: match[6] || undefined, // Target schema (optional)
            targetTable: match[7], // Target table
            targetField: match[8], // Target field
        });
    }

    // Extract all table definitions - Support both quoted and bracketed table names
    const tables: {
        [key: string]: {
            start: number;
            end: number;
            content: string;
            fullMatch: string;
        };
    } = {};

    // Use a more sophisticated approach to handle nested braces
    let currentPos = 0;
    while (currentPos < dbml.length) {
        // Find the next table definition
        const tableStartPattern =
            /Table\s+(?:"([^"]+)"(?:\."([^"]+)")?|(\[?[^\s[]+\]?\.\[?[^\s\]]+\]?)|(\[?[^\s[{]+\]?))\s*{/g;
        tableStartPattern.lastIndex = currentPos;
        const tableStartMatch = tableStartPattern.exec(dbml);

        if (!tableStartMatch) break;

        // Extract table name
        let tableName;
        if (tableStartMatch[1] && tableStartMatch[2]) {
            tableName = `${tableStartMatch[1]}.${tableStartMatch[2]}`;
        } else if (tableStartMatch[1]) {
            tableName = tableStartMatch[1];
        } else {
            tableName = tableStartMatch[3] || tableStartMatch[4];
        }

        // Clean up any bracket syntax from table names
        const cleanTableName = tableName.replace(/\[([^\]]+)\]/g, '$1');

        // Find the matching closing brace by counting nested braces
        const openBracePos =
            tableStartMatch.index + tableStartMatch[0].length - 1;
        let braceCount = 1;
        const contentStart = openBracePos + 1;
        let contentEnd = contentStart;

        for (let i = contentStart; i < dbml.length && braceCount > 0; i++) {
            if (dbml[i] === '{') braceCount++;
            else if (dbml[i] === '}') {
                braceCount--;
                if (braceCount === 0) {
                    contentEnd = i;
                }
            }
        }

        if (braceCount === 0) {
            const content = dbml.substring(contentStart, contentEnd);
            const fullMatch = dbml.substring(
                tableStartMatch.index,
                contentEnd + 1
            );

            tables[cleanTableName] = {
                start: tableStartMatch.index,
                end: contentEnd + 1,
                content: content,
                fullMatch: fullMatch,
            };

            currentPos = contentEnd + 1;
        } else {
            // Malformed DBML, skip this table
            currentPos = tableStartMatch.index + tableStartMatch[0].length;
        }
    }

    if (refs.length === 0 || Object.keys(tables).length === 0) {
        return dbml; // Return original if parsing failed
    }

    // Create a map for faster table lookup
    const tableMap = new Map(Object.entries(tables));

    // 1. First, collect all refs per field
    const fieldRefs = new Map<
        string,
        { table: string; refs: string[]; relatedTables: string[] }
    >();

    refs.forEach((ref) => {
        let targetTableName, fieldNameToModify, inlineRefSyntax, relatedTable;

        if (ref.direction === '<') {
            targetTableName = ref.targetSchema
                ? `${ref.targetSchema}.${ref.targetTable}`
                : ref.targetTable;
            fieldNameToModify = ref.targetField;
            const sourceRef = ref.sourceSchema
                ? `"${ref.sourceSchema}"."${ref.sourceTable}"."${ref.sourceField}"`
                : `"${ref.sourceTable}"."${ref.sourceField}"`;
            inlineRefSyntax = `ref: < ${sourceRef}`;
            relatedTable = ref.sourceTable;
        } else {
            targetTableName = ref.sourceSchema
                ? `${ref.sourceSchema}.${ref.sourceTable}`
                : ref.sourceTable;
            fieldNameToModify = ref.sourceField;
            const targetRef = ref.targetSchema
                ? `"${ref.targetSchema}"."${ref.targetTable}"."${ref.targetField}"`
                : `"${ref.targetTable}"."${ref.targetField}"`;
            inlineRefSyntax = `ref: > ${targetRef}`;
            relatedTable = ref.targetTable;
        }

        const fieldKey = `${targetTableName}.${fieldNameToModify}`;
        const existing = fieldRefs.get(fieldKey) || {
            table: targetTableName,
            refs: [],
            relatedTables: [],
        };
        existing.refs.push(inlineRefSyntax);
        existing.relatedTables.push(relatedTable);
        fieldRefs.set(fieldKey, existing);
    });

    // 2. Apply all refs to fields
    fieldRefs.forEach((fieldData, fieldKey) => {
        // fieldKey might be "schema.table.field" or just "table.field"
        const lastDotIndex = fieldKey.lastIndexOf('.');
        const tableName = fieldKey.substring(0, lastDotIndex);
        const fieldName = fieldKey.substring(lastDotIndex + 1);
        const tableData = tableMap.get(tableName);

        if (tableData) {
            // Updated pattern to capture field definition and all existing attributes in brackets
            const fieldPattern = new RegExp(
                `^([ \t]*"${fieldName}"[^\\n]*?)(?:\\s*(\\[[^\\]]*\\]))*\\s*(//.*)?$`,
                'gm'
            );
            let newContent = tableData.content;

            newContent = newContent.replace(
                fieldPattern,
                (lineMatch, fieldPart, existingBrackets, commentPart) => {
                    // Collect all attributes from existing brackets
                    const allAttributes: string[] = [];
                    if (existingBrackets) {
                        // Extract all bracket contents
                        const bracketPattern = /\[([^\]]*)\]/g;
                        let bracketMatch;
                        while (
                            (bracketMatch = bracketPattern.exec(lineMatch)) !==
                            null
                        ) {
                            const content = bracketMatch[1].trim();
                            if (content) {
                                allAttributes.push(content);
                            }
                        }
                    }

                    // Add all refs for this field
                    allAttributes.push(...fieldData.refs);

                    // Combine all attributes into a single bracket
                    const combinedAttributes = allAttributes.join(', ');

                    // Preserve original spacing from fieldPart
                    const leadingSpaces = fieldPart.match(/^(\s*)/)?.[1] || '';
                    const fieldDefWithoutSpaces = fieldPart.trim();

                    return `${leadingSpaces}${fieldDefWithoutSpaces} [${combinedAttributes}]${commentPart || ''}`;
                }
            );

            // Update the table content if modified
            if (newContent !== tableData.content) {
                tableData.content = newContent;
                tableMap.set(tableName, tableData);
            }
        }
    });

    // 2. Reconstruct DBML with modified tables
    let reconstructedDbml = '';
    let lastIndex = 0;
    const sortedTables = Object.entries(tables).sort(
        ([, a], [, b]) => a.start - b.start
    );

    for (const [, tableData] of sortedTables) {
        reconstructedDbml += dbml.substring(lastIndex, tableData.start);
        // Preserve the original table definition format but with updated content
        const originalTableDef = tableData.fullMatch;
        let formattedContent = tableData.content;

        // Clean up content formatting:
        // 1. Split into lines to handle each line individually
        const lines = formattedContent.split('\n');

        // 2. Process lines to ensure proper formatting
        const processedLines = [];
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i];
            const trimmedLine = line.trimEnd();

            // Skip empty lines at the end if followed by a closing brace
            if (trimmedLine === '' && i === lines.length - 1) {
                continue;
            }

            // Skip empty lines before a closing brace
            if (
                trimmedLine === '' &&
                i < lines.length - 1 &&
                lines[i + 1].trim().startsWith('}')
            ) {
                continue;
            }

            processedLines.push(line);
        }

        formattedContent = processedLines.join('\n');

        // Ensure content ends with a newline before the table's closing brace
        if (!formattedContent.endsWith('\n')) {
            formattedContent = formattedContent + '\n';
        }

        // Since we properly extracted content with nested braces, we need to rebuild the table definition
        const tableHeader = originalTableDef.substring(
            0,
            originalTableDef.indexOf('{') + 1
        );
        const updatedTableDef = `${tableHeader}${formattedContent}}`;
        reconstructedDbml += updatedTableDef;
        lastIndex = tableData.end;
    }
    reconstructedDbml += dbml.substring(lastIndex);

    // 3. Remove original Ref lines
    const finalLines = reconstructedDbml
        .split('\n')
        .filter((line) => !line.trim().startsWith('Ref '));
    const finalDbml = finalLines.join('\n').trim();

    // Clean up excessive empty lines - replace multiple consecutive empty lines with just one
    // But ensure there's at least one blank line between tables
    const cleanedDbml = finalDbml
        .replace(/\n\s*\n\s*\n/g, '\n\n')
        .replace(/}\n(?=Table)/g, '}\n\n');

    return cleanedDbml;
};

// Function to check for DBML reserved keywords
const isDBMLKeyword = (name: string): boolean => {
    const keywords = new Set([
        'YES',
        'NO',
        'TRUE',
        'FALSE',
        'NULL', // DBML reserved keywords (boolean literals)
    ]);
    return keywords.has(name.toUpperCase());
};

// Function to check for SQL keywords (add more if needed)
const isSQLKeyword = (name: string): boolean => {
    const keywords = new Set(['CASE', 'ORDER', 'GROUP', 'FROM', 'TO', 'USER']); // Common SQL keywords
    return keywords.has(name.toUpperCase());
};

// Function to remove duplicate relationships from the diagram
const deduplicateRelationships = (diagram: Diagram): Diagram => {
    if (!diagram.relationships) return diagram;

    const seenRelationships = new Set<string>();
    const uniqueRelationships = diagram.relationships.filter((rel) => {
        // Create a unique key based on the relationship endpoints
        const relationshipKey = `${rel.sourceTableId}-${rel.sourceFieldId}->${rel.targetTableId}-${rel.targetFieldId}`;

        if (seenRelationships.has(relationshipKey)) {
            return false; // Skip duplicate
        }

        seenRelationships.add(relationshipKey);
        return true; // Keep unique relationship
    });

    return {
        ...diagram,
        relationships: uniqueRelationships,
    };
};

// Function to append comment statements for renamed tables and fields
const appendRenameComments = (
    baseScript: string,
    sqlRenamedTables: Map<string, string>,
    fieldRenames: Array<{
        table: string;
        originalName: string;
        newName: string;
    }>,
    finalDiagramForExport: Diagram
): string => {
    let script = baseScript;

    // Append COMMENTS for tables renamed due to SQL keywords
    sqlRenamedTables.forEach((originalName, newName) => {
        const escapedOriginal = originalName.replace(/'/g, "\\'");
        // Find the table to get its schema
        const table = finalDiagramForExport.tables?.find(
            (t) => t.name === newName
        );
        const tableIdentifier = table?.schema
            ? `"${table.schema}"."${newName}"`
            : `"${newName}"`;
        script += `\nCOMMENT ON TABLE ${tableIdentifier} IS 'Original name was "${escapedOriginal}" (renamed due to SQL keyword conflict).';`;
    });

    // Append COMMENTS for fields renamed due to SQL keyword conflicts
    fieldRenames.forEach(({ table, originalName, newName }) => {
        const escapedOriginal = originalName.replace(/'/g, "\\'");
        // Find the table to get its schema
        const tableObj = finalDiagramForExport.tables?.find(
            (t) => t.name === table
        );
        const tableIdentifier = tableObj?.schema
            ? `"${tableObj.schema}"."${table}"`
            : `"${table}"`;
        script += `\nCOMMENT ON COLUMN ${tableIdentifier}."${newName}" IS 'Original name was "${escapedOriginal}" (renamed due to SQL keyword conflict).';`;
    });

    return script;
};

// Fix DBML formatting to ensure consistent display of char and varchar types
const normalizeCharTypeFormat = (dbml: string): string => {
    // Replace "char (N)" with "char(N)" to match varchar's formatting
    return dbml
        .replace(/"char "/g, '"char"')
        .replace(/char \(([0-9]+)\)/g, 'char($1)')
        .replace(/"char \(([0-9]+)\)"/g, '"char($1)"')
        .replace(/"character \(([0-9]+)\)"/g, '"character($1)"')
        .replace(/character \(([0-9]+)\)/g, 'character($1)');
};

// Fix table definitions with incorrect bracket syntax
const fixTableBracketSyntax = (dbml: string): string => {
    // Fix patterns like Table [schema].[table] to Table "schema"."table"
    return dbml.replace(
        /Table\s+\[([^\]]+)\]\.\[([^\]]+)\]/g,
        'Table "$1"."$2"'
    );
};

// Fix table names that have been broken across multiple lines
const fixMultilineTableNames = (dbml: string): string => {
    // Match Table declarations that might have line breaks in the table name
    // This regex captures:
    // - Table keyword
    // - Optional quoted schema with dot
    // - Table name that might be broken across lines (until the opening brace)
    return dbml.replace(
        /Table\s+((?:"[^"]*"\.)?"[^"]*(?:\n[^"]*)*")\s*\{/g,
        (_, tableName) => {
            // Remove line breaks within the table name
            const fixedTableName = tableName.replace(/\n\s*/g, '');
            return `Table ${fixedTableName} {`;
        }
    );
};

// Restore composite primary key names in the DBML
const restoreCompositePKNames = (dbml: string, tables: DBTable[]): string => {
    if (!tables || tables.length === 0) return dbml;

    let result = dbml;

    tables.forEach((table) => {
        // Check if this table has a PK index with a name
        const pkIndex = table.indexes.find((idx) => idx.isPrimaryKey);
        if (pkIndex?.name) {
            const primaryKeyFields = table.fields.filter((f) => f.primaryKey);
            if (primaryKeyFields.length >= 1) {
                // Build the column list for the composite PK
                const columnList = primaryKeyFields
                    .map((f) => f.name)
                    .join(', ');

                // Build the table identifier pattern
                const tableIdentifier = table.schema
                    ? `"${table.schema.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\."${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`
                    : `"${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`;

                // Pattern to match the composite PK index line
                // Match patterns like: (col1, col2, col3) [pk]
                const pkPattern = new RegExp(
                    `(Table ${tableIdentifier} \\{[^}]*?Indexes \\{[^}]*?)(\\(${columnList.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\) \\[pk\\])`,
                    'gs'
                );

                // Replace with the named version
                const replacement = `$1(${columnList}) [pk, name: "${pkIndex.name}"]`;
                result = result.replace(pkPattern, replacement);
            }
        }
    });

    return result;
};

// Restore schema information that may have been stripped by the DBML importer
const restoreTableSchemas = (dbml: string, tables: DBTable[]): string => {
    if (!tables || tables.length === 0) return dbml;

    // Group tables by name to handle duplicates
    const tablesByName = new Map<
        string,
        Array<{ table: DBTable; index: number }>
    >();
    tables.forEach((table, index) => {
        const existing = tablesByName.get(table.name) || [];
        existing.push({ table, index });
        tablesByName.set(table.name, existing);
    });

    let result = dbml;

    // Process each group of tables with the same name
    tablesByName.forEach((tablesGroup, tableName) => {
        if (tablesGroup.length === 1) {
            // Single table with this name - simple case
            const table = tablesGroup[0].table;
            if (table.schema) {
                // Match table definition without schema (e.g., Table "users" {)
                const tablePattern = new RegExp(
                    `Table\\s+"${table.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*{`,
                    'g'
                );
                const schemaTableName = `Table "${table.schema}"."${table.name}" {`;
                result = result.replace(tablePattern, schemaTableName);

                // Update references in Ref statements
                const escapedTableName = table.name.replace(
                    /[.*+?^${}()|[\]\\]/g,
                    '\\$&'
                );

                // Pattern 1: In Ref definitions - :"tablename"."field"
                const refDefPattern = new RegExp(
                    `(Ref\\s+"[^"]+")\\s*:\\s*"${escapedTableName}"\\."([^"]+)"`,
                    'g'
                );
                result = result.replace(
                    refDefPattern,
                    `$1:"${table.schema}"."${table.name}"."$2"`
                );

                // Pattern 2: In Ref targets - [<>] "tablename"."field"
                const refTargetPattern = new RegExp(
                    `([<>])\\s*"${escapedTableName}"\\."([^"]+)"`,
                    'g'
                );
                result = result.replace(
                    refTargetPattern,
                    `$1 "${table.schema}"."${table.name}"."$2"`
                );
            }
        } else {
            // Multiple tables with the same name - need to be more careful
            // Find all table definitions for this name
            const escapedTableName = tableName.replace(
                /[.*+?^${}()|[\]\\]/g,
                '\\$&'
            );

            // Get tables that need schema restoration (those without schema in DBML)
            const tablesNeedingSchema = tablesGroup.filter(({ table }) => {
                // Check if this table's schema is already in the DBML
                const schemaPattern = new RegExp(
                    `Table\\s+"${table.schema}"\\.\\s*"${escapedTableName}"\\s*{`,
                    'g'
                );
                return !result.match(schemaPattern);
            });

            // Then handle tables without schema in DBML
            const noSchemaTablePattern = new RegExp(
                `Table\\s+"${escapedTableName}"\\s*{`,
                'g'
            );

            let noSchemaMatchIndex = 0;
            result = result.replace(noSchemaTablePattern, (match) => {
                // We need to match based on the order in the DBML output
                // For PostgreSQL DBML, the @dbml/core sorts tables by:
                // 1. Tables with schemas (alphabetically)
                // 2. Tables without schemas
                // Since both our tables have schemas, they should appear in order

                // Only process tables that need schema restoration
                if (noSchemaMatchIndex >= tablesNeedingSchema.length) {
                    return match;
                }

                const correspondingTable =
                    tablesNeedingSchema[noSchemaMatchIndex];
                noSchemaMatchIndex++;

                if (correspondingTable && correspondingTable.table.schema) {
                    return `Table "${correspondingTable.table.schema}"."${tableName}" {`;
                }
                // If the table doesn't have a schema, keep it as is
                return match;
            });
        }
    });

    return result;
};

export interface DBMLExportResult {
    standardDbml: string;
    inlineDbml: string;
    error?: string;
}

export function generateDBMLFromDiagram(diagram: Diagram): DBMLExportResult {
    // Filter out fields with empty names
    const sanitizedTables =
        diagram.tables?.map((table) => {
            const validFields = table.fields.filter(
                (field) => field.name !== ''
            );
            return {
                ...table,
                fields: validFields,
            };
        }) ?? [];

    // Remove duplicate tables (consider both schema and table name)
    const seenTableIdentifiers = new Set<string>();
    const uniqueTables = sanitizedTables.filter((table) => {
        // Create a unique identifier combining schema and table name
        const tableIdentifier = table.schema
            ? `${table.schema}.${table.name}`
            : table.name;

        if (seenTableIdentifiers.has(tableIdentifier)) {
            return false; // Skip duplicate
        }
        seenTableIdentifiers.add(tableIdentifier);
        return true; // Keep unique table
    });

    // Create the base filtered diagram structure
    const filteredDiagram: Diagram = {
        ...diagram,
        tables: uniqueTables,
        relationships:
            diagram.relationships?.filter((rel) => {
                const sourceTable = uniqueTables.find(
                    (t) => t.id === rel.sourceTableId
                );
                const targetTable = uniqueTables.find(
                    (t) => t.id === rel.targetTableId
                );
                const sourceFieldExists = sourceTable?.fields.some(
                    (f) => f.id === rel.sourceFieldId
                );
                const targetFieldExists = targetTable?.fields.some(
                    (f) => f.id === rel.targetFieldId
                );

                return (
                    sourceTable &&
                    targetTable &&
                    sourceFieldExists &&
                    targetFieldExists
                );
            }) ?? [],
    };

    // Sanitize field names ('from'/'to' in 'relation' table)
    const cleanDiagram = fixProblematicFieldNames(filteredDiagram);

    // --- Final sanitization and renaming pass ---
    // Only rename keywords for PostgreSQL/SQLite
    // For other databases, we'll wrap problematic names in quotes instead
    const shouldRenameKeywords =
        diagram.databaseType === DatabaseType.POSTGRESQL ||
        diagram.databaseType === DatabaseType.SQLITE;
    const sqlRenamedTables = new Map<string, string>();
    const fieldRenames: Array<{
        table: string;
        originalName: string;
        newName: string;
    }> = [];

    const processTable = (table: DBTable) => {
        const originalName = table.name;
        let safeTableName = originalName;

        // If name contains spaces or special characters, wrap in quotes
        if (/[^\w]/.test(originalName)) {
            safeTableName = `"${originalName.replace(/"/g, '\\"')}"`;
        }

        // Rename table if it's a keyword (PostgreSQL/SQLite only)
        if (
            shouldRenameKeywords &&
            (isDBMLKeyword(originalName) || isSQLKeyword(originalName))
        ) {
            const newName = `${originalName}_table`;
            sqlRenamedTables.set(newName, originalName);
            safeTableName = /[^\w]/.test(newName)
                ? `"${newName.replace(/"/g, '\\"')}"`
                : newName;
        }
        // For other databases, just quote DBML keywords
        else if (!shouldRenameKeywords && isDBMLKeyword(originalName)) {
            safeTableName = `"${originalName.replace(/"/g, '\\"')}"`;
        }

        const fieldNameCounts = new Map<string, number>();
        const processedFields = table.fields.map((field) => {
            let finalSafeName = field.name;

            // If field name contains spaces or special characters, wrap in quotes
            if (/[^\w]/.test(field.name)) {
                finalSafeName = `"${field.name.replace(/"/g, '\\"')}"`;
            }

            // Handle duplicate field names
            const count = fieldNameCounts.get(field.name) || 0;
            if (count > 0) {
                const newName = `${field.name}_${count + 1}`;
                finalSafeName = /[^\w]/.test(newName)
                    ? `"${newName.replace(/"/g, '\\"')}"`
                    : newName;
            }
            fieldNameCounts.set(field.name, count + 1);

            // Create sanitized field
            const sanitizedField: DBField = {
                ...field,
                name: finalSafeName,
            };

            // Rename field if it's a keyword (PostgreSQL/SQLite only)
            if (
                shouldRenameKeywords &&
                (isDBMLKeyword(field.name) || isSQLKeyword(field.name))
            ) {
                const newFieldName = `${field.name}_field`;
                fieldRenames.push({
                    table: safeTableName,
                    originalName: field.name,
                    newName: newFieldName,
                });
                sanitizedField.name = /[^\w]/.test(newFieldName)
                    ? `"${newFieldName.replace(/"/g, '\\"')}"`
                    : newFieldName;
            }
            // For other databases, just quote DBML keywords
            else if (!shouldRenameKeywords && isDBMLKeyword(field.name)) {
                sanitizedField.name = `"${field.name.replace(/"/g, '\\"')}"`;
            }

            return sanitizedField;
        });

        return {
            ...table,
            name: safeTableName,
            fields: processedFields,
            indexes: (table.indexes || [])
                .filter((index) => !index.isPrimaryKey) // Filter out PK indexes as they're handled separately
                .map((index) => ({
                    ...index,
                    name: index.name
                        ? /[^\w]/.test(index.name)
                            ? `"${index.name.replace(/"/g, '\\"')}"`
                            : index.name
                        : `idx_${Math.random().toString(36).substring(2, 8)}`,
                })),
        };
    };

    const finalDiagramForExport: Diagram = deduplicateRelationships({
        ...cleanDiagram,
        tables: cleanDiagram.tables?.map(processTable) ?? [],
        relationships:
            cleanDiagram.relationships?.map((rel, index) => {
                const safeName = rel.name
                    ? rel.name.replace(/[^\w]/g, '_')
                    : Math.random().toString(36).substring(2, 8);
                return {
                    ...rel,
                    name: `fk_${index}_${safeName}`,
                };
            }) ?? [],
    } as Diagram);

    let standard = '';
    let inline = '';
    let baseScript = ''; // Define baseScript outside try

    // Use finalDiagramForExport.customTypes which should be DBCustomType[]
    const enumsDBML = generateEnumsDBML(finalDiagramForExport.customTypes);

    let errorMsg: string | undefined = undefined;

    try {
        baseScript = exportBaseSQL({
            diagram: finalDiagramForExport, // Use final diagram
            targetDatabaseType: diagram.databaseType,
            isDBMLFlow: true,
        });

        baseScript = sanitizeSQLforDBML(baseScript);

        // Append comments for renamed tables and fields (PostgreSQL/SQLite only)
        if (
            shouldRenameKeywords &&
            (sqlRenamedTables.size > 0 || fieldRenames.length > 0)
        ) {
            baseScript = appendRenameComments(
                baseScript,
                sqlRenamedTables,
                fieldRenames,
                finalDiagramForExport
            );
        }

        standard = normalizeCharTypeFormat(
            fixMultilineTableNames(
                fixTableBracketSyntax(
                    importer.import(
                        baseScript,
                        databaseTypeToImportFormat(diagram.databaseType)
                    )
                )
            )
        );

        // Restore schema information that may have been stripped by DBML importer
        standard = restoreTableSchemas(standard, uniqueTables);

        // Restore composite primary key names
        standard = restoreCompositePKNames(standard, uniqueTables);

        // Prepend Enum DBML to the standard output
        if (enumsDBML) {
            standard = enumsDBML + '\n\n' + standard;
        }

        inline = normalizeCharTypeFormat(convertToInlineRefs(standard));

        // Clean up excessive empty lines in both outputs
        standard = standard.replace(/\n\s*\n\s*\n/g, '\n\n');
        inline = inline.replace(/\n\s*\n\s*\n/g, '\n\n');

        // Ensure proper formatting with newline at end
        if (!standard.endsWith('\n')) {
            standard += '\n';
        }
        if (!inline.endsWith('\n')) {
            inline += '\n';
        }
    } catch (error: unknown) {
        console.error(
            'Error during DBML generation process:',
            error,
            'Input SQL was:',
            baseScript // Log the SQL that caused the error
        );

        errorMsg = error instanceof Error ? error.message : 'Unknown error';
        const errorMessage = `// Error generating DBML: ${errorMsg}`;
        standard = errorMessage;
        inline = errorMessage;

        // If an error occurred, still prepend enums if they exist, or they'll be lost.
        // The error message will then follow.
        if (standard.startsWith('// Error generating DBML:') && enumsDBML) {
            standard = enumsDBML + '\n\n' + standard;
        }
        if (inline.startsWith('// Error generating DBML:') && enumsDBML) {
            inline = enumsDBML + '\n\n' + inline;
        }
    }

    return { standardDbml: standard, inlineDbml: inline, error: errorMsg };
}
