/**
 * Type definitions for Acurity Architect language.
 */

/**
 * Acurity primitive types.
 */
const AcurityTypes = {
    STRING: "STRING",
    SHORT: "SHORT",
    LONG: "LONG",
    INTEGER: "INTEGER",
    BIGINT: "BIGINT",
    DOUBLE: "DOUBLE",
    BOOLEAN: "BOOLEAN",
    DATE: "DATE",
    TIME: "TIME",
    TIMESTAMP: "TIMESTAMP",
    UNKNOWN: "UNKNOWN"
};

/**
 * Naming convention prefixes mapped to types.
 */
const NamingPrefixes = {
    z: AcurityTypes.STRING,
    s: AcurityTypes.SHORT,
    l: AcurityTypes.LONG,
    n: AcurityTypes.BIGINT,
    f: AcurityTypes.DOUBLE,
    b: AcurityTypes.BOOLEAN,
    d: AcurityTypes.DATE,
    t: AcurityTypes.TIME,
    k: AcurityTypes.TIMESTAMP,
    g: "GLOBAL",          // Global/constant prefix
    h: "DATABASE_FIELD"   // Database field prefix
};

/**
 * Inverse of NamingPrefixes — maps primitive type → single-character prefix.
 * Derived automatically so the two never diverge.
 */
const TypeToPrefix = Object.fromEntries(
    Object.entries(NamingPrefixes)
        .filter(([k, v]) => k.length === 1 && !v.startsWith("GLOBAL") && v !== "DATABASE_FIELD")
        .map(([k, v]) => [v, k])
);

/**
 * Gets the expected type from a variable name prefix.
 * @param {string} variableName - The variable name
 * @returns {string|null} - The expected type or null
 */
function getExpectedTypeFromName(variableName) {
    const oneCharPrefix = variableName.substring(0, 1).toLowerCase();
    const twoCharPrefix = variableName.substring(0, 2).toLowerCase();

    // Derive global two-char prefix (g + type char) from single-char entries
    if (oneCharPrefix === "g" && twoCharPrefix.length === 2) {
        const baseType = NamingPrefixes[/** @type {keyof typeof NamingPrefixes} */ (twoCharPrefix[1])];
        if (baseType && baseType !== "GLOBAL" && baseType !== "DATABASE_FIELD") {
            return baseType;
        }
    }

    // Check single-character prefix
    const mappedType = NamingPrefixes[/** @type {keyof typeof NamingPrefixes} */ (oneCharPrefix)];
    if (mappedType && mappedType !== "GLOBAL" && mappedType !== "DATABASE_FIELD") {
        return mappedType;
    }

    return null;
}

/**
 * Gets the recommended prefix for a variable based on its type and scope.
 * Prefixes are applied in order of scope: global (g), function parameter (p), function-scoped (f),
 * followed by a type character (z=string, s=short, l=long, f=double, b=boolean, d=date, t=time).
 * 
 * Examples:
 * - getRecommendedPrefix("STRING", true) → "gz" (global string)
 * - getRecommendedPrefix("STRING", false, true) → "pz" (parameter string)
 * - getRecommendedPrefix("DOUBLE", false, false, true) → "ff" (function-scoped double)
 * - getRecommendedPrefix("SHORT") → "s" (local short)
 * 
 * @param {string} type - The variable type (STRING, SHORT, LONG, DOUBLE, BOOLEAN, DATE, TIME)
 * @param {boolean} isGlobal - Whether the variable is global/constant
 * @param {boolean} isFunctionParameter - Whether the variable is a function parameter
 * @param {boolean} isFunctionScoped - Whether the variable is scoped to a function (but not a parameter)
 * @returns {string} - The recommended prefix for the variable
 */
function getRecommendedPrefix(type, isGlobal = false, isFunctionParameter = false, isFunctionScoped = false) {
    const typeChar = TypeToPrefix[type] || "x";

    // Build scope prefix
    let scopePrefix = "";
    if (isGlobal) {
        scopePrefix = "g";
    } else if (isFunctionParameter) {
        scopePrefix = "p";
    } else if (isFunctionScoped) {
        scopePrefix = "f";
    }

    return scopePrefix + typeChar;
}

/**
 * Checks if two types are compatible for assignment.
 * @param {string} targetType - The target type
 * @param {string} sourceType - The source type
 * @returns {boolean} - True if compatible
 */
function areTypesCompatible(targetType, sourceType) {
    if (targetType === sourceType) {
        return true;
    }

    if (targetType === AcurityTypes.UNKNOWN || sourceType === AcurityTypes.UNKNOWN) {
        return true; // Allow unknown types for flexibility
    }

    if(targetType === AcurityTypes.TIMESTAMP && sourceType === AcurityTypes.DATE) {
        return true; // Allow assignment from DATE to TIMESTAMP.
    }

    // Numeric type compatibility
    const numericTypes = [
        AcurityTypes.SHORT,
        AcurityTypes.LONG,
        AcurityTypes.INTEGER,
        AcurityTypes.BIGINT,
        AcurityTypes.DOUBLE
    ];
    if (numericTypes.includes(targetType) && numericTypes.includes(sourceType)) {
        return true; // Allow numeric conversions
    }

    return false;
}
export {
    AcurityTypes,
    TypeToPrefix,
    getExpectedTypeFromName,
    getRecommendedPrefix,
    areTypesCompatible
};
