/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.codehaus.groovy.runtime.metaclass;

import groovy.lang.Closure;
import groovy.lang.ExpandoMetaClass;
import groovy.lang.GroovyInterceptable;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.MetaBeanProperty;
import groovy.lang.MetaClass;
import groovy.lang.MetaClassImpl;
import groovy.lang.MetaClassRegistry;
import groovy.lang.MetaMethod;
import groovy.lang.MetaProperty;
import groovy.lang.MissingMethodException;
import groovy.lang.ProxyMetaClass;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.reflection.CachedField;
import org.codehaus.groovy.reflection.CachedMethod;
import org.codehaus.groovy.reflection.ParameterTypes;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.runtime.wrappers.Wrapper;
import org.codehaus.groovy.util.FastArray;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A metaclass for closures generated by the Groovy compiler. These classes
 * have special characteristics this MetaClass uses. One of these is that a
 * generated Closure has only additional doCall methods, all other methods
 * are in the Closure class as well. To use this fact this MetaClass uses
 * a MetaClass for Closure as static field And delegates calls to this
 * MetaClass if needed. This allows a lean implementation for this MetaClass.
 * Multiple generated closures will then use the same MetaClass for Closure.
 * For static dispatching this class uses the MetaClass of Class, again
 * all instances of this class will share that MetaClass. The Class MetaClass
 * is initialized lazy, because most operations do not need this MetaClass.
 * <p>
 * The Closure and Class metaclasses are not replaceable.
 * <p>
 * This MetaClass is for internal usage only!
 *
 * @since 1.5
 */
public final class ClosureMetaClass extends MetaClassImpl {

    private volatile boolean attributeInitDone;
    private final FastArray closureMethods = new FastArray(3);
    private final Map<String, CachedField> attributes = new HashMap<>();
    private MethodChooser chooser;

    private static MetaClassImpl classMetaClass;
    private static MetaClassImpl CLOSURE_METACLASS;
    private static final Object[] EMPTY_ARGUMENTS = {};
    private static final String CLOSURE_CALL_METHOD = "call";
    private static final String CLOSURE_DO_CALL_METHOD = "doCall";

    static {
        resetCachedMetaClasses();
    }

    /**
     * Resets the cached metaclasses for Closure and Class. This method is used to reinitialize
     * the metaclass caches when the system configuration changes, ensuring that any cached
     * metaclass information is refreshed.
     */
    public static void resetCachedMetaClasses() {
        MetaClassImpl temp = new MetaClassImpl(Closure.class);
        temp.initialize();
        synchronized (ClosureMetaClass.class) {
            CLOSURE_METACLASS = temp;
        }
        if (classMetaClass != null) {
            temp = new MetaClassImpl(Class.class);
            temp.initialize();
            synchronized (ClosureMetaClass.class) {
                classMetaClass = temp;
            }
        }
    }

    private static synchronized MetaClass getStaticMetaClass() {
        if (classMetaClass == null) {
            classMetaClass = new MetaClassImpl(Class.class);
            classMetaClass.initialize();
        }
        return classMetaClass;
    }

    @FunctionalInterface
    private interface MethodChooser {
        Object chooseMethod(Class<?>[] arguments, boolean coerce);
    }

    private static class StandardClosureChooser implements MethodChooser {
        private final MetaMethod doCall0;
        private final MetaMethod doCall1;

        StandardClosureChooser(final MetaMethod m0, final MetaMethod m1) {
            doCall0 = m0;
            doCall1 = m1;
        }

        @Override
        public Object chooseMethod(final Class<?>[] arguments, final boolean coerce) {
            if (arguments.length == 0) return doCall0;
            if (arguments.length == 1) return doCall1;
            return null;
        }
    }

    private static class NormalMethodChooser implements MethodChooser {
        private final Class<?> theClass;
        private final FastArray methods;

        NormalMethodChooser(final Class<?> theClass, final FastArray methods) {
            this.theClass = theClass;
            this.methods = methods;
        }

        @Override
        public Object chooseMethod(final Class<?>[] arguments, final boolean coerce) {
            if (arguments.length == 0) {
                return MetaClassHelper.chooseEmptyMethodParams(methods);
            } else {
                int methodCount = methods.size();
                List<Object> matchingMethods = new ArrayList<>(methodCount);
                for (int i = 0; i < methodCount; i += 1) {
                    Object method = methods.get(i);
                    // making this false helps find matches
                    if (((ParameterTypes) method).isValidMethod(arguments)) {
                        matchingMethods.add(method);
                    }
                }

                int size = matchingMethods.size();
                if (size == 0) {
                    return null;
                } else if (size == 1) {
                    return matchingMethods.get(0);
                }

                return doChooseMostSpecificParams(theClass.getName(), CLOSURE_DO_CALL_METHOD, matchingMethods, arguments, true);
            }
        }
    }

    //--------------------------------------------------------------------------

    /**
     * Constructs a new ClosureMetaClass for the given closure class.
     * This metaclass handles method invocation and property access for closure instances,
     * providing special handling for closure-specific behavior.
     *
     * @param registry the metaclass registry
     * @param theClass the closure class this metaclass represents
     */
    public ClosureMetaClass(final MetaClassRegistry registry, final Class theClass) {
        super(registry, theClass);
    }

    /**
     * Gets the meta property for the given name from the closure metaclass.
     *
     * @param name the property name
     * @return the meta property or null if not found
     */
    @Override
    public MetaProperty getMetaProperty(final String name) {
        return CLOSURE_METACLASS.getMetaProperty(name);
    }

    private MetaMethod pickClosureMethod(final Class<?>[] argClasses) {
        return (MetaMethod) chooser.chooseMethod(argClasses, false);
    }

    private MetaMethod getDelegateMethod(final Closure<?> closure, final Object delegate, final String methodName, final Class<?>[] argClasses) {
        if (delegate == closure || delegate == null) {
            return null;
        }

        if (delegate instanceof Class<?> type) {
            for (; type != Object.class && type != null; type = type.getSuperclass()) {
                MetaMethod method = registry.getMetaClass(type).getStaticMetaMethod(methodName, argClasses);
                if (method != null) {
                    return method;
                }
            }
            return null;
        }

        MetaClass delegateMetaClass = lookupObjectMetaClass(delegate);

        if (delegate instanceof GroovyInterceptable) {
            // GROOVY-3015: must route calls through GroovyObject#invokeMethod(String,Object)
            MetaMethod interceptMethod = delegateMetaClass.pickMethod("invokeMethod", new Class[]{String.class, Object.class});
            return new TransformMetaMethod(interceptMethod) {
                @Override
                public Object invoke(final Object object, final Object[] arguments) {
                    return super.invoke(object, new Object[]{methodName, arguments});
                }
            };
        }

        MetaMethod method = delegateMetaClass.pickMethod(methodName, argClasses);
        if (method == null) {
            if (delegateMetaClass instanceof ExpandoMetaClass) {
                method = ((ExpandoMetaClass) delegateMetaClass).findMixinMethod(methodName, argClasses);
                if (method != null) {
                    onMixinMethodFound(method);
                }
            } else if (delegateMetaClass instanceof MetaClassImpl) {
                method = MetaClassImpl.findMethodInClassHierarchy(getTheClass(), methodName, argClasses, this);
                if (method != null) {
                    onSuperMethodFoundInHierarchy(method);
                }
            }
        }
        return method;
    }

    @Override
    public Object invokeMethod(final Class sender, final Object object, final String methodName, final Object[] arguments, final boolean isCallToSuper, final boolean fromInsideClass) {
        checkInitalised();
        if (object == null) {
            throw new NullPointerException("Cannot invoke method: " + methodName + " on null object");
        }

        final Object[] theArguments = arguments == null ? EMPTY_ARGUMENTS : arguments.clone();
        final Class<?>[] argClasses = MetaClassHelper.convertToTypeArray(theArguments);

        MetaMethod method = null;
        final var closure = (Closure<?>) object;
        final int resolveStrategy = closure.getResolveStrategy();

        if (CLOSURE_DO_CALL_METHOD.equals(methodName) || CLOSURE_CALL_METHOD.equals(methodName)) {
            method = pickClosureMethod(argClasses);
            if (method == null && argClasses.length == 1 && List.class.isAssignableFrom(argClasses[0])) {
                var list = (theArguments[0] instanceof Wrapper wrapper ? wrapper.unwrap() : theArguments[0]);
                if (list != null) {
                    var newArguments = ((List<?>) list).toArray();
                    var newArgClasses = MetaClassHelper.convertToTypeArray(newArguments);
                    method = createTransformMetaMethod(pickClosureMethod(newArgClasses));
                }
            }
            if (method == null) throw new MissingMethodException(methodName, theClass, theArguments, false);
        }

        MetaMethod cmm = null;
        if (method == null && (resolveStrategy != Closure.DELEGATE_ONLY || !isInternalMethod(methodName))) {
            method = CLOSURE_METACLASS.pickMethod(methodName, argClasses);
            // GROOVY-11858: delegate or owner may be desired target
            if (method != null && resolveStrategy != Closure.TO_SELF
                    && sender == getNonClosureOuter(getTheClass())) {
                CachedClass methodOwner = method.getDeclaringClass();
                if (methodOwner != getTheCachedClass()
                        && methodOwner.getTheClass() != Closure.class
                        && methodOwner.getTheClass() != GroovyObject.class) {
                    cmm= method; method= null; // try delegate or owner first
                }
            }
        }

        if (method != null) return method.doMethodInvoke(closure, theArguments);

        Object callObject = closure; // target for method
        final Object delegate = closure.getDelegate(), owner = closure.getOwner();
        boolean invokeOnDelegate = false, invokeOnOwner = false, ownerFirst = true;

        switch (resolveStrategy) {
          case Closure.TO_SELF:
            break;
          case Closure.DELEGATE_ONLY:
            method = getDelegateMethod(closure, delegate, methodName, argClasses);
            callObject = delegate;
            if (method == null) {
                invokeOnDelegate = (delegate != closure) && (delegate instanceof GroovyObject);
            }
            break;
          case Closure.OWNER_ONLY:
            method = getDelegateMethod(closure, owner, methodName, argClasses);
            callObject = owner;
            if (method == null) {
                invokeOnOwner = (owner != closure) && (owner instanceof GroovyObject);
            }
            break;
          case Closure.DELEGATE_FIRST:
            method = getDelegateMethod(closure, delegate, methodName, argClasses);
            callObject = delegate;
            if (method == null) {
                invokeOnDelegate = (delegate != closure);
                invokeOnOwner = (owner != closure);
                ownerFirst = false;
            }
            break;
          default: //Closure.OWNER_FIRST:
            method = getDelegateMethod(closure, owner, methodName, argClasses);
            callObject = owner;
            if (method == null) {
                invokeOnDelegate = (delegate != closure);
                invokeOnOwner = (owner != closure);
                ownerFirst = true;
            }
            break;
        }

        if (method != null) {
            var metaClass = registry.getMetaClass(callObject.getClass());
            if (metaClass instanceof ProxyMetaClass) {
                return metaClass.invokeMethod(callObject, methodName, arguments);
            } else {
                return method.doMethodInvoke(callObject, theArguments); // direct
            }
        } else if (invokeOnOwner || invokeOnDelegate) {
            if (ownerFirst) {
                return invokeOnDelegationObjects(invokeOnOwner, owner, invokeOnDelegate, delegate, methodName, arguments, sender);
            } else {
                return invokeOnDelegationObjects(invokeOnDelegate, delegate, invokeOnOwner, owner, methodName, arguments, sender);
            }
        }

        if (cmm != null) cmm.doMethodInvoke(closure, theArguments);

        throw new MissingMethodException(methodName, theClass, theArguments, false);
    }

    private static Object invokeOnDelegationObjects(final boolean i1, final Object o1, final boolean i2, final Object o2, final String methodName, final Object[] args, final Class<?> c) {
        MissingMethodException first = null;
        if (i1) {
            try {
                return invokeOnDelegationObject(c, o1, methodName, args);
            } catch (MissingMethodException mme) {
                first = mme;
            }
        }
        if (i2 && (!i1 || o1 != o2)) {
            try {
                return invokeOnDelegationObject(c, o2, methodName, args);
            } catch (MissingMethodException mme) {
                if (first == null) first = mme;
                else first.addSuppressed(mme);
            }
        }
        throw first;
    }

    private static Object invokeOnDelegationObject(final Class<?> sender, final Object object, final String methodName, final Object[] arguments) {
        MissingMethodException first = null;
        try {
            return InvokerHelper.invokeMethod(object, methodName, arguments); // includes callable property
        } catch (MissingMethodException mme) {
            first = mme;
        } catch (GroovyRuntimeException gre) {
            Throwable t = gre;
            while (t.getCause() != t && t.getCause() instanceof GroovyRuntimeException) t = t.getCause();
            if (t instanceof MissingMethodException mme && methodName.equals(mme.getMethod())) {
                first = mme;
            } else {
                throw gre;
            }
        }
        Class<?> thisType = sender;
        while (GeneratedClosure.class.isAssignableFrom(thisType)) thisType = thisType.getEnclosingClass();
        if (thisType != object.getClass() && thisType.isInstance(object)) { // GROOVY-2433, GROOVY-11128
            try {
                return ((GroovyObject) object).getMetaClass().invokeMethod(thisType, object, methodName, arguments, false, true);
            } catch (GroovyRuntimeException gre) {
                first.addSuppressed(gre);
            }
        }
        throw first;
    }

    private static final Set<String> INTERNAL_METHODS = Set.of("curry", "ncurry", "rcurry", "leftShift", "rightShift");
    private static boolean isInternalMethod(final String methodName) {
        return INTERNAL_METHODS.contains(methodName);
    }

    private synchronized void initAttributes() {
        if (attributes.isEmpty()) {
            attributes.put("!", null); // a dummy for later
            for (var field : theCachedClass.getFields()) {
                attributes.put(field.getName(), field);
            }
            attributeInitDone = !attributes.isEmpty();
        }
    }

    /**
     * Initializes the closure metaclass by discovering and indexing all closure-related methods.
     */
    @Override
    public synchronized void initialize() {
        if (!isInitialized()) {
            CachedMethod[] methodArray = theCachedClass.getMethods();
            for (CachedMethod cachedMethod : methodArray) {
                if (!cachedMethod.getName().equals(CLOSURE_DO_CALL_METHOD)) continue;
                closureMethods.add(cachedMethod);
            }
            assignMethodChooser();
            super.reinitialize();
        }
    }

    private void assignMethodChooser() {
        if (closureMethods.size() == 1) {
            MetaMethod doCall = (MetaMethod) closureMethods.get(0);
            CachedClass[] c = doCall.getParameterTypes();
            int length = c.length;
            if (length == 0) {
                // no arg method
                chooser = (arguments, coerce) -> {
                    if (arguments.length == 0) return doCall;
                    return null;
                };
            } else {
                if (length == 1 && c[0].getTheClass() == Object.class) {
                    // Object fits all, so simple dispatch rule here
                    chooser = (arguments, coerce) -> {
                        // <2, because foo() is same as foo(null)
                        if (arguments.length < 2) return doCall;
                        return null;
                    };
                } else {
                    boolean allObject = true;
                    for (int i = 0; i < c.length - 1; i++) {
                        if (c[i].getTheClass() != Object.class) {
                            allObject = false;
                            break;
                        }
                    }
                    if (allObject && c[c.length - 1].getTheClass() == Object.class) {
                        // all arguments are object, so test only if argument number is correct
                        chooser = (arguments, coerce) -> {
                            if (arguments.length == c.length) return doCall;
                            return null;
                        };
                    } else {
                        if (allObject && c[c.length - 1].getTheClass() == Object[].class) {
                            // all arguments are Object but last, which is a vargs argument, that
                            // will fit all, so just test if the number of argument is equal or
                            // more than the parameters we have.
                            final int minimumLength = c.length - 2;
                            chooser = (arguments, coerce) -> {
                                if (arguments.length > minimumLength) return doCall;
                                return null;
                            };
                        } else {
                            // general case for single method
                            chooser = (arguments, coerce) -> {
                                if (doCall.isValidMethod(arguments)) {
                                    return doCall;
                                }
                                return null;
                            };
                        }
                    }
                }
            }
        } else if (closureMethods.size() == 2) {
            MetaMethod m0 = null, m1 = null;
            for (int i = 0; i != closureMethods.size(); ++i) {
                MetaMethod m = (MetaMethod) closureMethods.get(i);
                CachedClass[] c = m.getParameterTypes();
                if (c.length == 0) {
                    m0 = m;
                } else {
                    if (c.length == 1 && c[0].getTheClass() == Object.class) {
                        m1 = m;
                    }
                }
            }
            if (m0 != null && m1 != null) {
                // standard closure (2 methods because "it" is with default null)
                chooser = new StandardClosureChooser(m0, m1);
            }
        }
        if (chooser == null) {
            // standard chooser for cases if it is not a single method and if it is
            // not the standard closure.
            chooser = new NormalMethodChooser(theClass, closureMethods);
        }
    }

    /**
     * Looks up the metaclass for the given object, handling special cases for
     * GroovyObject, Class instances, and regular objects.
     *
     * @param object the object to look up
     * @return the metaclass for the object
     */
    private MetaClass lookupObjectMetaClass(final Object object) {
        MetaClass metaClass;
        if (object instanceof GroovyObject) {
            metaClass = ((GroovyObject) object).getMetaClass();
        } else if (object.getClass() == Class.class) {
            metaClass = registry.getMetaClass((Class<?>) object);
        } else {
            metaClass = InvokerHelper.getMetaClass(object);
        }
        return metaClass;
    }

    /**
     * Returns a list of all methods available to closures, including both
     * closure-specific doCall methods and inherited Closure class methods.
     *
     * @return a list of all available methods
     */
    @Override
    public List<MetaMethod> getMethods() {
        List<MetaMethod> answer = CLOSURE_METACLASS.getMetaMethods();
        answer.addAll(closureMethods.toList());
        return answer;
    }

    /**
     * Returns a list of all metamethods in the Closure metaclass.
     *
     * @return a list of all metamethods
     */
    @Override
    public List<MetaMethod> getMetaMethods() {
        return CLOSURE_METACLASS.getMetaMethods();
    }

    /**
     * Returns a list of all properties available to closures.
     *
     * @return a list of all available properties
     */
    @Override
    public List<MetaProperty> getProperties() {
        return CLOSURE_METACLASS.getProperties();
    }

    /**
     * Picks the most appropriate method from the available closure methods that matches
     * the given method name and argument types.
     *
     * @param methodName the name of the method to pick (typically "call" or "doCall" for closures)
     * @param argumentTypes the types of arguments to match against method signatures
     * @return the matching method, or null if no match is found
     */
    @Override
    public MetaMethod pickMethod(final String methodName, final Class[] argumentTypes) {
        if (CLOSURE_CALL_METHOD.equals(methodName) || CLOSURE_DO_CALL_METHOD.equals(methodName)) {
            return pickClosureMethod(argumentTypes != null ? argumentTypes : MetaClassHelper.EMPTY_CLASS_ARRAY);
        }
        return CLOSURE_METACLASS.getMetaMethod(methodName, argumentTypes);
    }

    /**
     * Retrieves a static method with the given name and argument types.
     * Closures do not support static methods, so this always returns null.
     *
     * @param methodName the name of the static method to retrieve
     * @param arguments the argument types
     * @return null, as closures do not have static methods
     */
    public MetaMethod retrieveStaticMethod(final String methodName, final Class[] arguments) {
        return null;
    }

    /**
     * Gets a static meta method by name and argument objects.
     * Delegates to the Closure metaclass.
     *
     * @param name the name of the static method
     * @param args the arguments
     * @return the matching static meta method, or null if not found
     */
    @Override
    public MetaMethod getStaticMetaMethod(final String name, final Object[] args) {
        return CLOSURE_METACLASS.getStaticMetaMethod(name, args);
    }

    /**
     * Gets a static meta method by name and argument types.
     * Delegates to the Closure metaclass.
     *
     * @param name the name of the static method
     * @param argTypes the argument types
     * @return the matching static meta method, or null if not found
     */
    public MetaMethod getStaticMetaMethod(final String name, final Class[] argTypes) {
        return CLOSURE_METACLASS.getStaticMetaMethod(name, argTypes);
    }

    /**
     * Gets a property on the closure or the object it represents.
     * For Class objects, delegates to the static metaclass; otherwise delegates to Closure metaclass.
     *
     * @param sender the class that is sending this request
     * @param object the object to get the property from
     * @param name the name of the property
     * @param useSuper whether to use super resolution
     * @param fromInsideClass whether the call is from inside the class
     * @return the property value
     */
    @Override
    public Object getProperty(final Class sender, final Object object, final String name, final boolean useSuper, final boolean fromInsideClass) {
        if (object instanceof Class) {
            return getStaticMetaClass().getProperty(sender, object, name, useSuper, fromInsideClass);
        } else {
            return CLOSURE_METACLASS.getProperty(sender, object, name, useSuper, fromInsideClass);
        }
    }

    /**
     * Gets an attribute from the closure or the object it represents.
     * For Class objects, delegates to the static metaclass. For closures, checks the
     * cached fields first before delegating to the Closure metaclass.
     *
     * @param sender the class that is sending this request
     * @param object the object to get the attribute from
     * @param attribute the name of the attribute
     * @param useSuper whether to use super resolution
     * @param fromInsideClass whether the call is from inside the class
     * @return the attribute value
     */
    @Override
    public Object getAttribute(final Class sender, final Object object, final String attribute, final boolean useSuper, final boolean fromInsideClass) {
        if (object instanceof Class) {
            return getStaticMetaClass().getAttribute(sender, object, attribute, useSuper);
        } else {
            if (!attributeInitDone) initAttributes();
            CachedField mfp = attributes.get(attribute);
            if (mfp == null) {
                return CLOSURE_METACLASS.getAttribute(sender, object, attribute, useSuper);
            } else {
                return mfp.getProperty(object);
            }
        }
    }

    /**
     * Sets an attribute on the closure or the object it represents.
     * For Class objects, delegates to the static metaclass. For closures, checks the
     * cached fields first before delegating to the Closure metaclass.
     *
     * @param sender the class that is sending this request
     * @param object the object on which to set the attribute
     * @param attribute the name of the attribute
     * @param newValue the new value for the attribute
     * @param useSuper whether to use super resolution
     * @param fromInsideClass whether the call is from inside the class
     */
    @Override
    public void setAttribute(final Class sender, final Object object, final String attribute, final Object newValue, final boolean useSuper, final boolean fromInsideClass) {
        if (object instanceof Class) {
            getStaticMetaClass().setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass);
        } else {
            if (!attributeInitDone) initAttributes();
            CachedField mfp = attributes.get(attribute);
            if (mfp == null) {
                CLOSURE_METACLASS.setAttribute(sender, object, attribute, newValue, useSuper, fromInsideClass);
            } else {
                mfp.setProperty(object, newValue);
            }
        }
    }

    /**
     * Invokes a static method on the closure class itself.
     * Delegates to the static metaclass.
     *
     * @param object the object on which to invoke the static method
     * @param methodName the name of the static method
     * @param arguments the arguments to pass to the method
     * @return the result of the method invocation
     */
    @Override
    public Object invokeStaticMethod(final Object object, final String methodName, final Object[] arguments) {
        return getStaticMetaClass().invokeMethod(Class.class, object, methodName, arguments, false, false);
    }

    /**
     * Sets a property on the closure or the object it represents.
     * For Class objects, delegates to the static metaclass; otherwise delegates to Closure metaclass.
     *
     * @param sender the class that is sending this request
     * @param object the object on which to set the property
     * @param name the name of the property
     * @param newValue the new value for the property
     * @param useSuper whether to use super resolution
     * @param fromInsideClass whether the call is from inside the class
     */
    @Override
    public void setProperty(final Class sender, final Object object, final String name, final Object newValue, final boolean useSuper, final boolean fromInsideClass) {
        if (object instanceof Class) {
            getStaticMetaClass().setProperty(sender, object, name, newValue, useSuper, fromInsideClass);
        } else {
            CLOSURE_METACLASS.setProperty(sender, object, name, newValue, useSuper, fromInsideClass);
        }
    }

    /**
     * Gets a method without using the cache.
     * This operation is not supported for ClosureMetaClass.
     *
     * @param index the method index
     * @param sender the class that is sending this request
     * @param methodName the name of the method
     * @param arguments the argument types
     * @param isCallToSuper whether this is a call to a super method
     * @return never returns; always throws UnsupportedOperationException
     * @throws UnsupportedOperationException this method is not supported for closures
     */
    public MetaMethod getMethodWithoutCaching(final int index, final Class sender, final String methodName, final Class[] arguments, final boolean isCallToSuper) {
        throw new UnsupportedOperationException();
    }

    /**
     * Sets multiple properties on a bean.
     * This operation is not supported for ClosureMetaClass.
     *
     * @param bean the bean object
     * @param map the properties to set
     * @throws UnsupportedOperationException this method is not supported for closures
     */
    @Override
    public void setProperties(final Object bean, final Map map) {
        throw new UnsupportedOperationException();
    }

    /**
     * Adds a meta bean property to this metaclass.
     * This operation is not supported for ClosureMetaClass as it is immutable.
     *
     * @param mp the meta bean property to add
     * @throws UnsupportedOperationException this method is not supported for closures
     */
    @Override
    public void addMetaBeanProperty(final MetaBeanProperty mp) {
        throw new UnsupportedOperationException();
    }

    /**
     * Adds a meta method to this metaclass.
     * This operation is not supported for ClosureMetaClass as it is immutable.
     *
     * @param method the meta method to add
     * @throws UnsupportedOperationException this method is not supported for closures
     */
    @Override
    public void addMetaMethod(final MetaMethod method) {
        throw new UnsupportedOperationException();
    }

    /**
     * Adds a new instance method to this metaclass.
     * This operation is not supported for ClosureMetaClass as it is immutable.
     *
     * @param method the method to add
     * @throws UnsupportedOperationException this method is not supported for closures
     */
    @Override
    public void addNewInstanceMethod(final Method method) {
        throw new UnsupportedOperationException();
    }

    /**
     * Adds a new static method to this metaclass.
     * This operation is not supported for ClosureMetaClass as it is immutable.
     *
     * @param method the static method to add
     * @throws UnsupportedOperationException this method is not supported for closures
     */
    @Override
    public void addNewStaticMethod(final Method method) {
        throw new UnsupportedOperationException();
    }

    /**
     * Retrieves a constructor matching the given argument types.
     * This operation is not supported for ClosureMetaClass.
     *
     * @param arguments the argument types
     * @return never returns; always throws UnsupportedOperationException
     * @throws UnsupportedOperationException this method is not supported for closures
     */
    @Override
    public Constructor retrieveConstructor(final Class[] arguments) {
        throw new UnsupportedOperationException();
    }

    /**
     * Checks if this metaclass responds to the given method name with the specified argument types.
     * Returns a list of methods that match the given name and signature.
     *
     * @param obj the object to check
     * @param name the method name
     * @param argTypes the argument types
     * @return a list of matching methods, or an empty list if none match
     */
    @Override
    public List respondsTo(final Object obj, final String name, final Object[] argTypes) {
        loadMetaInfo();
        return super.respondsTo(obj, name, argTypes);
    }

    /**
     * Checks if this metaclass responds to the given method name.
     * Returns a list of methods that match the given name.
     *
     * @param obj the object to check
     * @param name the method name
     * @return a list of matching methods, or an empty list if none match
     */
    @Override
    public List respondsTo(final Object obj, final String name) {
        loadMetaInfo();
        return super.respondsTo(obj, name);
    }

    private synchronized void loadMetaInfo() {
        if (!isInitialized()) {
            reinitialize();
        }
    }

    @Override
    protected void applyPropertyDescriptors(final PropertyDescriptor[] propertyDescriptors) {
        // do nothing
    }
}
