import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.ui.ConsoleViewContentType
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vcs.changes.ChangeListManager
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager
import com.intellij.openapi.vfs.LocalFileSystem
import java.util.concurrent.atomic.AtomicBoolean
import static liveplugin.PluginUtil.registerAction
import static liveplugin.PluginUtil.showInConsole

// Loading this plugin only registers an action; it does not bump the version or commit anything.
def running = new AtomicBoolean(false)
def title = 'Auto version and Commit'
def actionId = 'Monima.BumpVersionAndOpenCommit'
def actionManager = ActionManager.instance
def toolsMenu = actionManager.getAction('ToolsMenu') as DefaultActionGroup

// Earlier reloads left unregistered action objects in Tools. Remove those as well as the current entry.
toolsMenu.getChildActionsOrStubs().findAll { item ->
    actionManager.getId(item) == actionId || item.templatePresentation.text in [title, 'Bump version and open Commit']
}.each { item -> toolsMenu.remove(item) }

def registeredAction = registerAction(actionId, '', 'ToolsMenu', title, pluginDisposable) { event ->
    def currentProject = event.project
    // Use the boolean method: Groovy .disposed resolves to getDisposed(), which returns a Condition.
    if (currentProject == null || currentProject.isDisposed()) return
    def root = currentProject.basePath
    def script = root == null ? null : new File(root, 'resources/develop/versioning/auto_version.bat')
    if (script == null || !script.isFile()) {
        Messages.showErrorDialog(currentProject, 'This project does not contain resources/develop/versioning/auto_version.bat.', title)
        return
    }
    if (!running.compareAndSet(false, true)) return

    def fail = { Throwable error ->
        running.set(false)
        if (!currentProject.isDisposed()) {
            Messages.showErrorDialog(currentProject, error.message ?: error.toString(), title)
        }
    }

    try {
        FileDocumentManager.instance.saveAllDocuments()
        def console = showInConsole('Running auto_version.bat...\n', title, currentProject)
        new Task.Backgroundable(currentProject, title, false) {
            @Override
            void run(ProgressIndicator indicator) {
                indicator.indeterminate = true
                indicator.text = 'Updating and staging config/version.php...'
                def command = new GeneralCommandLine('cmd.exe', '/d', '/c', 'call', script.absolutePath)
                    .withWorkDirectory(root)
                def handler = new CapturingProcessHandler(command)
                console.attachToProcess(handler)
                def output = handler.runProcess(120000)
                console.print("\nProcess finished with exit code ${output.exitCode}" +
                    (output.timeout ? ' (timed out)' : '') + '\n', ConsoleViewContentType.SYSTEM_OUTPUT)
                if (output.timeout || output.exitCode != 0) {
                    def reason = output.timeout ? 'Version script timed out after two minutes.' : "Version script exited with code ${output.exitCode}."
                    throw new IllegalStateException(reason + '\n\n' + (output.stdout + output.stderr).take(8000))
                }
                if (currentProject.isDisposed()) return
                indicator.text = 'Refreshing the version file...'
                // Synchronous refresh runs on this background thread, before requesting the Git refresh.
                def versionFile = LocalFileSystem.instance.refreshAndFindFileByIoFile(new File(root, 'config/version.php'))
                if (versionFile == null) throw new IllegalStateException('Cannot refresh config/version.php.')
                versionFile.refresh(false, false)
                VcsDirtyScopeManager.getInstance(currentProject).fileDirty(versionFile)
            }

            @Override
            void onSuccess() {
                if (currentProject.isDisposed()) {
                    running.set(false)
                    return
                }
                try {
                    // The callback runs on the UI thread after the pending change-list refresh completes.
                    ChangeListManager.getInstance(currentProject).invokeAfterUpdate(true, {
                        try {
                            if (!currentProject.isDisposed()) {
                                def commitAction = ActionManager.instance.getAction('CheckinProject')
                                if (commitAction == null) throw new IllegalStateException('PhpStorm Commit action was not found.')
                                ActionUtil.invokeAction(commitAction, SimpleDataContext.getProjectContext(currentProject),
                                    ActionPlaces.UNKNOWN, null, null)
                            }
                        } catch (Exception error) {
                            fail(error)
                        } finally {
                            running.set(false)
                        }
                    } as Runnable)
                } catch (Exception error) {
                    fail(error)
                }
            }

            @Override
            void onThrowable(Throwable error) {
                fail(error)
            }
        }.queue()
    } catch (Exception error) {
        fail(error)
    }
}

// Unregistering an action ID alone does not remove the action object from its menu group.
Disposer.register(pluginDisposable, { toolsMenu.remove(registeredAction) } as Disposable)
