/*
 * Copyright 2013-2026, Seqera Labs
 *
 * Licensed 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 nextflow.script.formatter;

import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.ImportNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.control.SourceUnit;

/**
 * Format Groovy code generated by the Nextflow compiler.
 *
 * @author Ben Sherman <bentshermann@gmail.com>
 */
public class GroovyFormatter extends ClassCodeVisitorSupport {

    private SourceUnit sourceUnit;

    private boolean verbose;

    private Formatter fmt;

    public GroovyFormatter(SourceUnit sourceUnit, boolean verbose) {
        this.sourceUnit = sourceUnit;
        this.verbose = verbose;
        this.fmt = new Formatter(new FormattingOptions(2, true));
    }

    @Override
    protected SourceUnit getSourceUnit() {
        return sourceUnit;
    }

    public String format() {
        var moduleNode = sourceUnit.getAST();
        if( moduleNode == null )
            return null;

        if( verbose )
            visitImports(moduleNode);

        for( var cn : moduleNode.getClasses() )
            visitClass(cn);

        return fmt.toString();
    }

    @Override
    public void visitImports(ModuleNode node) {
        for( var in : node.getImports() )
            visitImport(in);
        for( var isn : node.getStarImports() )
            visitImport(isn);
        for( var isn : node.getStaticImports().values() )
            visitImport(isn);
        for( var issn : node.getStaticStarImports().values() )
            visitImport(issn);
    }

    private void visitImport(ImportNode node) {
        fmt.append(node.getText());
        fmt.appendNewLine();
    }

    @Override
    public void visitClass(ClassNode node) {
        fmt.appendNewLine();
        visitAnnotations(node);
        fmt.appendIndent();
        fmt.append(node.isEnum() ? "enum" : "class");
        fmt.append(' ');
        fmt.append(node.getNameWithoutPackage());
        if( node.getSuperClass() != null ) {
            fmt.append(" extends ");
            fmt.append(node.getSuperClass().getNameWithoutPackage());
        }
        fmt.append(" {\n");
        fmt.incIndent();
        node.visitContents(this);
        fmt.decIndent();
        fmt.appendIndent();
        fmt.append("}\n");
    }

    @Override
    public void visitAnnotations(AnnotatedNode node) {
        for( var an : node.getAnnotations() ) {
            if( an.isBuiltIn() )
                continue;
            fmt.appendIndent();
            fmt.append(an.getText());
            fmt.appendNewLine();
        }
    }

    @Override
    public void visitConstructor(ConstructorNode node) {
        fmt.appendNewLine();
        visitAnnotations(node);
        fmt.appendIndent();
        fmt.append(node.getDeclaringClass().getNameWithoutPackage());
        fmt.append('(');
        fmt.visitParameters(node.getParameters());
        fmt.append(") {\n");
        fmt.incIndent();
        fmt.visit(node.getCode());
        fmt.decIndent();
        fmt.appendIndent();
        fmt.append("}\n");
    }

    @Override
    public void visitMethod(MethodNode node) {
        fmt.appendNewLine();
        visitAnnotations(node);
        fmt.appendIndent();
        if( ClassHelper.isObjectType(node.getReturnType()) ) {
            fmt.append("def ");
        }
        else {
            fmt.visitTypeAnnotation(node.getReturnType());
            fmt.append(' ');
        }
        fmt.append(node.getName());
        fmt.append('(');
        fmt.visitParameters(node.getParameters());
        fmt.append(") {\n");
        fmt.incIndent();
        fmt.visit(node.getCode());
        fmt.decIndent();
        fmt.appendIndent();
        fmt.append("}\n");
    }

    @Override
    public void visitField(FieldNode node) {
        fmt.appendNewLine();
        visitAnnotations(node);
        fmt.appendIndent();
        if( ClassHelper.isObjectType(node.getType()) ) {
            fmt.append("def ");
        }
        else {
            fmt.visitTypeAnnotation(node.getType());
            fmt.append(' ');
        }
        fmt.append(node.getName());
        if( node.hasInitialExpression() ) {
            fmt.append(" = ");
            visit(node.getInitialExpression());
        }
        fmt.appendNewLine();
    }

}
