"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.migrate = migrate;
const ts = require("typescript");
const ast_utils_1 = require("../../utils/ast-utils");
const workspace_utils_1 = require("../../utils/workspace-utils");
// ─── Constants ────────────────────────────────────────────────────────────────
const REQUIRED_STYLE = 'node_modules/@seniorsistemas/angular-components/styles.css';
const DYNAMIC_FORM_MODULE = '@seniorsistemas/angular-components/dynamic-form';
const EDITOR_MODULE = '@seniorsistemas/angular-components/optional-fields/editor';
const SENIORSISTEMAS_PATTERN = '@seniorsistemas/angular-components';
/** FieldType enum (v18) → string literal (v19). null = no equivalent / special handling */
const FIELD_TYPE_MAP = {
    Autocomplete: 'autocomplete',
    Binary: 'binary',
    Blob: 'blob',
    Boolean: 'boolean',
    BooleanSwitch: 'booleanSwitch',
    Button: 'button',
    Checkbox: 'checkbox',
    Chips: 'chips',
    CountryPhonePicker: 'countryPhonePicker',
    Date: 'date',
    DateTime: 'dateTime',
    Double: 'double',
    Editor: 'editor',
    Enum: 'enum',
    Integer: 'integer',
    LocalDateTime: 'localDateTime',
    Lookup: 'lookup',
    Money: 'money',
    Number: 'number',
    Password: 'password',
    ProfilePicture: 'profilePicture',
    Radio: 'radioButton',
    Slider: 'slider',
    StarRating: 'starRating',
    String: 'string',
    Text: 'textArea',
    TextIA: 'contentGenerator',
    Time: 'time',
    CodeEditor: null,
    Custom: null,
};
/** DynamicType enum (v18) member → string literal (v19). */
const DYNAMIC_TYPE_ENUM_MAP = {
    Row: 'row',
    Section: 'section',
    Fieldset: 'fieldset',
    // Field-type members of v18 DynamicType enum
    Autocomplete: 'autocomplete',
    Binary: 'binary',
    Boolean: 'boolean',
    Chips: 'chips',
    Date: 'date',
    DateTime: 'dateTime',
    LocalDateTime: 'localDateTime',
    Double: 'double',
    Enum: 'enum',
    Integer: 'integer',
    Lookup: 'lookup',
    Money: 'money',
    Radio: 'radioButton',
    String: 'string',
    Text: 'textArea',
    Time: 'time',
    Number: 'number',
    Password: 'password',
    Slider: 'slider',
};
const GRID_TYPE_MAP = { Row: 'row' };
const STRUCTURE_TYPE_MAP = { Section: 'section', Fieldset: 'fieldset' };
/** v18 field/structure class names that become plain objects in v19 */
const FIELD_CLASS_NAMES = new Set([
    'FormField',
    'TextField', 'SelectField', 'CalendarField', 'CurrencyField', 'NumberField',
    'RadioButtonField', 'AutocompleteField', 'BooleanField', 'BooleanSwitchField',
    'CheckboxField', 'ChipsField', 'LookupField', 'SliderField', 'TextAreaField',
    'EditorField', 'BlobField', 'CountryPhonePickerField', 'ButtonField', 'StarRatingField',
    'CodeEditorField', 'ProfilePictureField', 'DecimalField', 'BignumberField',
    'TextAreaIAField', 'PasswordField',
    'Section', 'Grid', 'Row', 'Fieldset',
]);
/** Mapping from v18 class → v19 interface (for isolated object type assertions) */
const CLASS_TO_INTERFACE = {
    TextField: 'TextFieldConfig',
    SelectField: 'SelectFieldConfig',
    CalendarField: 'CalendarFieldConfig',
    CurrencyField: 'CurrencyFieldConfig',
    DecimalField: 'NumberFieldConfig',
    NumberField: 'NumberFieldConfig',
    BignumberField: 'NumberFieldConfig',
    RadioButtonField: 'RadioButtonFieldConfig',
    AutocompleteField: 'AutocompleteFieldConfig',
    BooleanField: 'BooleanFieldConfig',
    BooleanSwitchField: 'BooleanSwitchFieldConfig',
    CheckboxField: 'CheckboxFieldConfig',
    ChipsField: 'ChipsFieldConfig',
    LookupField: 'LookupFieldConfig',
    SliderField: 'SliderFieldConfig',
    TextAreaField: 'TextAreaFieldConfig',
    TextAreaIAField: 'ContentGeneratorFieldConfig',
    EditorField: 'EditorFieldConfig',
    BlobField: 'BlobFieldConfig',
    CountryPhonePickerField: 'CountryPhonePickerFieldConfig',
    ButtonField: 'ButtonFieldConfig',
    StarRatingField: 'StarRatingFieldConfig',
    ProfilePictureField: 'ProfilePictureFieldConfig',
    PasswordField: 'PasswordFieldConfig',
    FormField: 'DynamicType',
    Section: 'SectionConfig',
    Grid: 'RowConfig',
    Row: 'RowConfig',
    Fieldset: 'FieldsetConfig',
    CodeEditorField: 'DynamicType',
};
/** v18 type annotation names → v19 equivalents */
const TYPE_ANNOTATION_MAP = {
    FormField: 'DynamicType',
    Field: 'DynamicType',
    TextField: 'DynamicType',
    SelectField: 'DynamicType',
    CalendarField: 'DynamicType',
    CurrencyField: 'DynamicType',
    DecimalField: 'DynamicType',
    NumberField: 'DynamicType',
    BignumberField: 'DynamicType',
    RadioButtonField: 'DynamicType',
    AutocompleteField: 'DynamicType',
    BooleanField: 'DynamicType',
    BooleanSwitchField: 'DynamicType',
    CheckboxField: 'DynamicType',
    ChipsField: 'DynamicType',
    LookupField: 'DynamicType',
    SliderField: 'DynamicType',
    TextAreaField: 'DynamicType',
    TextAreaIAField: 'DynamicType',
    EditorField: 'DynamicType',
    BlobField: 'DynamicType',
    CountryPhonePickerField: 'DynamicType',
    ButtonField: 'DynamicType',
    StarRatingField: 'DynamicType',
    ProfilePictureField: 'DynamicType',
    PasswordField: 'DynamicType',
    CodeEditorField: 'DynamicType',
    DynamicConfig: 'DynamicStructure',
};
/** All valid DynamicType `type` string literals in v19 */
const KNOWN_FIELD_TYPES = new Set([
    'autocomplete', 'binary', 'blob', 'boolean', 'booleanSwitch', 'button', 'checkbox',
    'chips', 'contentGenerator', 'countryPhonePicker', 'date', 'dateTime', 'double',
    'editor', 'enum', 'integer', 'localDateTime', 'lookup', 'money', 'number',
    'password', 'profilePicture', 'radioButton', 'slider', 'starRating', 'string',
    'textArea', 'time',
]);
/** Names to always remove from @seniorsistemas imports */
const NAMES_TO_REMOVE = [
    'FormField', 'Field', 'GridType', 'StructureType', 'DynamicConfig',
    'TextField', 'SelectField', 'CalendarField', 'CurrencyField', 'NumberField',
    'RadioButtonField', 'AutocompleteField', 'BooleanField', 'BooleanSwitchField',
    'CheckboxField', 'ChipsField', 'LookupField', 'SliderField', 'TextAreaField',
    'EditorField', 'BlobField', 'CountryPhonePickerField', 'ButtonField', 'StarRatingField',
    'CodeEditorField', 'ProfilePictureField', 'DecimalField', 'BignumberField',
    'TextAreaIAField', 'PasswordField',
    'Section', 'Grid', 'Row', 'Fieldset',
    // Config types used for v18 type casts
    'TextAreaFieldConfig', 'StarRatingFieldConfig', 'AutocompleteFieldConfig',
    'BooleanFieldConfig', 'BooleanSwitchFieldConfig', 'CalendarFieldConfig',
    'CheckboxFieldConfig', 'ChipsFieldConfig', 'CountryPhonePickerFieldConfig',
    'CurrencyFieldConfig', 'LookupFieldConfig', 'NumberFieldConfig', 'PasswordFieldConfig',
    'ProfilePictureFieldConfig', 'RadioButtonFieldConfig', 'SelectFieldConfig',
    'SliderFieldConfig', 'TextFieldConfig', 'ButtonFieldConfig', 'BlobFieldConfig',
    'SectionConfig', 'RowConfig', 'FieldsetConfig',
    // Old structure types
    'Structure', 'DynamicType',
];
// ─── Main entry point ─────────────────────────────────────────────────────────
function migrate() {
    return (tree, context) => {
        let editorDetected = false;
        const fieldsBindings = [];
        (0, workspace_utils_1.ensureAngularJsonStyle)(tree, REQUIRED_STYLE, context);
        tree.visit((filePath) => {
            if (filePath.includes('/node_modules/') || filePath.includes('/dist/'))
                return;
            if (filePath.endsWith('.ts')) {
                const content = tree.readText(filePath);
                if (!quickCheck(content))
                    return;
                const result = migrateTypescriptFile(content, filePath, context);
                if (result.editorDetected)
                    editorDetected = true;
                if (result.updated !== content)
                    tree.overwrite(filePath, result.updated);
            }
        });
        tree.visit((filePath) => {
            if (filePath.includes('/node_modules/') || filePath.includes('/dist/'))
                return;
            if (filePath.endsWith('.html')) {
                const content = tree.readText(filePath);
                if (!content.includes('[fields]') || !content.includes('s-dynamic-form'))
                    return;
                const result = migrateHtmlFile(content);
                if (result.bindings.length > 0) {
                    fieldsBindings.push(...result.bindings.map((b) => ({ ...b, htmlPath: filePath })));
                    tree.overwrite(filePath, result.updated);
                }
            }
        });
        // Third pass: annotate field arrays and warn on FormField patterns in all .ts files
        tree.visit((filePath) => {
            if (filePath.includes('/node_modules/') || filePath.includes('/dist/'))
                return;
            if (!filePath.endsWith('.ts'))
                return;
            const content = tree.readText(filePath);
            if (!quickCheckFieldPatterns(content))
                return;
            const result = migrateFieldDefinitions(content, filePath, context);
            if (result !== content)
                tree.overwrite(filePath, result);
        });
        for (const binding of fieldsBindings) {
            addGetterToTsFile(tree, binding, context);
        }
        if (editorDetected) {
            addProviderToApp(tree, 'provideEditorField()', EDITOR_MODULE, context);
        }
        else {
            removeFromPackageJson(tree, 'quill', context);
        }
    };
}
// ─── Quick-check ──────────────────────────────────────────────────────────────
const QUICK_CHECK_KEYWORDS = [
    'FormField', 'FieldType', 'DynamicType', 'GridType', 'StructureType',
    'TextField', 'SelectField', 'CalendarField', 'CurrencyField', 'NumberField',
    'RadioButtonField', 'AutocompleteField', 'EditorField', 'DynamicConfig',
    'BooleanField', 'SliderField', 'TextAreaField', 'Field',
];
function quickCheck(content) {
    return QUICK_CHECK_KEYWORDS.some((kw) => content.includes(kw));
}
function quickCheckFieldPatterns(content) {
    for (const t of KNOWN_FIELD_TYPES) {
        if (content.includes(`type: '${t}'`))
            return true;
    }
    return false;
}
// ─── TypeScript migration ──────────────────────────────────────────────────────
function migrateTypescriptFile(content, filePath, context) {
    const importedNames = getImportedNamesFromSenior(content);
    let updated = content;
    let editorDetected = false;
    // Pass 1: enum accesses → string literals
    const enumResult = migrateEnumAccesses(updated, importedNames, filePath, context);
    updated = enumResult.updated;
    editorDetected = enumResult.editorDetected;
    // Pass 2: new XxxField({...}) → plain object (re-parse after pass 1 changes ranges)
    const constructorsResult = migrateConstructors(updated, importedNames);
    updated = constructorsResult.updated;
    // Pass 2b: { type: 'boolean', representedBy: 'switch' } → { type: 'booleanSwitch' }
    updated = migrateBooleanSwitch(updated);
    // Pass 2c: remove v18 type casts (as TextAreaFieldConfig, etc.)
    updated = removeV18TypeCasts(updated, importedNames);
    // Pass 3: type annotations FormField[] → DynamicType[], DynamicConfig[] → DynamicStructure[]
    updated = migrateTypeAnnotations(updated, importedNames);
    // Pass 3b: @Input() on BaseFieldComponentConfig implementors → input()/input.required()
    if (importedNames.has('BaseFieldComponentConfig')) {
        updated = migrateBaseFieldInputs(updated, filePath, context);
    }
    // Pass 4: cleanup and add imports
    updated = migrateImports(updated, importedNames, editorDetected, filePath, constructorsResult.castTypesUsed);
    // Pass 5: remove FormField from interface heritage clauses, warn for classes
    updated = warnAndFixFormFieldHeritage(updated, filePath, context);
    return { updated, editorDetected };
}
function getImportedNamesFromSenior(content) {
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const names = new Set();
    ts.forEachChild(sourceFile, (node) => {
        if (!ts.isImportDeclaration(node))
            return;
        const spec = node.moduleSpecifier.text;
        if (!spec.includes(SENIORSISTEMAS_PATTERN))
            return;
        const nb = node.importClause?.namedBindings;
        if (!nb || !ts.isNamedImports(nb))
            return;
        for (const el of nb.elements)
            names.add(el.name.text);
    });
    return names;
}
// Pass 1 ─────────────────────────────────────────────────────────────────────
function migrateEnumAccesses(content, importedNames, filePath, context) {
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    let editorDetected = false;
    function visit(node) {
        if (ts.isPropertyAccessExpression(node)) {
            const enumName = node.expression.getText(sourceFile);
            const memberName = node.name.text;
            if (enumName === 'FieldType' && importedNames.has('FieldType')) {
                if (memberName === 'Editor') {
                    editorDetected = true;
                    changes.push({ start: node.getStart(), end: node.getEnd(), replacement: "'editor'" });
                }
                else if (memberName === 'Custom') {
                    // A6: custom field handling — leave enum access for now, detect in pass 2
                }
                else if (memberName === 'CodeEditor') {
                    context.logger.warn(`${filePath}: FieldType.CodeEditor não tem equivalente em v19. Remova ou substitua manualmente.`);
                }
                else {
                    const mapped = FIELD_TYPE_MAP[memberName];
                    if (mapped !== undefined && mapped !== null) {
                        changes.push({ start: node.getStart(), end: node.getEnd(), replacement: `'${mapped}'` });
                    }
                }
            }
            else if (enumName === 'DynamicType' && importedNames.has('DynamicType')) {
                const mapped = DYNAMIC_TYPE_ENUM_MAP[memberName];
                if (mapped) {
                    changes.push({ start: node.getStart(), end: node.getEnd(), replacement: `'${mapped}'` });
                }
            }
            else if (enumName === 'GridType' && importedNames.has('GridType')) {
                const mapped = GRID_TYPE_MAP[memberName];
                if (mapped) {
                    changes.push({ start: node.getStart(), end: node.getEnd(), replacement: `'${mapped}'` });
                }
            }
            else if (enumName === 'StructureType' && importedNames.has('StructureType')) {
                const mapped = STRUCTURE_TYPE_MAP[memberName];
                if (mapped) {
                    changes.push({ start: node.getStart(), end: node.getEnd(), replacement: `'${mapped}'` });
                }
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
    return {
        updated: changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content,
        editorDetected,
    };
}
// Pass 2 ─────────────────────────────────────────────────────────────────────
function migrateConstructors(content, importedNames) {
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    const castTypesUsed = new Set();
    function visit(node) {
        if (ts.isNewExpression(node) && ts.isIdentifier(node.expression)) {
            const calleeName = node.expression.text;
            if (FIELD_CLASS_NAMES.has(calleeName) && importedNames.has(calleeName)) {
                const args = node.arguments;
                if (args && args.length > 0) {
                    // v19 has no field classes anymore — `new XxxField(arg)` becomes just `arg`,
                    // whether `arg` is an inline object literal or a variable/expression.
                    const argText = args[0].getText(sourceFile);
                    const inArray = node.parent && ts.isArrayLiteralExpression(node.parent);
                    const castType = CLASS_TO_INTERFACE[calleeName] ?? 'DynamicType';
                    const body = inArray ? argText : `${argText} as ${castType}`;
                    if (!inArray)
                        castTypesUsed.add(castType);
                    // `() => { ... } as X` misparses the `{` as a block, not an object literal —
                    // same issue for a bare `{ ... } as X;` expression statement. Parenthesize.
                    const needsParens = (ts.isArrowFunction(node.parent) && node.parent.body === node) ||
                        (ts.isExpressionStatement(node.parent) && node.parent.expression === node);
                    const replacement = needsParens ? `(${body})` : body;
                    changes.push({ start: node.getStart(), end: node.getEnd(), replacement });
                }
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
    return {
        updated: changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content,
        castTypesUsed,
    };
}
// Pass 2b ─────────────────────────────────────────────────────────────────────
function migrateBooleanSwitch(content) {
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    function visit(node) {
        if (ts.isObjectLiteralExpression(node)) {
            let typeProp;
            let repProp;
            for (const prop of node.properties) {
                if (!ts.isPropertyAssignment(prop))
                    continue;
                const key = prop.name.getText(sourceFile);
                if (key === 'type' && ts.isStringLiteral(prop.initializer) && prop.initializer.text === 'boolean') {
                    typeProp = prop;
                }
                if (key === 'representedBy' && ts.isStringLiteral(prop.initializer) && prop.initializer.text === 'switch') {
                    repProp = prop;
                }
            }
            if (typeProp && repProp) {
                // Replace type value
                changes.push({
                    start: typeProp.initializer.getStart(),
                    end: typeProp.initializer.getEnd(),
                    replacement: "'booleanSwitch'",
                });
                // Remove representedBy property with surrounding comma
                const props = [...node.properties];
                const repIdx = props.indexOf(repProp);
                const isLast = repIdx === props.length - 1;
                if (isLast && repIdx > 0) {
                    changes.push({
                        start: props[repIdx - 1].getEnd(),
                        end: repProp.getEnd(),
                        replacement: '',
                    });
                }
                else if (!isLast) {
                    changes.push({
                        start: repProp.getStart(),
                        end: props[repIdx + 1].getStart(),
                        replacement: '',
                    });
                }
                else {
                    changes.push({ start: repProp.getStart(), end: repProp.getEnd(), replacement: '' });
                }
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
    return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
}
// Pass 2c ─────────────────────────────────────────────────────────────────────
function removeV18TypeCasts(content, importedNames) {
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    const v18CastNames = new Set([
        ...NAMES_TO_REMOVE,
        ...Object.values(CLASS_TO_INTERFACE),
    ]);
    function visit(node) {
        if (ts.isAsExpression(node) && ts.isTypeReferenceNode(node.type)) {
            const typeName = node.type.typeName.getText(sourceFile);
            if (v18CastNames.has(typeName) && importedNames.has(typeName)) {
                changes.push({
                    start: node.getStart(),
                    end: node.getEnd(),
                    replacement: node.expression.getText(sourceFile),
                });
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
    return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
}
// Pass 3 ─────────────────────────────────────────────────────────────────────
function migrateTypeAnnotations(content, importedNames) {
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    function visit(node) {
        if (ts.isTypeReferenceNode(node)) {
            const typeName = node.typeName.getText(sourceFile);
            const mapped = TYPE_ANNOTATION_MAP[typeName];
            if (mapped && importedNames.has(typeName)) {
                changes.push({ start: node.getStart(), end: node.getEnd(), replacement: mapped });
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
    return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
}
// Pass 4 — imports ──────────────────────────────────────────────────────────
function migrateImports(content, originalImportedNames, editorDetected, filePath, castTypesUsed) {
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    // Determine if FieldType is still needed as a type annotation
    const fieldTypeStillUsed = originalImportedNames.has('FieldType') && isUsedAsType(content, 'FieldType');
    // DynamicType (v18 enum) was replaced; if used as a type annotation it maps to the v19 type union
    const dynamicTypeUsedAsAnnotation = isUsedAsType(content, 'DynamicType');
    const toRemove = [...NAMES_TO_REMOVE];
    if (!fieldTypeStillUsed)
        toRemove.push('FieldType');
    const { changes } = (0, ast_utils_1.buildRemoveNamedImportsChanges)(sourceFile, toRemove, SENIORSISTEMAS_PATTERN);
    let updated = (0, ast_utils_1.applyChanges)(content, changes).replace(/\n{3,}/g, '\n\n');
    // Re-parse after removal to check what's currently imported
    const currentlyImported = getImportedNamesFromSenior(updated);
    // Ensure every XxxFieldConfig interface introduced by isolated casts in Pass 2 is imported
    for (const typeName of castTypesUsed) {
        if (!isSymbolImportedFrom(updated, typeName, DYNAMIC_FORM_MODULE)) {
            updated = ensureNamedImport(updated, typeName, DYNAMIC_FORM_MODULE);
        }
    }
    // Add v19 types as needed
    if (dynamicTypeUsedAsAnnotation &&
        !currentlyImported.has('DynamicType') &&
        !isSymbolImportedFrom(updated, 'DynamicType', DYNAMIC_FORM_MODULE)) {
        updated = ensureNamedImport(updated, 'DynamicType', DYNAMIC_FORM_MODULE);
    }
    if (updated.includes('ExpandedDynamicType') &&
        !isSymbolImportedFrom(updated, 'ExpandedDynamicType', DYNAMIC_FORM_MODULE)) {
        updated = ensureNamedImport(updated, 'ExpandedDynamicType', DYNAMIC_FORM_MODULE);
    }
    if (updated.includes('DynamicStructure') &&
        !isSymbolImportedFrom(updated, 'DynamicStructure', DYNAMIC_FORM_MODULE)) {
        updated = ensureNamedImport(updated, 'DynamicStructure', DYNAMIC_FORM_MODULE);
    }
    if (updated.includes('dynamicTypeToDynamicStructure') &&
        !isSymbolImportedFrom(updated, 'dynamicTypeToDynamicStructure', DYNAMIC_FORM_MODULE)) {
        updated = ensureNamedImport(updated, 'dynamicTypeToDynamicStructure', DYNAMIC_FORM_MODULE);
    }
    if (editorDetected && !isSymbolImportedFrom(updated, 'provideEditorField', EDITOR_MODULE)) {
        updated = (0, ast_utils_1.insertImportStatement)(updated, 'provideEditorField', EDITOR_MODULE);
    }
    return updated;
}
// ─── HTML migration ────────────────────────────────────────────────────────────
function migrateHtmlFile(content) {
    const bindings = [];
    let updated = content;
    // Match [fields]="<expr>" inside <s-dynamic-form> tags
    const tagRegex = /(<s-dynamic-form\b[^>]*?)\[fields\]="([^"]+)"([^>]*?>)/gs;
    updated = updated.replace(tagRegex, (match, before, expr, after) => {
        const isDynamic = expr.includes('.');
        const newBindingValue = isDynamic ? `dynamicTypeToConfigs(${expr})` : `${expr}Configs`;
        bindings.push({ propExpression: expr, isDynamic });
        return `${before}[configs]="${newBindingValue}"${after}`;
    });
    return { updated, bindings };
}
// ─── Getter injection in TS file ───────────────────────────────────────────────
function addGetterToTsFile(tree, binding, context) {
    const tsPath = binding.htmlPath.replace(/\.html$/, '.ts');
    if (!tree.exists(tsPath)) {
        context.logger.warn(`${binding.htmlPath}: Não foi possível encontrar ${tsPath} para adicionar getter. Adicione manualmente.`);
        return;
    }
    const content = tree.readText(tsPath);
    if (binding.isDynamic) {
        // Helper method: dynamicTypeToConfigs
        if (content.includes('dynamicTypeToConfigs'))
            return;
        const method = [
            '',
            '    protected dynamicTypeToConfigs(fields: DynamicType[]): DynamicStructure[] {',
            '        return dynamicTypeToDynamicStructure(fields);',
            '    }',
        ].join('\n');
        const updated = insertBeforeLastBrace(content, method);
        const withImports = ensureMultipleImports(updated, [
            'DynamicType', 'DynamicStructure', 'dynamicTypeToDynamicStructure',
        ], DYNAMIC_FORM_MODULE);
        tree.overwrite(tsPath, withImports);
    }
    else {
        // Simple getter: get <prop>Configs()
        const prop = binding.propExpression;
        const getterName = `${prop}Configs`;
        if (content.includes(getterName))
            return;
        const getter = [
            '',
            `    protected get ${getterName}(): DynamicStructure[] {`,
            `        return dynamicTypeToDynamicStructure(this.${prop});`,
            '    }',
        ].join('\n');
        const updated = insertBeforeLastBrace(content, getter);
        const withImports = ensureMultipleImports(updated, [
            'DynamicStructure', 'dynamicTypeToDynamicStructure',
        ], DYNAMIC_FORM_MODULE);
        tree.overwrite(tsPath, withImports);
    }
}
function insertBeforeLastBrace(content, insertion) {
    const idx = content.lastIndexOf('}');
    if (idx === -1)
        return content + insertion;
    return content.slice(0, idx) + insertion + '\n' + content.slice(idx);
}
// ─── Provider injection ────────────────────────────────────────────────────────
function addProviderToApp(tree, providerCall, importFrom, context) {
    const candidates = ['src/app/app.config.ts', 'src/app/app.module.ts'];
    for (const candidate of candidates) {
        if (!tree.exists(candidate))
            continue;
        const content = tree.readText(candidate);
        const updated = injectProvider(content, providerCall);
        if (updated !== content) {
            const symbolName = providerCall.replace(/\(.*\)$/, '');
            const withImport = ensureNamedImport(updated, symbolName, importFrom);
            tree.overwrite(candidate, withImport);
            context.logger.info(`${candidate}: adicionado ${providerCall} aos providers.`);
            return;
        }
    }
    context.logger.warn(`Não foi possível injetar ${providerCall} automaticamente. Adicione manualmente ao providers do AppModule ou ApplicationConfig.`);
}
function injectProvider(content, providerCall) {
    if (content.includes(providerCall))
        return content;
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    function visitForProviders(node) {
        if (ts.isObjectLiteralExpression(node)) {
            for (const prop of node.properties) {
                if (!ts.isPropertyAssignment(prop))
                    continue;
                if (prop.name.getText(sourceFile) !== 'providers')
                    continue;
                if (!ts.isArrayLiteralExpression(prop.initializer))
                    continue;
                const arr = prop.initializer;
                const els = arr.elements;
                if (els.length > 0) {
                    const last = els[els.length - 1];
                    changes.push({ start: last.getEnd(), end: last.getEnd(), replacement: `,\n        ${providerCall}` });
                }
                else {
                    changes.push({ start: arr.getStart() + 1, end: arr.getStart() + 1, replacement: providerCall });
                }
                return;
            }
        }
        ts.forEachChild(node, visitForProviders);
    }
    visitForProviders(sourceFile);
    return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
}
// ─── package.json cleanup ─────────────────────────────────────────────────────
function removeFromPackageJson(tree, dep, context) {
    const pkgPath = '/package.json';
    if (!tree.exists(pkgPath))
        return;
    const pkg = JSON.parse(tree.readText(pkgPath));
    let removed = false;
    for (const section of ['dependencies', 'devDependencies', 'peerDependencies']) {
        if (pkg[section]?.[dep]) {
            delete pkg[section][dep];
            removed = true;
        }
    }
    if (removed) {
        tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
        context.logger.info(`package.json: removida dependência "${dep}" (sem uso de Editor field).`);
    }
}
// ─── Helpers ──────────────────────────────────────────────────────────────────
/** Returns true if `symbolName` appears in the file body (not in import declarations) NOT as enum access */
function isUsedAsType(content, symbolName) {
    // Strip import declarations before checking — the symbol name appears in the import itself
    // and we only care about usage in the code body.
    const withoutImports = content
        .split('\n')
        .filter((line) => !line.trimStart().startsWith('import '))
        .join('\n');
    // After pass 1, all MyEnum.Member accesses are replaced.
    // Any remaining occurrence of symbolName in non-import code is a type annotation.
    const regex = new RegExp(`\\b${symbolName}\\b(?!\\s*\\.)`);
    return regex.test(withoutImports);
}
function isSymbolImportedFrom(content, symbolName, fromModule) {
    const escapedModule = fromModule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const regex = new RegExp(`import\\s*\\{[^}]*\\b${symbolName}\\b[^}]*\\}\\s*from\\s*['"]${escapedModule}['"]`);
    return regex.test(content);
}
function ensureNamedImport(content, symbolName, fromModule) {
    if (isSymbolImportedFrom(content, symbolName, fromModule))
        return content;
    // Try to append to an existing import from the same module
    const escapedModule = fromModule.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const existingImportRegex = new RegExp(`(import\\s*\\{)([^}]*)(\\}\\s*from\\s*['"]${escapedModule}['"])`);
    const match = existingImportRegex.exec(content);
    if (match) {
        const newNames = match[2].trimEnd();
        const separator = newNames.endsWith(',') ? ' ' : ', ';
        return content.replace(match[0], `${match[1]}${newNames}${separator}${symbolName}${match[3]}`);
    }
    return (0, ast_utils_1.insertImportStatement)(content, symbolName, fromModule);
}
function ensureMultipleImports(content, symbols, fromModule) {
    let result = content;
    for (const sym of symbols) {
        result = ensureNamedImport(result, sym, fromModule);
    }
    return result;
}
// ─── BaseFieldComponentConfig input migration ─────────────────────────────────
function migrateBaseFieldInputs(content, filePath, context) {
    if (!content.includes('BaseFieldComponentConfig'))
        return content;
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    function visit(node) {
        if (ts.isClassDeclaration(node)) {
            const implementsBaseField = node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ImplementsKeyword &&
                clause.types.some((t) => t.expression.getText(sourceFile) === 'BaseFieldComponentConfig'));
            if (implementsBaseField) {
                for (const member of node.members) {
                    if (!ts.isPropertyDeclaration(member))
                        continue;
                    if (!hasInputDecorator(member))
                        continue;
                    if (!ts.isIdentifier(member.name))
                        continue;
                    if (!member.type)
                        continue;
                    const propName = member.name.text;
                    if (propName !== 'field' && propName !== 'formControl')
                        continue;
                    const typeStr = unwrapSignalType(member.type, sourceFile).trim();
                    const replacement = propName === 'field'
                        ? `field = input.required<${typeStr}>()`
                        : `formControl = input<${typeStr}>()`;
                    changes.push({
                        start: member.getStart(sourceFile),
                        end: member.getEnd(),
                        replacement,
                    });
                }
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
    if (changes.length === 0)
        return content;
    let updated = (0, ast_utils_1.applyChanges)(content, changes);
    // Add input() to @angular/core imports
    updated = ensureNamedImport(updated, 'input', '@angular/core');
    // Remove Input from @angular/core if no longer used as decorator
    if (!/@Input\b/.test(updated)) {
        const updatedSf = ts.createSourceFile('_temp.ts', updated, ts.ScriptTarget.Latest, true);
        const { changes: removeChanges } = (0, ast_utils_1.buildRemoveNamedImportsChanges)(updatedSf, ['Input'], '@angular/core');
        if (removeChanges.length > 0) {
            updated = (0, ast_utils_1.applyChanges)(updated, removeChanges);
        }
    }
    return updated;
}
function hasInputDecorator(node) {
    const decorators = ts.getDecorators(node);
    return (decorators?.some((d) => {
        const expr = ts.isCallExpression(d.expression) ? d.expression.expression : d.expression;
        return ts.isIdentifier(expr) && expr.text === 'Input';
    }) ?? false);
}
function unwrapSignalType(typeNode, sourceFile) {
    if (ts.isTypeReferenceNode(typeNode) &&
        typeNode.typeName.getText(sourceFile) === 'Signal' &&
        typeNode.typeArguments?.length === 1) {
        return typeNode.typeArguments[0].getText(sourceFile);
    }
    return typeNode.getText(sourceFile);
}
// ─── Field array detection helpers ────────────────────────────────────────────
/** All elements are plain objects with `name` + known `type` (no spreads). */
function areAllFieldObjects(elements, sourceFile) {
    if (elements.length === 0)
        return false;
    return elements.every((el) => {
        if (!ts.isObjectLiteralExpression(el))
            return false;
        let hasName = false;
        let hasValidType = false;
        for (const prop of el.properties) {
            if (!ts.isPropertyAssignment(prop))
                continue;
            const key = prop.name.getText(sourceFile);
            if (key === 'name')
                hasName = true;
            if (key === 'type' &&
                ts.isStringLiteral(prop.initializer) &&
                KNOWN_FIELD_TYPES.has(prop.initializer.text)) {
                hasValidType = true;
            }
        }
        return hasName && hasValidType;
    });
}
/** Array has spread elements/assignments AND at least one field-like object — lenient match. */
function looksLikeFieldArray(elements, sourceFile) {
    if (elements.length === 0)
        return false;
    let hasSpreads = false;
    let hasFieldLikeElement = false;
    for (const el of elements) {
        if (ts.isSpreadElement(el)) {
            hasSpreads = true;
        }
        else if (ts.isObjectLiteralExpression(el)) {
            let hasSpreadAssignment = false;
            let hasName = false;
            let hasValidType = false;
            for (const prop of el.properties) {
                if (ts.isSpreadAssignment(prop))
                    hasSpreadAssignment = true;
                if (!ts.isPropertyAssignment(prop))
                    continue;
                const key = prop.name.getText(sourceFile);
                if (key === 'name')
                    hasName = true;
                if (key === 'type' &&
                    ts.isStringLiteral(prop.initializer) &&
                    KNOWN_FIELD_TYPES.has(prop.initializer.text)) {
                    hasValidType = true;
                }
            }
            if (hasSpreadAssignment)
                hasSpreads = true;
            if (hasName || hasValidType)
                hasFieldLikeElement = true;
        }
    }
    return hasSpreads && hasFieldLikeElement;
}
// ─── Field definition migration ───────────────────────────────────────────────
function migrateFieldDefinitions(content, filePath, context) {
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    let needsDynamicTypeImport = false;
    const SPREAD_WARN = `${filePath}: Array com elementos spread detectado — adicionado "as DynamicType[]" para contornar type widening. Verifique se todos os elementos são realmente DynamicType.`;
    function visit(node) {
        // ── Variable declarations without type annotation ─────────────────────
        if (ts.isVariableDeclaration(node) && !node.type && node.initializer && ts.isIdentifier(node.name)) {
            const init = node.initializer;
            if (ts.isArrayLiteralExpression(init)) {
                if (areAllFieldObjects(init.elements, sourceFile)) {
                    changes.push({ start: node.name.getEnd(), end: node.name.getEnd(), replacement: ': DynamicType[]' });
                    needsDynamicTypeImport = true;
                }
                else if (looksLikeFieldArray(init.elements, sourceFile)) {
                    changes.push({ start: node.name.getEnd(), end: node.name.getEnd(), replacement: ': DynamicType[]' });
                    context.logger.warn(SPREAD_WARN);
                    needsDynamicTypeImport = true;
                }
            }
            else if (ts.isArrowFunction(init) && !init.type && ts.isArrayLiteralExpression(init.body)) {
                const arr = init.body;
                if (areAllFieldObjects(arr.elements, sourceFile)) {
                    changes.push({ start: arr.getEnd(), end: arr.getEnd(), replacement: ' as DynamicType[]' });
                    needsDynamicTypeImport = true;
                }
                else if (looksLikeFieldArray(arr.elements, sourceFile)) {
                    changes.push({ start: arr.getEnd(), end: arr.getEnd(), replacement: ' as DynamicType[]' });
                    context.logger.warn(SPREAD_WARN);
                    needsDynamicTypeImport = true;
                }
            }
        }
        // ── Function / method declarations without return type ────────────────
        if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) &&
            !node.type &&
            node.body) {
            const hasFieldReturn = node.body.statements.some((stmt) => ts.isReturnStatement(stmt) &&
                stmt.expression !== undefined &&
                ts.isArrayLiteralExpression(stmt.expression) &&
                (areAllFieldObjects(stmt.expression.elements, sourceFile) ||
                    looksLikeFieldArray(stmt.expression.elements, sourceFile)));
            if (hasFieldReturn) {
                const pos = node.body.getFullStart();
                changes.push({ start: pos, end: pos, replacement: ': DynamicType[]' });
                needsDynamicTypeImport = true;
            }
        }
        // ── Inline array in function call / constructor arguments ─────────────
        if (ts.isArrayLiteralExpression(node) && !ts.isAsExpression(node.parent)) {
            const parent = node.parent;
            const args = ts.isCallExpression(parent) ? parent.arguments :
                ts.isNewExpression(parent) ? (parent.arguments ?? []) :
                    null;
            if (args && [...args].some((arg) => arg === node)) {
                if (areAllFieldObjects(node.elements, sourceFile)) {
                    changes.push({ start: node.getEnd(), end: node.getEnd(), replacement: ' as DynamicType[]' });
                    needsDynamicTypeImport = true;
                }
                else if (looksLikeFieldArray(node.elements, sourceFile)) {
                    changes.push({ start: node.getEnd(), end: node.getEnd(), replacement: ' as DynamicType[]' });
                    context.logger.warn(SPREAD_WARN);
                    needsDynamicTypeImport = true;
                }
            }
        }
        // ── Object literal with type: 'enum'/'select' but no options ─────────
        if (ts.isObjectLiteralExpression(node)) {
            let typeValue;
            let hasOptions = false;
            for (const prop of node.properties) {
                if (!ts.isPropertyAssignment(prop))
                    continue;
                const key = prop.name.getText(sourceFile);
                if (key === 'type' && ts.isStringLiteral(prop.initializer)) {
                    const val = prop.initializer.text;
                    if (val === 'enum' || val === 'select')
                        typeValue = val;
                }
                if (key === 'options')
                    hasOptions = true;
            }
            if (typeValue && !hasOptions) {
                context.logger.warn(`${filePath}: Campo com type: '${typeValue}' sem a propriedade "options" — SelectFieldConfig requer "options" em v19. Adicione os valores corretos manualmente.`);
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
    let updated = changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
    if (needsDynamicTypeImport) {
        updated = ensureNamedImport(updated, 'DynamicType', DYNAMIC_FORM_MODULE);
    }
    return updated;
}
// ─── FormField heritage warning / fix ─────────────────────────────────────────
function warnAndFixFormFieldHeritage(content, filePath, context) {
    if (!content.includes('FormField'))
        return content;
    const sourceFile = ts.createSourceFile('_temp.ts', content, ts.ScriptTarget.Latest, true);
    const changes = [];
    function visit(node) {
        const isInterface = ts.isInterfaceDeclaration(node);
        const isClass = ts.isClassDeclaration(node);
        if (isInterface || isClass) {
            for (const clause of node.heritageClauses ?? []) {
                const typesArr = [...clause.types];
                for (const typeExpr of typesArr) {
                    if (typeExpr.expression.getText(sourceFile) !== 'FormField')
                        continue;
                    if (isInterface) {
                        if (typesArr.length === 1) {
                            // Remove the entire extends clause
                            changes.push({ start: clause.getFullStart(), end: clause.getEnd(), replacement: '' });
                        }
                        else {
                            const idx = typesArr.indexOf(typeExpr);
                            if (idx === 0) {
                                changes.push({ start: typeExpr.getStart(), end: typesArr[1].getStart(), replacement: '' });
                            }
                            else {
                                changes.push({ start: typesArr[idx - 1].getEnd(), end: typeExpr.getEnd(), replacement: '' });
                            }
                        }
                    }
                    else {
                        context.logger.warn(`${filePath}: Classe estende "FormField" (removido em v19). Remova "extends FormField", o uso de ConstructorParameters<typeof FormField> e as chamadas super() relacionados manualmente.`);
                    }
                }
            }
        }
        ts.forEachChild(node, visit);
    }
    visit(sourceFile);
    if (content.includes('ConstructorParameters') && content.includes('FormField')) {
        context.logger.warn(`${filePath}: Uso de ConstructorParameters<typeof FormField> detectado — FormField foi removido em v19. Revise e substitua manualmente.`);
    }
    return changes.length > 0 ? (0, ast_utils_1.applyChanges)(content, changes) : content;
}
//# sourceMappingURL=index.js.map