"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypeChecker = exports.TypeCheckErrorCode = void 0;
/**
 * Stable error codes for type checking diagnostics
 */
exports.TypeCheckErrorCode = {
    TypeMismatch: 'TYPE_NOT_ASSIGNABLE',
};
/**
 * Type checker for SomonScript AST
 * Provides comprehensive type checking with Tajik Cyrillic type annotations
 */
class TypeChecker {
    constructor(source) {
        this.errors = [];
        this.warnings = [];
        this.symbolTable = new Map();
        this.interfaceTable = new Map();
        this.typeAliasTable = new Map();
        this.sourceLines = [];
        this.sourceLines = source ? source.split(/\r?\n/) : [];
        this.initializePrimitiveTypes();
    }
    /**
     * Initialize primitive types in the symbol table
     */
    initializePrimitiveTypes() {
        this.symbolTable.set('сатр', { kind: 'primitive', name: 'string' });
        this.symbolTable.set('рақам', { kind: 'primitive', name: 'number' });
        this.symbolTable.set('мантиқӣ', { kind: 'primitive', name: 'boolean' });
        this.symbolTable.set('холӣ', { kind: 'primitive', name: 'null' });
    }
    getSnippet(line) {
        return this.sourceLines[line - 1] ?? '';
    }
    /**
     * Perform type checking on the entire program
     * @param program - The AST program to type check
     * @returns Type checking result with errors and warnings
     */
    check(program) {
        this.errors = [];
        this.warnings = [];
        // First pass: collect type definitions
        this.collectTypeDefinitions(program);
        // Second pass: type check statements
        for (const statement of program.body) {
            this.checkStatement(statement);
        }
        return {
            errors: this.errors,
            warnings: this.warnings,
        };
    }
    collectTypeDefinitions(program) {
        for (const statement of program.body) {
            if (statement.type === 'InterfaceDeclaration') {
                this.collectInterface(statement);
            }
            else if (statement.type === 'TypeAlias') {
                this.collectTypeAlias(statement);
            }
            else if (statement.type === 'ClassDeclaration') {
                this.collectClass(statement);
            }
        }
    }
    collectInterface(interfaceDecl) {
        const properties = new Map();
        for (const prop of interfaceDecl.body.properties) {
            const propType = this.resolveTypeNode(prop.typeAnnotation.typeAnnotation);
            properties.set(prop.key.name, {
                type: propType,
                optional: prop.optional,
            });
        }
        const interfaceType = {
            kind: 'interface',
            name: interfaceDecl.name.name,
            properties,
        };
        this.interfaceTable.set(interfaceDecl.name.name, interfaceType);
    }
    collectTypeAlias(typeAlias) {
        const aliasType = this.resolveTypeNode(typeAlias.typeAnnotation.typeAnnotation);
        this.typeAliasTable.set(typeAlias.name.name, aliasType);
    }
    collectClass(classDecl) {
        // Register the class type in the symbol table during collection phase
        const classType = {
            kind: 'class',
            name: classDecl.name.name,
        };
        this.symbolTable.set(classDecl.name.name, classType);
    }
    checkStatement(statement) {
        switch (statement.type) {
            case 'VariableDeclaration':
                this.checkVariableDeclaration(statement);
                break;
            case 'FunctionDeclaration':
                this.checkFunctionDeclaration(statement);
                break;
            case 'ClassDeclaration':
                this.checkClassDeclaration(statement);
                break;
            // Add more statement types as needed
        }
    }
    checkVariableDeclaration(varDecl) {
        let declaredType;
        // Get declared type if present
        if (varDecl.typeAnnotation) {
            const annotation = varDecl.typeAnnotation;
            declaredType = this.resolveTypeNode(annotation.typeAnnotation);
        }
        // Get inferred type from initializer
        let inferredType;
        if (varDecl.init) {
            inferredType = this.inferExpressionType(varDecl.init, declaredType);
        }
        // Type checking
        if (declaredType && inferredType) {
            if (!this.isAssignable(inferredType, declaredType)) {
                this.addError(exports.TypeCheckErrorCode.TypeMismatch, `Type '${this.typeToString(inferredType)}' is not assignable to type '${this.typeToString(declaredType)}'`, varDecl.line, varDecl.column);
            }
        }
        // Store variable type in symbol table
        const finalType = declaredType || inferredType;
        if (finalType) {
            if (varDecl.identifier.type === 'Identifier') {
                this.symbolTable.set(varDecl.identifier.name, finalType);
            }
            else {
                this.bindPatternTypes(varDecl.identifier, finalType);
            }
        }
    }
    bindPatternTypes(pattern, type) {
        if (pattern.type === 'Identifier') {
            this.symbolTable.set(pattern.name, type);
            return;
        }
        if (pattern.type === 'ArrayPattern' && type.elementType) {
            pattern.elements.forEach(element => {
                if (element) {
                    this.bindPatternTypes(element, type.elementType);
                }
            });
            return;
        }
        if (pattern.type === 'ObjectPattern' && type.properties) {
            for (const prop of pattern.properties) {
                if (prop.type !== 'PropertyPattern')
                    continue;
                const keyName = prop.key.type === 'Identifier' ? prop.key.name : String(prop.key.value);
                const propType = type.properties.get(keyName);
                if (propType) {
                    this.bindPatternTypes(prop.value, propType.type);
                }
            }
        }
    }
    checkFunctionDeclaration(funcDecl) {
        // Create new scope for function parameters
        const savedSymbols = new Map(this.symbolTable);
        // Add parameters to symbol table
        for (const param of funcDecl.params) {
            if (param.typeAnnotation) {
                const annotation = param.typeAnnotation;
                const paramType = this.resolveTypeNode(annotation.typeAnnotation);
                this.symbolTable.set(param.name.name, paramType);
            }
        }
        // Check function body (simplified - would need full statement checking)
        // For now, just restore symbol table
        this.symbolTable = savedSymbols;
        // Store function type with return type
        const returnTypeAnnotation = funcDecl.returnType;
        const returnType = returnTypeAnnotation
            ? this.resolveTypeNode(returnTypeAnnotation.typeAnnotation)
            : { kind: 'unknown' };
        const functionType = {
            kind: 'function',
            name: funcDecl.name.name,
            returnType: returnType,
        };
        this.symbolTable.set(funcDecl.name.name, functionType);
    }
    checkClassDeclaration(classDecl) {
        // Register the class type in the symbol table
        const classType = {
            kind: 'class',
            name: classDecl.name.name,
        };
        this.symbolTable.set(classDecl.name.name, classType);
    }
    resolveTypeNode(typeNode) {
        switch (typeNode.type) {
            case 'PrimitiveType':
                return this.resolvePrimitiveType(typeNode);
            case 'ArrayType':
                return this.resolveArrayType(typeNode);
            case 'UnionType':
                return this.resolveUnionType(typeNode);
            case 'IntersectionType':
                return this.resolveIntersectionType(typeNode);
            case 'TupleType':
                return this.resolveTupleType(typeNode);
            case 'GenericType':
                return this.resolveGenericType(typeNode);
            case 'UniqueType':
                return this.resolveUniqueType(typeNode);
            case 'Identifier':
                return this.resolveIdentifierType(typeNode);
            default:
                return { kind: 'unknown' };
        }
    }
    resolvePrimitiveType(primitiveType) {
        return { kind: 'primitive', name: this.mapTajikToPrimitive(primitiveType.name) };
    }
    resolveArrayType(arrayType) {
        return {
            kind: 'array',
            elementType: this.resolveTypeNode(arrayType.elementType),
        };
    }
    resolveUnionType(unionType) {
        return {
            kind: 'union',
            types: unionType.types.map(t => this.resolveTypeNode(t)),
        };
    }
    resolveIntersectionType(intersectionType) {
        return {
            kind: 'intersection',
            types: intersectionType.types.map(t => this.resolveTypeNode(t)),
        };
    }
    resolveTupleType(tupleType) {
        return {
            kind: 'tuple',
            types: tupleType.elementTypes.map(t => this.resolveTypeNode(t)),
        };
    }
    resolveGenericType(genericType) {
        return this.resolveNamedType(genericType.name.name);
    }
    resolveUniqueType(uniqueType) {
        return { kind: 'unique', baseType: this.resolveTypeNode(uniqueType.baseType) };
    }
    resolveIdentifierType(identifierType) {
        return this.resolveNamedType(identifierType.name);
    }
    resolveNamedType(typeName) {
        // Check if it's a known interface or type alias
        const interfaceType = this.interfaceTable.get(typeName);
        if (interfaceType) {
            return interfaceType;
        }
        const aliasType = this.typeAliasTable.get(typeName);
        if (aliasType) {
            return aliasType;
        }
        // Check if it's a class type
        const classType = this.symbolTable.get(typeName);
        if (classType && classType.kind === 'class') {
            return classType;
        }
        // Unknown type
        return { kind: 'unknown', name: typeName };
    }
    mapTajikToPrimitive(tajikType) {
        switch (tajikType) {
            case 'сатр':
                return 'string';
            case 'рақам':
                return 'number';
            case 'мантиқӣ':
                return 'boolean';
            case 'холӣ':
                return 'null';
            default:
                return 'unknown';
        }
    }
    inferExpressionType(expression, targetType) {
        switch (expression.type) {
            case 'Literal':
                return this.inferLiteralType(expression);
            case 'Identifier':
                return this.inferIdentifierType(expression);
            case 'ArrayExpression':
                return { kind: 'array', elementType: { kind: 'unknown' } };
            case 'ObjectExpression':
                return this.inferObjectType(expression, targetType);
            case 'CallExpression':
                return this.inferCallType(expression);
            case 'NewExpression':
                return this.inferNewExpressionType(expression);
            default:
                return { kind: 'unknown' };
        }
    }
    inferLiteralType(literal) {
        if (typeof literal.value === 'string') {
            return { kind: 'primitive', name: 'string' };
        }
        else if (typeof literal.value === 'number') {
            return { kind: 'primitive', name: 'number' };
        }
        else if (typeof literal.value === 'boolean') {
            return { kind: 'primitive', name: 'boolean' };
        }
        else if (literal.value === null) {
            return { kind: 'primitive', name: 'null' };
        }
        return { kind: 'unknown' };
    }
    inferIdentifierType(identifier) {
        return this.symbolTable.get(identifier.name) || { kind: 'unknown' };
    }
    inferObjectType(objExpr, targetType) {
        // If we have a target interface type, use it
        if (targetType && targetType.kind === 'interface') {
            return targetType;
        }
        // Otherwise, infer object type from properties
        const properties = new Map();
        if (objExpr.properties) {
            for (const prop of objExpr.properties) {
                if (prop.key && prop.value) {
                    const keyName = prop.key.type === 'Identifier'
                        ? prop.key.name
                        : String(prop.key.value);
                    const valueType = this.inferExpressionType(prop.value);
                    properties.set(keyName, {
                        type: valueType,
                        optional: false,
                    });
                }
            }
        }
        return { kind: 'object', properties: properties };
    }
    inferCallType(callExpr) {
        if (callExpr.callee && callExpr.callee.type === 'Identifier') {
            const functionName = callExpr.callee.name;
            const functionType = this.symbolTable.get(functionName);
            if (functionType && functionType.kind === 'function' && functionType.returnType) {
                return functionType.returnType;
            }
        }
        return { kind: 'unknown' };
    }
    inferNewExpressionType(newExpr) {
        if (newExpr.callee && newExpr.callee.type === 'Identifier') {
            const className = newExpr.callee.name;
            const classType = this.symbolTable.get(className);
            if (classType && classType.kind === 'class') {
                // Return an instance type of the class
                return { kind: 'class', name: className };
            }
        }
        return { kind: 'unknown' };
    }
    isAssignable(source, target) {
        if (this.isExactMatch(source, target)) {
            return true;
        }
        if (this.isArrayAssignable(source, target)) {
            return true;
        }
        if (this.isTupleAssignable(source, target)) {
            return true;
        }
        if (this.isArrayToTupleAssignable(source, target)) {
            return true;
        }
        if (this.isUnionAssignable(source, target)) {
            return true;
        }
        if (this.isIntersectionAssignable(source, target)) {
            return true;
        }
        if (this.isInterfaceAssignable(source, target)) {
            return true;
        }
        if (this.isClassAssignable(source, target)) {
            return true;
        }
        if (this.isUniqueAssignable(source, target)) {
            return true;
        }
        return false;
    }
    isExactMatch(source, target) {
        return source.kind === target.kind && source.name === target.name;
    }
    isArrayAssignable(source, target) {
        if (source.kind !== 'array' || target.kind !== 'array') {
            return false;
        }
        return source.elementType && target.elementType
            ? this.isAssignable(source.elementType, target.elementType)
            : false;
    }
    isTupleAssignable(source, target) {
        if (source.kind !== 'tuple' || target.kind !== 'tuple') {
            return false;
        }
        if (!source.types || !target.types || source.types.length !== target.types.length) {
            return false;
        }
        return source.types.every((sourceType, index) => this.isAssignable(sourceType, target.types[index]));
    }
    isArrayToTupleAssignable(source, target) {
        return source.kind === 'array' && target.kind === 'tuple';
    }
    isUnionAssignable(source, target) {
        if (target.kind === 'union' && target.types) {
            return target.types.some(t => this.isAssignable(source, t));
        }
        if (source.kind === 'union' && source.types) {
            return source.types.every(t => this.isAssignable(t, target));
        }
        return false;
    }
    isIntersectionAssignable(source, target) {
        if (target.kind === 'intersection' && target.types) {
            return target.types.every(t => this.isAssignable(source, t));
        }
        if (source.kind === 'intersection' && source.types) {
            return source.types.some(t => this.isAssignable(t, target));
        }
        return false;
    }
    isInterfaceAssignable(source, target) {
        if (target.kind !== 'interface') {
            return false;
        }
        if (source.kind === 'interface' || source.kind === 'object') {
            return this.isStructurallyCompatible(source, target);
        }
        return false;
    }
    isClassAssignable(source, target) {
        return source.kind === 'class' && target.kind === 'class' && source.name === target.name;
    }
    isUniqueAssignable(source, target) {
        if (source.kind === 'unique' && target.kind === 'unique') {
            return this.isAssignable(source.baseType, target.baseType);
        }
        if (source.kind === 'unique' || target.kind === 'unique') {
            return false;
        }
        return false;
    }
    typeToString(type) {
        switch (type.kind) {
            case 'primitive':
                return this.mapPrimitiveToTajik(type.name || 'unknown');
            case 'array':
                return `${this.typeToString(type.elementType)}[]`;
            case 'union':
                return type.types.map(t => this.typeToString(t)).join(' | ');
            case 'intersection':
                return type.types.map(t => this.typeToString(t)).join(' & ');
            case 'interface':
                return type.name || 'interface';
            case 'tuple':
                return `[${type.types.map(t => this.typeToString(t)).join(', ')}]`;
            case 'literal':
                return typeof type.name === 'string' ? `"${type.name}"` : String(type.name);
            case 'class':
                return type.name || 'class';
            case 'unique':
                return `беназир ${this.typeToString(type.baseType)}`;
            default:
                return type.name || 'unknown';
        }
    }
    isStructurallyCompatible(source, target) {
        if (!source.properties || !target.properties) {
            return false;
        }
        // Check if source has all required properties of target
        for (const [propName, targetProp] of target.properties) {
            const sourceProp = source.properties.get(propName);
            // Required property missing
            if (!sourceProp && !targetProp.optional) {
                return false;
            }
            // Property exists, check type compatibility
            if (sourceProp && !this.isAssignable(sourceProp.type, targetProp.type)) {
                return false;
            }
        }
        return true;
    }
    mapPrimitiveToTajik(primitiveType) {
        switch (primitiveType) {
            case 'string':
                return 'сатр';
            case 'number':
                return 'рақам';
            case 'boolean':
                return 'мантиқӣ';
            case 'null':
                return 'холӣ';
            default:
                return primitiveType;
        }
    }
    addError(code, message, line, column) {
        this.errors.push({
            code,
            message,
            line,
            column,
            snippet: this.getSnippet(line),
            severity: 'error',
        });
    }
    addWarning(code, message, line, column) {
        this.warnings.push({
            code,
            message,
            line,
            column,
            snippet: this.getSnippet(line),
            severity: 'warning',
        });
    }
}
exports.TypeChecker = TypeChecker;
//# sourceMappingURL=type-checker.js.map