/*
 *  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.transform.traitx

import org.junit.Test

import static groovy.test.GroovyAssert.assertScript

/**
 * A sub-trait must be able to resolve a {@code static} method inherited from a
 * super-trait from its own body under {@code @CompileStatic}, including when an
 * argument's static type is a <em>proper subtype</em> of the declared parameter
 * type (GROOVY-12106).
 *
 * <p>The bug: {@code TraitTypeCheckingExtension} resolved the inherited super-trait
 * helper static by an <em>exact</em> parameter-type match, so only an argument whose
 * static type exactly matched the parameter resolved; a subtype argument failed with
 * {@code Cannot find matching method <Child>$Trait$Helper#m(java.lang.Class, ...)}.
 * Because the minimal report passed an exact-type argument, the issue was first closed
 * "Cannot Reproduce". Plain class inheritance never had this problem — the defect was
 * specific to trait static-helper dispatch.
 */
final class Groovy12106 {

    @Test
    void testUnqualifiedInheritedStaticWithSubtypeArg() {
        assertScript '''
            import groovy.transform.CompileStatic
            @CompileStatic
            trait ExecutesClosures {
                static String withDelegate(Closure cl, Object delegate) { 'target=' + delegate.class.simpleName }
            }
            final class SimpleArgument { }
            @CompileStatic
            trait Arguable<T> extends ExecutesClosures {
                String run(SimpleArgument arg) { withDelegate({ -> }, arg) }   // unqualified, subtype arg
            }
            class C implements Arguable<String> { }
            assert new C().run(new SimpleArgument()) == 'target=SimpleArgument'
        '''
    }

    @Test
    void testThisQualifiedInheritedStaticWithSubtypeArg() {
        assertScript '''
            import groovy.transform.CompileStatic
            @CompileStatic
            trait ExecutesClosures {
                static String withDelegate(Closure cl, Object delegate) { 'target=' + delegate.class.simpleName }
            }
            final class SimpleArgument { }
            @CompileStatic
            trait Arguable extends ExecutesClosures {
                String run(SimpleArgument arg) { this.withDelegate({ -> }, arg) }
            }
            class C implements Arguable { }
            assert new C().run(new SimpleArgument()) == 'target=SimpleArgument'
        '''
    }

    @Test
    void testQualifiedInheritedStaticWithSubtypeArg() {
        assertScript '''
            import groovy.transform.CompileStatic
            @CompileStatic
            trait ExecutesClosures {
                static String withDelegate(Closure cl, Object delegate) { 'target=' + delegate.class.simpleName }
            }
            final class SimpleArgument { }
            @CompileStatic
            trait Arguable extends ExecutesClosures {
                String run(SimpleArgument arg) { ExecutesClosures.withDelegate({ -> }, arg) }
            }
            class C implements Arguable { }
            assert new C().run(new SimpleArgument()) == 'target=SimpleArgument'
        '''
    }

    @Test
    void testExactTypeArgStillResolves() {
        assertScript '''
            import groovy.transform.CompileStatic
            @CompileStatic
            trait ExecutesClosures {
                static String withDelegate(Closure cl, Object delegate) { 'target=' + delegate.class.simpleName }
            }
            @CompileStatic
            trait Arguable extends ExecutesClosures {
                String run(Object arg) { withDelegate({ -> }, arg) }   // exact-type arg (the case that always worked)
            }
            class C implements Arguable { }
            assert new C().run('s') == 'target=String'
        '''
    }

    @Test
    void testOverloadResolutionAcrossTraitInheritance() {
        assertScript '''
            import groovy.transform.CompileStatic
            @CompileStatic
            trait P {
                static String pick(Object o) { 'object' }
                static String pick(CharSequence c) { 'charseq' }
            }
            @CompileStatic
            trait Q extends P {
                String viaCharSequence(String s) { pick(s) }   // String -> most specific CharSequence overload
                String viaObject(Integer i) { pick(i) }        // Integer -> Object overload
            }
            class C implements Q { }
            assert new C().viaCharSequence('x') == 'charseq'
            assert new C().viaObject(42) == 'object'
        '''
    }

    @Test
    void testThreeLevelTraitInheritance() {
        assertScript '''
            import groovy.transform.CompileStatic
            final class Sub { }
            @CompileStatic
            trait A { static String tag(Object o) { 'A:' + o.class.simpleName } }
            @CompileStatic
            trait B extends A { }
            @CompileStatic
            trait C extends B {
                String go(Sub s) { tag(s) }   // inherited from grandparent A, subtype arg
            }
            class Impl implements C { }
            assert new Impl().go(new Sub()) == 'A:Sub'
        '''
    }

    @Test
    void testGrailsHelperShapeWithDelegatesTo() {
        assertScript '''
            import groovy.transform.CompileStatic
            final class Field { String name = 'f' }
            @CompileStatic
            trait ExecutesClosures {
                static void withDelegate(@DelegatesTo(strategy=Closure.DELEGATE_ONLY, genericTypeIndex=0) Closure callable, Object delegate) {
                    if (callable != null) { callable.delegate = delegate; callable.resolveStrategy = Closure.DELEGATE_ONLY; callable.call() }
                }
            }
            @CompileStatic
            trait Arguable<T> extends ExecutesClosures {
                String describe(Field f) {
                    def sb = new StringBuilder()
                    withDelegate({ -> sb.append('seen') }, f)   // f:Field is a subtype of Object
                    sb.toString()
                }
            }
            class C implements Arguable<String> { }
            assert new C().describe(new Field()) == 'seen'
        '''
    }

    /**
     * The Grails helper shape (above) but with the sub-trait declared <em>before</em>
     * the super-trait in the same compilation unit (GROOVY-12117). When the sub-trait
     * is transformed first, the super-trait's helper has not been generated yet, so
     * {@code TraitReceiverTransformer.findConcreteMethod} could not see the inherited
     * static via the helper and left the call as an unrewritten {@code Arguable#withDelegate},
     * which then failed type checking — making resolution depend on declaration order.
     * In a multi-file build (e.g. Grails GraphQL) the files sort sub-trait-first, which
     * is why this shape escaped {@link #testGrailsHelperShapeWithDelegatesTo}. Resolution
     * must be order independent (GEP-22 P1').
     */
    @Test
    void testGrailsHelperShapeWithSubTraitDeclaredFirst() {
        assertScript '''
            import groovy.transform.CompileStatic
            final class Field { String name = 'f' }
            @CompileStatic
            trait Arguable<T> extends ExecutesClosures {   // sub-trait declared FIRST
                String describe(Field f) {
                    def sb = new StringBuilder()
                    withDelegate({ -> sb.append('seen') }, f)   // f:Field is a subtype of Object
                    sb.toString()
                }
            }
            @CompileStatic
            trait ExecutesClosures {
                static void withDelegate(@DelegatesTo(strategy=Closure.DELEGATE_ONLY, genericTypeIndex=0) Closure callable, Object delegate) {
                    if (callable != null) { callable.delegate = delegate; callable.resolveStrategy = Closure.DELEGATE_ONLY; callable.call() }
                }
            }
            class C implements Arguable<String> { }
            assert new C().describe(new Field()) == 'seen'
        '''
    }

    /**
     * The sub-trait-first shape (above) with the helper's delegate parameter carrying
     * {@code @DelegatesTo.Target} — the faithful Grails {@code withDelegate} signature, so
     * the {@code @DelegatesTo} closure annotation fully resolves rather than relying on
     * {@code genericTypeIndex}. The inherited static must still resolve order independently
     * (GROOVY-12117); the {@code @DelegatesTo} metadata on the super-trait helper static
     * must not perturb that resolution.
     */
    @Test
    void testGrailsHelperShapeWithDelegatesToTargetSubTraitFirst() {
        assertScript '''
            import groovy.transform.CompileStatic
            final class Field { String name = 'f' }
            @CompileStatic
            trait Arguable<T> extends ExecutesClosures {   // sub-trait declared FIRST
                String describe(Field f) {
                    def sb = new StringBuilder()
                    withDelegate({ -> sb.append('seen') }, f)   // f:Field is a subtype of the target delegate
                    sb.toString()
                }
            }
            @CompileStatic
            trait ExecutesClosures {
                static void withDelegate(@DelegatesTo(strategy=Closure.DELEGATE_ONLY) Closure callable,
                                         @DelegatesTo.Target Object delegate) {
                    if (callable != null) { callable.delegate = delegate; callable.resolveStrategy = Closure.DELEGATE_ONLY; callable.call() }
                }
            }
            class C implements Arguable<String> { }
            assert new C().describe(new Field()) == 'seen'
        '''
    }
}
