package p.studio.compiler.pbs.semantics;

import p.studio.compiler.pbs.ast.PbsAst;
import p.studio.utilities.structures.ReadOnlyList;

final class PbsCallableShape {
    private PbsCallableShape() {
    }

    static String callableShapeKey(
            final ReadOnlyList<PbsAst.Parameter> parameters,
            final PbsAst.ReturnKind returnKind,
            final PbsAst.TypeRef returnType,
            final PbsAst.TypeRef resultErrorType) {
        final var builder = new StringBuilder();
        builder.append('(');
        for (int i = 0; i < parameters.size(); i++) {
            if (i > 0) {
                builder.append(',');
            }
            builder.append(typeKey(parameters.get(i).typeRef()));
        }
        builder.append(")->");
        builder.append(outputSurfaceKey(returnKind, returnType, resultErrorType));
        return builder.toString();
    }

    static String ctorShapeKey(final ReadOnlyList<PbsAst.Parameter> parameters) {
        final var builder = new StringBuilder();
        builder.append('(');
        for (int i = 0; i < parameters.size(); i++) {
            if (i > 0) {
                builder.append(',');
            }
            builder.append(typeKey(parameters.get(i).typeRef()));
        }
        builder.append(')');
        return builder.toString();
    }

    private static String outputSurfaceKey(
            final PbsAst.ReturnKind returnKind,
            final PbsAst.TypeRef returnType,
            final PbsAst.TypeRef resultErrorType) {
        return switch (returnKind) {
            case INFERRED_UNIT, EXPLICIT_UNIT -> "unit";
            case PLAIN -> carrierTypeKey(returnType);
            case RESULT -> "result<" + typeKey(resultErrorType) + ">" + carrierTypeKey(returnType);
        };
    }

    private static String carrierTypeKey(final PbsAst.TypeRef typeRef) {
        final var unwrapped = unwrapGroup(typeRef);
        if (unwrapped != null && unwrapped.kind() == PbsAst.TypeRefKind.NAMED_TUPLE && unwrapped.fields().size() == 1) {
            return typeKey(unwrapped.fields().getFirst().typeRef());
        }
        return typeKey(unwrapped);
    }

    private static PbsAst.TypeRef unwrapGroup(final PbsAst.TypeRef typeRef) {
        if (typeRef == null) {
            return null;
        }
        if (typeRef.kind() != PbsAst.TypeRefKind.GROUP) {
            return typeRef;
        }
        return unwrapGroup(typeRef.inner());
    }

    private static String typeKey(final PbsAst.TypeRef typeRef) {
        if (typeRef == null) {
            return "unit";
        }
        final var unwrapped = unwrapGroup(typeRef);
        if (unwrapped == null) {
            return "unit";
        }
        return switch (unwrapped.kind()) {
            case SIMPLE -> "simple:" + unwrapped.name();
            case SELF -> "self";
            case OPTIONAL -> "optional(" + typeKey(unwrapped.inner()) + ")";
            case UNIT -> "unit";
            case GROUP -> typeKey(unwrapped.inner());
            case NAMED_TUPLE -> "tuple(" + unwrapped.fields().stream()
                    .map(field -> typeKey(field.typeRef()))
                    .reduce((left, right) -> left + "," + right)
                    .orElse("") + ")";
            case ERROR -> "error";
        };
    }
}
