import { ImperativeLanguageExporter, toSnakeCase, escapeString } from '../LanguageExporter.js';

/* ────────────────────────────────────────────────────────────────
   GDScript Exporter — Godot 4.x Script Generation
   
   Exports visual scripts to GDScript for Godot game engine.
   Generates Node-derived scripts with proper Godot lifecycle.
──────────────────────────────────────────────────────────────── */
/* ── GDScript Type Mapping ────────────────────────────────────── */
const gdscriptTypeMapping = {
    void: 'void',
    bool: 'bool',
    int: 'int',
    float: 'float',
    string: 'String',
    vector2: 'Vector2',
    vector3: 'Vector3',
    color: 'Color',
    entity: 'Node',
    array: 'Array',
    dictionary: 'Dictionary',
    any: 'Variant',
};
/* ── GDScript Exporter ────────────────────────────────────────── */
class GDScriptExporter extends ImperativeLanguageExporter {
    constructor(config = {}) {
        super({
            targetEngine: 'godot',
            engineVersion: '4.2',
            indent: '\t', // GDScript convention is tabs
            ...config,
        });
        this.languageName = 'gdscript';
        this.fileExtension = 'gd';
        this.typeMapping = gdscriptTypeMapping;
        this.statementTerminator = '';
        this.breakKeyword = 'break';
        this.continueKeyword = 'continue';
    }
    /* ── Main Generation ────────────────────────────────────────── */
    generateScript(script) {
        var _a;
        const builder = this.createBuilder();
        // Each class becomes a separate file in Godot, but we'll generate
        // the primary class inline
        const mainClass = script.classes[0];
        if (!mainClass) {
            this.addError('No class found in script');
            return '';
        }
        // File header comment
        if (script.headerComment) {
            builder.comment(script.headerComment.split('\n').join('\n# ').replace(/^/, ''));
        }
        // Class name declaration (optional in Godot 4)
        builder.line(`class_name ${mainClass.name}`);
        // Extends - use base class or default to Node
        const baseClass = mainClass.baseClass || this.config.baseClassName || 'Node';
        builder.line(`extends ${baseClass}`);
        builder.emptyLine();
        // Signals first (Godot convention)
        for (const signal of mainClass.signals || []) {
            this.generateSignal(signal, builder);
        }
        if ((_a = mainClass.signals) === null || _a === void 0 ? void 0 : _a.length) {
            builder.emptyLine();
        }
        // Enums
        for (const en of mainClass.enums || []) {
            this.generateEnum(en, builder);
            builder.emptyLine();
        }
        // Exported fields first (Godot convention for inspector)
        const exportedFields = mainClass.fields.filter((f) => f.isExported);
        const normalFields = mainClass.fields.filter((f) => !f.isExported);
        for (const field of exportedFields) {
            this.generateField(field, builder);
        }
        if (exportedFields.length && normalFields.length) {
            builder.emptyLine();
        }
        for (const field of normalFields) {
            this.generateField(field, builder);
        }
        if (normalFields.length) {
            builder.emptyLine();
        }
        // Methods
        for (let i = 0; i < mainClass.methods.length; i++) {
            this.generateMethod(mainClass.methods[i], builder);
            if (i < mainClass.methods.length - 1) {
                builder.emptyLine();
            }
        }
        return builder.toString();
    }
    generateSignal(signal, builder) {
        if (signal.params.length === 0) {
            builder.line(`signal ${toSnakeCase(signal.name)}`);
        }
        else {
            const params = signal.params.map((p) => toSnakeCase(p.name)).join(', ');
            builder.line(`signal ${toSnakeCase(signal.name)}(${params})`);
        }
    }
    generateEnum(en, builder) {
        builder.line(`enum ${en.name} {`);
        builder.indent();
        const lines = [];
        for (const member of en.members) {
            const valuePart = member.value !== undefined ? ` = ${member.value}` : '';
            lines.push(`${member.name}${valuePart}`);
        }
        builder.line(lines.join(', '));
        builder.dedent();
        builder.line('}');
    }
    generateClass(_cls, _builder) {
        // GDScript doesn't have explicit class blocks in the same file
        // Each class is a separate .gd file, handled in generateScript
    }
    generateField(field, builder) {
        if (field.comment) {
            builder.comment(field.comment);
        }
        const prefix = field.isExported ? '@export ' : '';
        const staticMod = field.isStatic ? 'static ' : '';
        const constKeyword = field.isConst ? 'const ' : 'var ';
        // Type hint
        const typeHint = field.fieldType && field.fieldType.type !== 'any' ? `: ${this.mapType(field.fieldType)}` : '';
        // Initializer
        const init = field.initializer ? ` = ${this.generateExpression(field.initializer)}` : '';
        // Godot 4 export with type hints
        if (field.isExported && field.fieldType) {
            const type = this.mapType(field.fieldType);
            builder.line(`@export var ${toSnakeCase(field.name)}: ${type}${init}`);
        }
        else {
            builder.line(`${prefix}${staticMod}${constKeyword}${toSnakeCase(field.name)}${typeHint}${init}`);
        }
    }
    generateProperty() {
        // GDScript 4 uses setget inline or get/set keywords
        // Implement if needed
    }
    generateMethod(method, builder) {
        const name = this.convertMethodName(method.name);
        const staticMod = method.isStatic ? 'static ' : '';
        // Parameters
        const params = method.params.map((p) => this.formatParameter(p)).join(', ');
        // Return type
        const returnType = method.returnType.type !== 'void' ? ` -> ${this.mapType(method.returnType)}` : '';
        builder.line(`${staticMod}func ${name}(${params})${returnType}:`);
        builder.indent();
        if (method.body.length === 0) {
            builder.line('pass');
        }
        else {
            for (const stmt of method.body) {
                this.generateStatement(stmt, builder);
            }
        }
        builder.dedent();
    }
    convertMethodName(name) {
        // Convert Unity/common lifecycle methods to Godot equivalents
        const methodMap = {
            Start: '_ready',
            Awake: '_init',
            Update: '_process',
            FixedUpdate: '_physics_process',
            OnDestroy: '_exit_tree',
            OnEnable: '_enter_tree',
        };
        return methodMap[name] || toSnakeCase(name);
    }
    formatParameter(param) {
        const name = toSnakeCase(param.name);
        const typeHint = param.paramType.type !== 'any' ? `: ${this.mapType(param.paramType)}` : '';
        const defaultVal = param.defaultValue
            ? ` = ${this.generateExpression(param.defaultValue)}`
            : '';
        return `${name}${typeHint}${defaultVal}`;
    }
    /* ── Statement Generation ───────────────────────────────────── */
    generateVarDecl(decl, builder) {
        const keyword = decl.isConst ? 'const ' : 'var ';
        const name = toSnakeCase(decl.name);
        const typeHint = decl.varType && decl.varType.type !== 'any' ? `: ${this.mapType(decl.varType)}` : '';
        const init = decl.initializer ? ` = ${this.generateExpression(decl.initializer)}` : '';
        builder.line(`${keyword}${name}${typeHint}${init}`);
    }
    generateAssignment(assign, builder) {
        const target = this.generateExpression(assign.target);
        const value = this.generateExpression(assign.value);
        builder.line(`${target} ${assign.operator} ${value}`);
    }
    generateIf(stmt, builder) {
        var _a;
        const cond = this.generateExpression(stmt.condition);
        builder.line(`if ${cond}:`);
        builder.indent();
        if (stmt.thenBranch.length === 0) {
            builder.line('pass');
        }
        else {
            for (const s of stmt.thenBranch) {
                this.generateStatement(s, builder);
            }
        }
        builder.dedent();
        for (const elseIf of stmt.elseIfs || []) {
            builder.line(`elif ${this.generateExpression(elseIf.condition)}:`);
            builder.indent();
            if (elseIf.body.length === 0) {
                builder.line('pass');
            }
            else {
                for (const s of elseIf.body) {
                    this.generateStatement(s, builder);
                }
            }
            builder.dedent();
        }
        if ((_a = stmt.elseBranch) === null || _a === void 0 ? void 0 : _a.length) {
            builder.line('else:');
            builder.indent();
            for (const s of stmt.elseBranch) {
                this.generateStatement(s, builder);
            }
            builder.dedent();
        }
    }
    generateWhile(stmt, builder) {
        const cond = this.generateExpression(stmt.condition);
        builder.line(`while ${cond}:`);
        builder.indent();
        if (stmt.body.length === 0) {
            builder.line('pass');
        }
        else {
            for (const s of stmt.body) {
                this.generateStatement(s, builder);
            }
        }
        builder.dedent();
    }
    generateFor(stmt, builder) {
        // GDScript doesn't have C-style for, convert to while
        if (stmt.init) {
            this.generateStatement(stmt.init, builder);
        }
        const cond = stmt.condition ? this.generateExpression(stmt.condition) : 'true';
        builder.line(`while ${cond}:`);
        builder.indent();
        for (const s of stmt.body) {
            this.generateStatement(s, builder);
        }
        if (stmt.update) {
            builder.line(this.generateExpression(stmt.update));
        }
        builder.dedent();
    }
    generateForEach(stmt, builder) {
        const iterable = this.generateExpression(stmt.iterable);
        const variable = toSnakeCase(stmt.variable);
        builder.line(`for ${variable} in ${iterable}:`);
        builder.indent();
        if (stmt.body.length === 0) {
            builder.line('pass');
        }
        else {
            for (const s of stmt.body) {
                this.generateStatement(s, builder);
            }
        }
        builder.dedent();
    }
    generateReturn(stmt, builder) {
        if (stmt.value) {
            builder.line(`return ${this.generateExpression(stmt.value)}`);
        }
        else {
            builder.line('return');
        }
    }
    generateSwitch(stmt, builder) {
        // GDScript uses match instead of switch
        const expr = this.generateExpression(stmt.expression);
        builder.line(`match ${expr}:`);
        builder.indent();
        for (const c of stmt.cases) {
            builder.line(`${this.generateExpression(c.value)}:`);
            builder.indent();
            if (c.body.length === 0) {
                builder.line('pass');
            }
            else {
                for (const s of c.body) {
                    this.generateStatement(s, builder);
                }
            }
            builder.dedent();
        }
        if (stmt.defaultCase) {
            builder.line('_:');
            builder.indent();
            for (const s of stmt.defaultCase) {
                this.generateStatement(s, builder);
            }
            builder.dedent();
        }
        builder.dedent();
    }
    generateTry(stmt, builder) {
        // GDScript doesn't have try/catch, use a workaround or warning
        this.addWarning('GDScript does not support try/catch. Using guard pattern instead.');
        // Generate try block directly
        for (const s of stmt.tryBlock) {
            this.generateStatement(s, builder);
        }
    }
    /* ── Expression Generation ──────────────────────────────────── */
    generateLiteral(lit) {
        var _a;
        switch (lit.valueType) {
            case 'bool':
                return lit.value ? 'true' : 'false';
            case 'string':
                return `"${escapeString(String(lit.value))}"`;
            case 'float':
                return String(lit.value);
            case 'int':
                return String(lit.value);
            case 'vector2': {
                const v = lit.value;
                return `Vector2(${v.x}, ${v.y})`;
            }
            case 'vector3': {
                const v = lit.value;
                return `Vector3(${v.x}, ${v.y}, ${v.z})`;
            }
            case 'color': {
                const c = lit.value;
                return `Color(${c.r}, ${c.g}, ${c.b}, ${(_a = c.a) !== null && _a !== void 0 ? _a : 1})`;
            }
            default:
                return lit.value === null ? 'null' : String(lit.value);
        }
    }
    generateBinaryOp(op) {
        const left = this.generateExpression(op.left);
        const right = this.generateExpression(op.right);
        // GDScript uses 'and', 'or', 'not' for boolean ops
        let operator = op.operator;
        switch (op.operator) {
            case '&&':
                operator = 'and';
                break;
            case '||':
                operator = 'or';
                break;
        }
        return `(${left} ${operator} ${right})`;
    }
    generateUnaryOp(op) {
        const operand = this.generateExpression(op.operand);
        let operator = op.operator;
        // GDScript uses 'not' instead of '!'
        if (op.operator === '!') {
            operator = 'not ';
        }
        return op.prefix ? `${operator}${operand}` : `${operand}${operator}`;
    }
    generateCall(call) {
        const args = call.args.map((a) => this.generateExpression(a)).join(', ');
        const funcName = toSnakeCase(call.callee);
        return `${funcName}(${args})`;
    }
    generateMethodCall(call) {
        const obj = this.generateExpression(call.object);
        const args = call.args.map((a) => this.generateExpression(a)).join(', ');
        const methodName = toSnakeCase(call.method);
        return `${obj}.${methodName}(${args})`;
    }
    generatePropertyAccess(access) {
        const obj = this.generateExpression(access.object);
        return `${obj}.${toSnakeCase(access.property)}`;
    }
    generateIndexAccess(access) {
        const obj = this.generateExpression(access.object);
        const idx = this.generateExpression(access.index);
        return `${obj}[${idx}]`;
    }
    generateConditional(cond) {
        const condition = this.generateExpression(cond.condition);
        const ifTrue = this.generateExpression(cond.ifTrue);
        const ifFalse = this.generateExpression(cond.ifFalse);
        // GDScript ternary syntax
        return `${ifTrue} if ${condition} else ${ifFalse}`;
    }
    generateArrayLiteral(arr) {
        const elements = arr.elements.map((e) => this.generateExpression(e)).join(', ');
        return `[${elements}]`;
    }
    generateDictionaryLiteral(dict) {
        const entries = dict.entries
            .map((e) => `${this.generateExpression(e.key)}: ${this.generateExpression(e.value)}`)
            .join(', ');
        return `{${entries}}`;
    }
    generateCast(cast) {
        const expr = this.generateExpression(cast.expression);
        const type = this.mapType(cast.targetType);
        // GDScript 4 cast syntax
        return `${expr} as ${type}`;
    }
    generateLambda(lambda) {
        const params = lambda.params.map((p) => toSnakeCase(p.name)).join(', ');
        if (Array.isArray(lambda.body)) {
            // GDScript doesn't support multi-line lambdas well
            this.addWarning('Multi-statement lambdas not supported in GDScript, using Callable');
            return `func(${params}): pass`;
        }
        else {
            const body = this.generateExpression(lambda.body);
            return `func(${params}): return ${body}`;
        }
    }
    generateNew(n) {
        const args = n.args.map((a) => this.generateExpression(a)).join(', ');
        return `${n.typeName}.new(${args})`;
    }
    /* ── GDScript-specific Helpers ──────────────────────────────── */
    formatArrayType(elementType) {
        // Godot 4 typed arrays
        return `Array[${elementType}]`;
    }
    formatDictType(_keyType, _valueType) {
        // Godot dictionaries aren't typed
        return 'Dictionary';
    }
    formatNullableType(baseType) {
        // GDScript doesn't have explicit nullable
        return baseType;
    }
}

export { GDScriptExporter };
//# sourceMappingURL=GDScriptExporter.js.map
