package fr.adrienbrault.idea.symfony2plugin.dic.intention;

import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.components.JBList;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.php.lang.PhpLanguage;
import com.jetbrains.php.lang.psi.elements.PhpClass;
import fr.adrienbrault.idea.symfony2plugin.action.ServiceActionUtil;
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
import fr.adrienbrault.idea.symfony2plugin.stubs.ServiceIndexUtil;
import fr.adrienbrault.idea.symfony2plugin.util.ProjectUtil;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.yaml.psi.YAMLKeyValue;

import java.util.*;

/**
 * @author Daniel Espendiller <[email protected]>
 */
public class PhpServiceArgumentIntention extends PsiElementBaseIntentionAction {
    @Override
    public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) throws IncorrectOperationException {
        PhpClass phpClass = PsiTreeUtil.getParentOfType(psiElement, PhpClass.class);
        if (phpClass == null) {

            return;
        }

        Set<String> serviceNames = ContainerCollectionResolver.ServiceCollector.create(project).convertClassNameToServices(phpClass.getFQN());
        if (serviceNames.isEmpty()) {

            return;
        }

        Collection<PsiElement> psiElements = new ArrayList<>();
        for (String serviceName : serviceNames) {
            psiElements.addAll(ServiceIndexUtil.findServiceDefinitions(project, serviceName));
        }

        if (psiElements.isEmpty()) {

            return;
        }

        Map<String, PsiElement> map = new HashMap<>();

        for (PsiElement element : psiElements) {
            map.put(VfsUtil.getRelativePath(element.getContainingFile().getVirtualFile(), ProjectUtil.getProjectDir(element)), element);
        }

        final JBList<String> list = new JBList<>(map.keySet());
        JBPopupFactory.getInstance().createListPopupBuilder(list)
                .setTitle("Symfony: Services Definitions")
                .setItemChoosenCallback(() -> WriteCommandAction.writeCommandAction(project).withName("Service Update").run(() -> invokeByScope(map.get(list.getSelectedValue()), editor)))
                .createPopup()
                .showInBestPositionFor(editor)
        ;
    }

    private void invokeByScope(@NotNull PsiElement psiElement, @NotNull Editor editor) {
        boolean success = false;

        if (psiElement instanceof XmlTag) {
            List<String> args = ServiceActionUtil.getXmlMissingArgumentTypes((XmlTag) psiElement, true, new ContainerCollectionResolver.LazyServiceCollector(psiElement.getProject()));

            success = args.size() > 0;
            if (success) {
                ServiceActionUtil.fixServiceArgument(args, (XmlTag) psiElement);
            }
        } else if (psiElement instanceof YAMLKeyValue) {
            List<String> args = ServiceActionUtil.getYamlMissingArgumentTypes(
                    psiElement.getProject(),
                    ServiceActionUtil.ServiceYamlContainer.create((YAMLKeyValue) psiElement),
                    false,
                    new ContainerCollectionResolver.LazyServiceCollector(psiElement.getProject())
            );

            success = args.size() > 0;
            if (success) {
                ServiceActionUtil.fixServiceArgument((YAMLKeyValue) psiElement);
            }
        }

        if (!success) {
            HintManager.getInstance().showErrorHint(editor, "No argument update needed");

            return;
        }

        String relativePath = VfsUtil.getRelativePath(psiElement.getContainingFile().getVirtualFile(), ProjectUtil.getProjectDir(psiElement));
        if (relativePath == null) {
            relativePath = "n/a";
        }

        HintManager.getInstance().showInformationHint(editor, String.format("Argument updated: %s", relativePath));
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement psiElement) {
        return psiElement.getLanguage().equals(PhpLanguage.INSTANCE) && getServicesInScope(psiElement).size() > 0;
    }

    @Nls
    @NotNull
    @Override
    public String getFamilyName() {
        return "Symfony";
    }

    @NotNull
    @Override
    public String getText() {
        return "Symfony: Update service arguments";
    }

    @NotNull
    private Set<String> getServicesInScope(@NotNull PsiElement psiElement) {
        PhpClass phpClass = PsiTreeUtil.getParentOfType(psiElement, PhpClass.class);

        return phpClass == null
                ? Collections.emptySet()
                : ContainerCollectionResolver.ServiceCollector.create(psiElement.getProject()).convertClassNameToServices(phpClass.getFQN());
    }
}