/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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 com.facebook.litho.intellij.toolwindows;

import com.facebook.litho.intellij.LithoPluginUtils;
import com.facebook.litho.intellij.extensions.EventLogger;
import com.facebook.litho.intellij.logging.LithoLoggerProvider;
import com.facebook.litho.intellij.services.ComponentGenerateService;
import com.facebook.litho.specmodels.model.SpecModel;
import com.intellij.ide.structureView.StructureView;
import com.intellij.ide.structureView.StructureViewFactory;
import com.intellij.ide.structureView.StructureViewModel;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.content.ContentManager;
import com.intellij.util.ui.FormBuilder;
import com.siyeh.ig.ui.BlankFiller;
import java.awt.event.ActionEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import org.jetbrains.annotations.Nullable;

/** Service creates structure view reflecting {@link ComponentTreeModel}. */
class ComponentStructureView implements Disposable {
  private static final BlankFiller STUB = new BlankFiller();
  private final Project project;
  private ContentManager contentManager;
  private Content contentContainer;
  private JButton refreshButton;
  @Nullable private StructureView structureView;

  static ComponentStructureView getInstance(Project project) {
    return ServiceManager.getService(project, ComponentStructureView.class);
  }

  ComponentStructureView(Project project) {
    this.project = project;
    Disposer.register(project, this);
  }

  @Override
  public void dispose() {
    contentManager = null;
    refreshButton = null;
    contentContainer.dispose();
    contentContainer = null;
    dispose(structureView);
  }

  synchronized void setup(ToolWindow toolWindow) {
    contentManager = toolWindow.getContentManager();
    Disposer.register(contentManager, this);
    refreshButton = createButton("Update", this::updateView);
    contentContainer =
        ContentFactory.SERVICE
            .getInstance()
            .createContent(createView(refreshButton, STUB), "", false);
    contentManager.addContent(contentContainer);
    DumbService.getInstance(project).smartInvokeLater(this::updateView);
  }

  synchronized void updateView() {
    final FileEditor selectedEditor = FileEditorManager.getInstance(project).getSelectedEditor();
    final PsiFile selectedFile = getSelectedFile(selectedEditor, project);
    final StructureView oldStructure = structureView;
    final Map<String, String> data = new HashMap<>();
    data.put(EventLogger.KEY_TYPE, "update");
    // Overriden below
    data.put(EventLogger.KEY_RESULT, "fail");
    final JComponent mainView =
        Optional.ofNullable(selectedFile)
            .flatMap(file -> LithoPluginUtils.getFirstClass(file, LithoPluginUtils::isLayoutSpec))
            .map(cls -> cls.getUserData(ComponentGenerateService.KEY_SPEC_MODEL))
            .map(
                model -> {
                  structureView = createStructureView(model, selectedEditor, selectedFile, project);
                  data.put(EventLogger.KEY_RESULT, "success");
                  return structureView.getComponent();
                })
            .orElse(STUB);
    final JComponent newView = createView(refreshButton, mainView);
    contentContainer.setComponent(newView);
    // View wasn't updating without this step
    contentManager.setSelectedContent(contentContainer);
    dispose(oldStructure);
    LithoLoggerProvider.getEventLogger().log(EventLogger.EVENT_TOOLWINDOW, data);
  }

  private void dispose(@Nullable StructureView oldStructure) {
    if (oldStructure == null) return;

    oldStructure.dispose();
    if (this.structureView == oldStructure) {
      this.structureView = null;
    }
  }

  private static StructureView createStructureView(
      SpecModel model, FileEditor selectedEditor, PsiFile selectedFile, Project project) {
    final StructureViewModel viewModel = ComponentTreeModel.create(selectedFile, model);
    final StructureView view =
        StructureViewFactory.getInstance(project)
            .createStructureView(selectedEditor, viewModel, project, true);
    Disposer.register(view, viewModel);
    return view;
  }

  private static JComponent createView(JComponent top, JComponent bottom) {
    return FormBuilder.createFormBuilder()
        .addComponent(top, 1)
        .addComponentFillVertically(bottom, 0)
        .getPanel();
  }

  private static JButton createButton(String name, Runnable action) {
    return new JButton(
        new AbstractAction(name) {
          @Override
          public void actionPerformed(ActionEvent actionEvent) {
            action.run();
          }
        });
  }

  @Nullable
  private static PsiFile getSelectedFile(@Nullable FileEditor editor, Project project) {
    if (editor == null) return null;

    final VirtualFile vf = editor.getFile();
    if (vf == null) return null;

    return PsiManager.getInstance(project).findFile(vf);
  }
}

@@ -1845,43 +1845,8 @@
 er;%0A
-import java.awt.event.ActionEvent;%0A
 impo
@@ -1920,71 +1920,8 @@
 al;%0A
-import javax.swing.AbstractAction;%0Aimport javax.swing.JButton;%0A
 impo
@@ -2296,41 +2296,8 @@
 er;%0A
-  private JButton refreshButton;%0A
   @N
@@ -2671,34 +2671,8 @@
 ll;%0A
-    refreshButton = null;%0A
     
@@ -2914,70 +2914,8 @@
 s);%0A
-    refreshButton = createButton(%22Update%22, this::updateView);%0A
     
@@ -2932,24 +2932,16 @@
 tainer =
-%0A       
  Content
@@ -2955,29 +2955,16 @@
 .SERVICE
-%0A            
 .getInst
@@ -2969,29 +2969,16 @@
 stance()
-%0A            
 .createC
@@ -2988,39 +2988,12 @@
 ent(
-createView(refreshButton, 
 STUB
-)
 , %22%22
@@ -3047,24 +3047,87 @@
 Container);%0A
+    updateViewLater();%0A  %7D%0A%0A  private void updateViewLater() %7B%0A
     DumbServ
@@ -3644,20 +3644,19 @@
 mponent 
-main
+new
 View =%0A 
@@ -4191,76 +4191,8 @@
 B);%0A
-    final JComponent newView = createView(refreshButton, mainView);%0A
     
@@ -5345,277 +5345,8 @@
  %7D%0A%0A
-  private static JButton createButton(String name, Runnable action) %7B%0A    return new JButton(%0A        new AbstractAction(name) %7B%0A          @Override%0A          public void actionPerformed(ActionEvent actionEvent) %7B%0A            action.run();%0A          %7D%0A        %7D);%0A  %7D%0A%0A
   @N

Remove refresh button from toolwindow