/**
 * PHP language provider.
 *
 * PHP uses named imports (use statements for classes/functions/constants),
 * and standard export/import resolution. PHP files can use a variety of
 * extensions from legacy versions through modern PHP 8.
 */
import { SupportedLanguages } from '../../../_shared/index.js';
import { createClassExtractor } from '../class-extractors/generic.js';
import { phpClassConfig } from '../class-extractors/configs/php.js';
import { defineLanguage } from '../language-provider.js';
import { typeConfig as phpConfig } from '../type-extractors/php.js';
import { phpExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
import { phpImportConfig } from '../import-resolvers/configs/php.js';
import { extractPhpNamedBindings } from '../named-bindings/php.js';
import { PHP_QUERIES } from '../tree-sitter-queries.js';
import { findDescendant, extractStringContent } from '../utils/ast-helpers.js';
import { createFieldExtractor } from '../field-extractors/generic.js';
import { phpConfig as phpFieldConfig } from '../field-extractors/configs/php.js';
import { createMethodExtractor } from '../method-extractors/generic.js';
import { phpMethodConfig } from '../method-extractors/configs/php.js';
import { createVariableExtractor } from '../variable-extractors/generic.js';
import { phpVariableConfig } from '../variable-extractors/configs/php.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { phpCallConfig } from '../call-extractors/configs/php.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
const BUILT_INS = new Set([
    'echo',
    'isset',
    'empty',
    'unset',
    'list',
    'array',
    'compact',
    'extract',
    'count',
    'strlen',
    'strpos',
    'strrpos',
    'substr',
    'strtolower',
    'strtoupper',
    'trim',
    'ltrim',
    'rtrim',
    'str_replace',
    'str_contains',
    'str_starts_with',
    'str_ends_with',
    'sprintf',
    'vsprintf',
    'printf',
    'number_format',
    'array_map',
    'array_filter',
    'array_reduce',
    'array_push',
    'array_pop',
    'array_shift',
    'array_unshift',
    'array_slice',
    'array_splice',
    'array_merge',
    'array_keys',
    'array_values',
    'array_key_exists',
    'in_array',
    'array_search',
    'array_unique',
    'usort',
    'rsort',
    'json_encode',
    'json_decode',
    'serialize',
    'unserialize',
    'intval',
    'floatval',
    'strval',
    'boolval',
    'is_null',
    'is_string',
    'is_int',
    'is_array',
    'is_object',
    'is_numeric',
    'is_bool',
    'is_float',
    'var_dump',
    'print_r',
    'var_export',
    'date',
    'time',
    'strtotime',
    'mktime',
    'microtime',
    'file_exists',
    'file_get_contents',
    'file_put_contents',
    'is_file',
    'is_dir',
    'preg_match',
    'preg_match_all',
    'preg_replace',
    'preg_split',
    'header',
    'session_start',
    'session_destroy',
    'ob_start',
    'ob_end_clean',
    'ob_get_clean',
    'dd',
    'dump',
]);
/** Eloquent model properties whose array values are worth indexing. */
const ELOQUENT_ARRAY_PROPS = new Set(['fillable', 'casts', 'hidden', 'guarded', 'with', 'appends']);
/** Eloquent relationship method names. */
const ELOQUENT_RELATIONS = new Set([
    'hasMany',
    'hasOne',
    'belongsTo',
    'belongsToMany',
    'morphTo',
    'morphMany',
    'morphOne',
    'morphToMany',
    'morphedByMany',
    'hasManyThrough',
    'hasOneThrough',
]);
/**
 * For a PHP property_declaration node, extract array values as a description string.
 * Returns null if not an Eloquent model property or no array values found.
 */
function extractPhpPropertyDescription(propName, propDeclNode) {
    if (!ELOQUENT_ARRAY_PROPS.has(propName))
        return null;
    const arrayNode = findDescendant(propDeclNode, 'array_creation_expression');
    if (!arrayNode)
        return null;
    const items = [];
    for (const child of arrayNode.children ?? []) {
        if (child.type !== 'array_element_initializer')
            continue;
        const children = child.children ?? [];
        const arrowIdx = children.findIndex((c) => c.type === '=>');
        if (arrowIdx !== -1) {
            const key = extractStringContent(children[arrowIdx - 1]);
            const val = extractStringContent(children[arrowIdx + 1]);
            if (key && val)
                items.push(`${key}:${val}`);
        }
        else {
            const val = extractStringContent(children[0]);
            if (val)
                items.push(val);
        }
    }
    return items.length > 0 ? items.join(', ') : null;
}
/**
 * For a PHP method_declaration node, detect if it defines an Eloquent relationship.
 * Returns description like "hasMany(Post)" or null.
 */
function extractEloquentRelationDescription(methodNode) {
    function findRelationCall(root) {
        const stack = [root];
        while (stack.length > 0) {
            const node = stack.pop();
            if (node.type === 'member_call_expression') {
                const children = node.children ?? [];
                const objectNode = children.find((c) => c.type === 'variable_name' && c.text === '$this');
                const nameNode = children.find((c) => c.type === 'name');
                if (objectNode && nameNode && ELOQUENT_RELATIONS.has(nameNode.text))
                    return node;
            }
            const children = node.children ?? [];
            for (let i = children.length - 1; i >= 0; i--) {
                stack.push(children[i]);
            }
        }
        return null;
    }
    const callNode = findRelationCall(methodNode);
    if (!callNode)
        return null;
    const relType = callNode.children?.find((c) => c.type === 'name')?.text;
    const argsNode = callNode.children?.find((c) => c.type === 'arguments');
    let targetModel = null;
    if (argsNode) {
        const firstArg = argsNode.children?.find((c) => c.type === 'argument');
        if (firstArg) {
            const classConstant = firstArg.children?.find((c) => c.type === 'class_constant_access_expression');
            if (classConstant) {
                targetModel =
                    classConstant.children?.find((c) => c.type === 'name')?.text ?? null;
            }
        }
    }
    if (relType && targetModel)
        return `${relType}(${targetModel})`;
    if (relType)
        return relType;
    return null;
}
/**
 * LanguageProvider.descriptionExtractor implementation for PHP.
 * Extracts Eloquent model property metadata and relationship descriptions.
 */
function phpDescriptionExtractor(nodeLabel, nodeName, captureMap) {
    if (nodeLabel === 'Property' && captureMap['definition.property']) {
        return extractPhpPropertyDescription(nodeName, captureMap['definition.property']) ?? undefined;
    }
    if (nodeLabel === 'Method' && captureMap['definition.method']) {
        return extractEloquentRelationDescription(captureMap['definition.method']) ?? undefined;
    }
    return undefined;
}
/** Detect Laravel route files by path convention. */
function isPhpRouteFile(filePath) {
    return (filePath.endsWith('.php') && (filePath.includes('/routes/') || filePath.startsWith('routes/')));
}
export const phpProvider = defineLanguage({
    id: SupportedLanguages.PHP,
    extensions: ['.php', '.phtml', '.php3', '.php4', '.php5', '.php8'],
    treeSitterQueries: PHP_QUERIES,
    typeConfig: phpConfig,
    exportChecker: phpExportChecker,
    importResolver: createImportResolver(phpImportConfig),
    namedBindingExtractor: extractPhpNamedBindings,
    callExtractor: createCallExtractor(phpCallConfig),
    fieldExtractor: createFieldExtractor(phpFieldConfig),
    methodExtractor: createMethodExtractor(phpMethodConfig),
    variableExtractor: createVariableExtractor(phpVariableConfig),
    classExtractor: createClassExtractor(phpClassConfig),
    heritageExtractor: createHeritageExtractor(SupportedLanguages.PHP),
    descriptionExtractor: phpDescriptionExtractor,
    isRouteFile: isPhpRouteFile,
    builtInNames: BUILT_INS,
});
