package requirejs;

import com.intellij.javascript.nodejs.library.NodeJsCoreModulesCatalog;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.List;

public class Path {
    protected PsiElement element;
    protected RequirejsProjectComponent component;
    protected String originValue;
    protected String path;
    protected String module = null;

    public static final List<String> MODULES_SKIPPED_RESOLVING = Arrays.asList(
            "goog",
            "font"
    );

    public Path(PsiElement element, RequirejsProjectComponent component) {
        this.element = element;
        this.component = component;

        parse(element.getText());
    }

    public void parse(String rawValue) {
        originValue = rawValue.replace("\"", "").replace("'", "");

        if (originValue.contains("!")) {
            String[] exclamationMarkSplit = originValue.split("!");
            if (exclamationMarkSplit.length == 2) {
                module = exclamationMarkSplit[0];
                path = exclamationMarkSplit[1];
            } else {
                module = exclamationMarkSplit[0];
                path = "";
            }
        } else {
            path = originValue;
        }
    }

    public String getOriginValue() {
        return originValue;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Nullable
    public String getModule() {
        return module;
    }

    public boolean isAbsolutePath() {
        return path.startsWith("/") && !path.startsWith("//");
    }

    public boolean isRelativePath() {
        return path.startsWith(".");
    }

    public boolean isBuildInVariable() {
        return originValue.equals("exports") || originValue.equals("module") || originValue.equals("require");
    }

    @Nullable
    public PsiElement resolve() {
        PsiElement result;

        if (this.isBuildInVariable()) {
            return getContainingFile();
        }

        if (this.isAbsolutePath()) {
            return resolveAbsolutePath();
        } else if (this.isRelativePath()) {
            result = probeResolveRelativePath();
            if (null != result) {
                return result;
            }
        }

        result = probeResolveUrl();
        if (null != result) {
            return result;
        }

        result = probeResolveBasic();
        if (null != result) {
            return result;
        }

        result = probeResolveRequirePath();
        if (null != result) {
            return result;
        }

        result = probeResolveRequireAlias();
        if (null != result) {
            return result;
        }

        result = probeResolvePackage();
        if (null != result) {
            return result;
        }

        component.getLogger().debug("Could not resolve reference for " + this.getOriginValue());
        return null;
    }

    @Nullable
    protected PsiElement probeResolveUrl() {
        if (this.getPath().startsWith("http") || this.getPath().startsWith("//")) {
            return new PsiUriElement(this.element, this.getPath());
        }

        return null;
    }

    @Nullable
    protected PsiElement probeResolvePackage() {
        for (Package pkg : component.packageConfig.packages) {
            if (this.getPath().startsWith(pkg.name)) {
                VirtualFile targetFile;
                String moduleFilePath;
                if (this.getPath().equals(pkg.name)) {
                    moduleFilePath = pkg.main;
                } else {
                    moduleFilePath = this.getPath().replace(pkg.name, "");
                }

                targetFile = component.getBaseUrlPath(false)
                        .findFileByRelativePath(pkg.location + "/" + moduleFilePath + ".js");
                if (null != targetFile) {
                    return getPsiManager().findFile(targetFile);
                }
            }
        }

        return null;
    }

    @Nullable
    protected PsiElement probeResolveRequireAlias() {
        String requireMapModule = FileUtils.removeExt(element.getContainingFile().getVirtualFile().getPath().replace(
                component.getWebDir(this.getElementFile()).getPath() + '/',
                ""
        ), ".js");

        RequirePathAlias alias = component.requireMap.getAliasByModule(requireMapModule, this.getPath());
        if (null != alias) {
            VirtualFile targetFile = FileUtils.findFileByPath(component.getWebDir(getElementFile()), alias.path);
            if (null != targetFile) {
                return getPsiManager().findFile(targetFile);
            }
        }

        return null;
    }

    @Nullable
    protected PsiElement probeResolveRequirePath() {
        return component.requirePaths.resolve(this);
    }

    @NotNull
    protected PsiManager getPsiManager() {
        return PsiManager.getInstance(element.getProject());
    }

    @Nullable
    protected PsiElement probeResolveBasic() {
        VirtualFile baseUrl = component.getBaseUrlPath(true);
        if (null != baseUrl) {
            VirtualFile targetFile = FileUtils.findFileByPath(baseUrl, this.getPath());

            if (null != targetFile) {
                return getPsiManager().findFile(targetFile);
            } else if (null != this.getModule()) {
                if (isSkippedModule()) {
                    return this.getContainingFile();
                }

                if (NodeJsCoreModulesCatalog.INSTANCE.isPublicCoreModule(this.getPath())) {
                    return this.getContainingFile();
                }

                String modulePath = this.getPath().concat(".").concat(this.getModule());
                targetFile = FileUtils.findFileByPath(baseUrl, modulePath);
                if (null != targetFile) {
                    return getPsiManager().findFile(targetFile);
                }
            }
        }

        return null;
    }

    protected boolean isSkippedModule() {
        return MODULES_SKIPPED_RESOLVING.contains(this.getModule());
    }

    @Nullable
    protected PsiElement probeResolveRelativePath() {
        VirtualFile targetFile;
        PsiDirectory fileDirectory = element.getContainingFile().getContainingDirectory();
        if (null != fileDirectory) {
            targetFile = FileUtils.findFileByPath(fileDirectory.getVirtualFile(), this.getPath());
            if (null != targetFile) {
                return getPsiManager().findFile(targetFile);
            }
        }

        return null;
    }

    @Nullable
    protected PsiElement resolveAbsolutePath() {
        VirtualFile targetFile = FileUtils.findFileByPath(component.getWebDir(getElementFile()), this.getPath());
        if (null != targetFile) {
            return getPsiManager().findFile(targetFile);
        } else {
            return null;
        }
    }

    protected VirtualFile getElementFile() {
        return element
            .getContainingFile()
            .getOriginalFile()
            .getVirtualFile();
    }

    protected PsiElement getContainingFile() {
        return element.getContainingFile();
    }
}