/**
 * Ruby language provider.
 *
 * Ruby uses wildcard import semantics (require/require_relative bring
 * everything into scope). Ruby has SPECIAL call routing via routeRubyCall
 * to handle require, include/extend (heritage), and attr_accessor/
 * attr_reader/attr_writer (property definitions) as call expressions.
 */
import { SupportedLanguages } from '../../../_shared/index.js';
import { createClassExtractor } from '../class-extractors/generic.js';
import { rubyClassConfig } from '../class-extractors/configs/ruby.js';
import { defineLanguage } from '../language-provider.js';
import { typeConfig as rubyConfig } from '../type-extractors/ruby.js';
import { routeRubyCall } from '../call-routing.js';
import { rubyExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
import { rubyImportConfig } from '../import-resolvers/configs/ruby.js';
import { RUBY_QUERIES } from '../tree-sitter-queries.js';
import { createFieldExtractor } from '../field-extractors/generic.js';
import { rubyConfig as rubyFieldConfig } from '../field-extractors/configs/ruby.js';
import { createMethodExtractor } from '../method-extractors/generic.js';
import { rubyMethodConfig } from '../method-extractors/configs/ruby.js';
import { createVariableExtractor } from '../variable-extractors/generic.js';
import { rubyVariableConfig } from '../variable-extractors/configs/ruby.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { rubyCallConfig } from '../call-extractors/configs/ruby.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { rubyHeritageConfig } from '../heritage-extractors/configs/ruby.js';
import { maybeRewriteRubyBareCallToSelf } from '../utils/ruby-self-call.js';
import { findEnclosingClassInfo } from '../utils/ast-helpers.js';
import { emitRubyScopeCaptures, rubyArityCompatibility, rubyBindingScopeFor, rubyImportOwningScope, rubyReceiverBinding, interpretRubyImport, interpretRubyTypeBinding, } from './ruby/index.js';
/**
 * Ruby label override. Applied to:
 *   - `definition.module` captures in the structure phase — remaps to `Trait`
 *     so Ruby modules are registered in the class-like type registry and are
 *     therefore resolvable by `lookupClassByName` during mixin heritage
 *     resolution (`include`/`extend`/`prepend`).
 *   - `definition.function` captures — Ruby has no bare "function" construct
 *     (top-level `def` is a method on `main`); return the default so generic
 *     logic continues to apply.
 *
 * Returning `null` means "skip this definition"; we never do that here.
 */
const rubyLabelOverride = (_node, defaultLabel) => {
    if (defaultLabel === 'Module')
        return 'Trait';
    return defaultLabel;
};
/** Ruby method/singleton_method: extract name from 'name' field, label as Method. */
const rubyExtractFunctionName = (node) => {
    if (node.type !== 'method' && node.type !== 'singleton_method')
        return null;
    let nameNode = node.childForFieldName?.('name');
    if (!nameNode) {
        for (let i = 0; i < node.childCount; i++) {
            const c = node.child(i);
            if (c?.type === 'identifier') {
                nameNode = c;
                break;
            }
        }
    }
    return { funcName: nameNode?.text ?? null, label: 'Method' };
};
const BUILT_INS = new Set([
    'puts',
    'p',
    'pp',
    'raise',
    'fail',
    'require',
    'require_relative',
    'load',
    'autoload',
    'include',
    'extend',
    'prepend',
    'attr_accessor',
    'attr_reader',
    'attr_writer',
    'public',
    'private',
    'protected',
    'module_function',
    'lambda',
    'proc',
    'block_given?',
    'nil?',
    'is_a?',
    'kind_of?',
    'instance_of?',
    'respond_to?',
    'freeze',
    'frozen?',
    'dup',
    'tap',
    'yield_self',
    'each',
    'select',
    'reject',
    'detect',
    'collect',
    'inject',
    'flat_map',
    'each_with_object',
    'each_with_index',
    'any?',
    'all?',
    'none?',
    'count',
    'first',
    'last',
    'sort_by',
    'min_by',
    'max_by',
    'group_by',
    'partition',
    'compact',
    'flatten',
    'uniq',
]);
/**
 * Remaps `class << self` (singleton_class) to its enclosing class/module for
 * receiver inference. A `singleton_class` node is not itself a type — walking
 * up to the real owner lets `inferImplicitReceiver` set `hint='singleton'`.
 * Returns null for orphaned singleton_class (no enclosing class/module found).
 * All other container types are returned as-is.
 */
const rubyResolveEnclosingOwner = (node) => {
    if (node.type === 'singleton_class') {
        let ancestor = node.parent;
        while (ancestor) {
            if (ancestor.type === 'class' || ancestor.type === 'module') {
                return ancestor;
            }
            ancestor = ancestor.parent;
        }
        return null; // no enclosing class/module — skip
    }
    return node; // use as-is for all other container types
};
export const rubyProvider = defineLanguage({
    id: SupportedLanguages.Ruby,
    extensions: ['.rb', '.rake', '.gemspec'],
    entryPointPatterns: [/^call$/, /^perform$/, /^execute$/],
    astFrameworkPatterns: [
        {
            framework: 'rails',
            entryPointMultiplier: 3.0,
            reason: 'rails-pattern',
            patterns: [
                'ApplicationController',
                'ApplicationRecord',
                'ActiveRecord::Base',
                'before_action',
                'after_action',
                'has_many',
                'belongs_to',
                'has_one',
                'validates',
            ],
        },
        {
            framework: 'sinatra',
            entryPointMultiplier: 2.8,
            reason: 'sinatra-pattern',
            patterns: ['Sinatra::Base', 'Sinatra::Application'],
        },
    ],
    treeSitterQueries: RUBY_QUERIES,
    typeConfig: rubyConfig,
    exportChecker: rubyExportChecker,
    importResolver: createImportResolver(rubyImportConfig),
    callRouter: routeRubyCall,
    importSemantics: 'wildcard-leaf',
    callExtractor: createCallExtractor(rubyCallConfig),
    resolveEnclosingOwner: rubyResolveEnclosingOwner,
    fieldExtractor: createFieldExtractor(rubyFieldConfig),
    methodExtractor: createMethodExtractor({
        ...rubyMethodConfig,
        extractFunctionName: rubyExtractFunctionName,
    }),
    variableExtractor: createVariableExtractor(rubyVariableConfig),
    classExtractor: createClassExtractor(rubyClassConfig),
    heritageExtractor: createHeritageExtractor(rubyHeritageConfig),
    labelOverride: rubyLabelOverride,
    // Ruby MRO is kind-aware: prepend providers beat the class's own method,
    // which in turn beats include providers. See `lookupMethodByOwnerWithMRO`
    // in `model/resolve.ts` for the walk order.
    mroStrategy: 'ruby-mixin',
    // ── DAG hooks ────────────────────────────────────────────────────
    //
    // DAG stage 3: rewrite bare calls (e.g. `serialize` in Account#call_serialize)
    // as `self.serialize` so they route through owner-scoped MRO instead of
    // global free-call lookup. `dispatchKind` goes into `hint` for stage 4.
    inferImplicitReceiver: ({ calledName, callForm, receiverName, receiverTypeName, callNode, filePath, }) => {
        // Only fire when no receiver has been resolved already.
        if (receiverName || receiverTypeName)
            return null;
        const enclosing = findEnclosingClassInfo(callNode, filePath, rubyResolveEnclosingOwner);
        const rewrite = maybeRewriteRubyBareCallToSelf(calledName, callForm, callNode, enclosing?.className ?? null, { isBuiltInName: (n) => BUILT_INS.has(n), mroStrategy: 'ruby-mixin' });
        if (!rewrite)
            return null;
        return {
            callForm: rewrite.callForm,
            receiverName: rewrite.receiverName,
            receiverTypeName: rewrite.receiverTypeName,
            receiverSource: 'implicit-self',
            hint: rewrite.dispatchKind, // 'instance' | 'singleton'
        };
    },
    // DAG stage 4: two Ruby dispatch overrides —
    //   implicit-self: MRO walk first, fallback to free-arity-narrowed on miss.
    //   class-as-receiver: singleton ancestry (extend providers only); miss null-routes.
    selectDispatch: ({ receiverSource, hint }) => {
        if (receiverSource === 'implicit-self') {
            // hint='instance' → instance ancestry (prepend→direct→include, see mro-strategy.ts § 'ruby-mixin')
            // hint='singleton' → singleton ancestry (extend providers only; miss null-routes)
            const ancestryView = hint === 'singleton' ? 'singleton' : 'instance';
            return {
                primary: 'owner-scoped',
                fallback: 'free-arity-narrowed',
                ancestryView,
            };
        }
        if (receiverSource === 'class-as-receiver') {
            // Class constant receiver (e.g. Account.log): singleton ancestry only; miss null-routes.
            return {
                primary: 'owner-scoped',
                ancestryView: 'singleton',
            };
        }
        return null;
    },
    builtInNames: BUILT_INS,
    // ── RFC #909 Ring 3: scope-based resolution hooks ──────────
    emitScopeCaptures: emitRubyScopeCaptures,
    interpretImport: interpretRubyImport,
    interpretTypeBinding: interpretRubyTypeBinding,
    bindingScopeFor: rubyBindingScopeFor,
    importOwningScope: rubyImportOwningScope,
    receiverBinding: rubyReceiverBinding,
    arityCompatibility: rubyArityCompatibility,
});
