package p.studio.window;

import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import p.studio.Container;
import p.studio.projects.*;
import p.studio.utilities.i18n.I18n;

import java.io.File;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

public final class NewProjectWizard {
    private final ProjectCatalogService projectCatalogService;
    private final ProjectLanguageCatalogService projectLanguageCatalogService;
    private final Stage stage;
    private final AtomicReference<ProjectReference> result = new AtomicReference<>();
    private final Label stepTitle = new Label();
    private final Label stepDescription = new Label();
    private final VBox stepBody = new VBox(12);
    private final Label feedbackLabel = new Label();
    private final Button backButton = new Button();
    private final Button nextButton = new Button();
    private final Button createButton = new Button();
    private final TextField projectNameField = new TextField();
    private final TextField locationField = new TextField();
    private final TextField runtimePathField = new TextField();
    private final ComboBox<String> languageCombo = new ComboBox<>();
    private final ComboBox<ProjectLanguageTemplate.StdlibOption> stdlibCombo = new ComboBox<>();
    private final ComboBox<String> sourceRootCombo = new ComboBox<>();
    private final ComboBox<Integer> indentationWidthCombo = new ComboBox<>();
    private final Map<String, ProjectLanguageTemplate> languageTemplatesById = new LinkedHashMap<>();
    private int stepIndex = 0;

    private NewProjectWizard(Window owner, ProjectCatalogService projectCatalogService) {
        this.projectCatalogService = Objects.requireNonNull(projectCatalogService, "projectCatalogService");
        this.projectLanguageCatalogService = new ProjectLanguageCatalogService();
        this.stage = new Stage();
        stage.initOwner(owner);
        stage.initModality(Modality.WINDOW_MODAL);
        stage.setTitle(Container.i18n().text(I18n.WIZARD_TITLE));
        stage.setScene(new Scene(buildRoot(), 700, 420));
        stage.getScene().getStylesheets().add(Container.theme().getDefaultTheme());

        projectNameField.promptTextProperty().bind(Container.i18n().bind(I18n.LAUNCHER_PROJECT_NAME_PROMPT));
        locationField.setText(projectCatalogService.projectsRoot().toString());
        runtimePathField.promptTextProperty().bind(Container.i18n().bind(I18n.WIZARD_RUNTIME_PROMPT));
        indentationWidthCombo.getItems().setAll(2, 4, 8);
        indentationWidthCombo.getSelectionModel().select(Integer.valueOf(4));
        initializeLanguageTemplates();

        renderStep();
    }

    public static Optional<ProjectReference> showAndWait(Window owner, ProjectCatalogService projectCatalogService) {
        final NewProjectWizard wizard = new NewProjectWizard(owner, projectCatalogService);
        wizard.stage.showAndWait();
        return Optional.ofNullable(wizard.result.get());
    }

    private VBox buildRoot() {
        stepTitle.getStyleClass().add("studio-launcher-section-title");
        stepDescription.getStyleClass().add("studio-launcher-subtitle");
        feedbackLabel.getStyleClass().add("studio-launcher-feedback");
        feedbackLabel.setWrapText(true);

        backButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_BACK));
        backButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
        backButton.setOnAction(ignored -> goBack());

        nextButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_NEXT));
        nextButton.getStyleClass().addAll("studio-button", "studio-button-primary");
        nextButton.setOnAction(ignored -> goNext());

        createButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_CREATE));
        createButton.getStyleClass().addAll("studio-button", "studio-button-primary");
        createButton.setOnAction(ignored -> finishCreate());

        final Button cancelButton = new Button();
        cancelButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_CANCEL));
        cancelButton.getStyleClass().addAll("studio-button", "studio-button-cancel");
        cancelButton.setOnAction(ignored -> stage.close());

        final HBox actions = new HBox(12, backButton, nextButton, createButton, cancelButton);
        actions.setAlignment(Pos.CENTER_RIGHT);

        final VBox root = new VBox(16, stepTitle, stepDescription, stepBody, feedbackLabel, actions);
        root.setPadding(new Insets(24));
        VBox.setVgrow(stepBody, Priority.ALWAYS);
        return root;
    }

    private void renderStep() {
        feedbackLabel.setText("");
        backButton.setDisable(stepIndex == 0);
        nextButton.setVisible(stepIndex < 4);
        nextButton.setManaged(stepIndex < 4);
        createButton.setVisible(stepIndex == 4);
        createButton.setManaged(stepIndex == 4);

        switch (stepIndex) {
            case 0 -> renderNameStep();
            case 1 -> renderLanguageStep();
            case 2 -> renderDetailsStep();
            case 3 -> renderLocationStep();
            case 4 -> renderConfirmStep();
            default -> throw new IllegalStateException("unknown wizard step: " + stepIndex);
        }
    }

    private void renderNameStep() {
        stepTitle.textProperty().unbind();
        stepDescription.textProperty().unbind();
        stepTitle.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_NAME_TITLE));
        stepDescription.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_NAME_DESCRIPTION));
        stepBody.getChildren().setAll(projectNameField);
    }

    private void renderLanguageStep() {
        stepTitle.textProperty().unbind();
        stepDescription.textProperty().unbind();
        stepTitle.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_LANGUAGE_TITLE));
        stepDescription.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_LANGUAGE_DESCRIPTION));

        final Label languageLabel = new Label(Container.i18n().text(I18n.WIZARD_LANGUAGE_LABEL));
        final Label stdlibLabel = new Label(Container.i18n().text(I18n.WIZARD_STDLIB_LABEL));
        final Label sourceRootLabel = new Label(Container.i18n().text(I18n.WIZARD_SOURCE_ROOT_LABEL));

        languageCombo.setMaxWidth(Double.MAX_VALUE);
        stdlibCombo.setMaxWidth(Double.MAX_VALUE);
        sourceRootCombo.setMaxWidth(Double.MAX_VALUE);

        final VBox languageBox = new VBox(6, languageLabel, languageCombo);
        final VBox stdlibBox = new VBox(6, stdlibLabel, stdlibCombo);
        final VBox sourceRootBox = new VBox(6, sourceRootLabel, sourceRootCombo);
        stepBody.getChildren().setAll(languageBox, stdlibBox, sourceRootBox);
    }

    private void renderLocationStep() {
        stepTitle.textProperty().unbind();
        stepDescription.textProperty().unbind();
        stepTitle.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_LOCATION_TITLE));
        stepDescription.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_LOCATION_DESCRIPTION));

        final Button browseButton = new Button();
        browseButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_BROWSE));
        browseButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
        browseButton.setOnAction(ignored -> browseForLocation());

        final HBox row = new HBox(12, locationField, browseButton);
        HBox.setHgrow(locationField, Priority.ALWAYS);
        row.setAlignment(Pos.CENTER_LEFT);
        stepBody.getChildren().setAll(row);
    }

    private void renderConfirmStep() {
        stepTitle.textProperty().unbind();
        stepDescription.textProperty().unbind();
        stepTitle.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_CONFIRM_TITLE));
        stepDescription.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_CONFIRM_DESCRIPTION));

        final Label projectName = new Label(Container.i18n().format(I18n.WIZARD_CONFIRM_NAME, sanitizedName()));
        final Label projectLanguage = new Label(Container.i18n().format(I18n.WIZARD_CONFIRM_LANGUAGE, selectedLanguageId()));
        final Label projectStdlib = new Label(Container.i18n().format(I18n.WIZARD_CONFIRM_STDLIB, selectedStdlibLabel()));
        final Label projectSourceRoot = new Label(Container.i18n().format(I18n.WIZARD_CONFIRM_SOURCE_ROOT, selectedSourceRoot()));
        final Label projectLocation = new Label(Container.i18n().format(I18n.WIZARD_CONFIRM_LOCATION, locationField.getText().trim()));
        final Label projectIndentation = new Label(Container.i18n().format(I18n.WIZARD_CONFIRM_INDENTATION, selectedIndentationLabel()));
        final Label projectRuntime = new Label(Container.i18n().format(I18n.WIZARD_CONFIRM_RUNTIME, selectedRuntimePathLabel()));
        final Label projectRoot = new Label(Container.i18n().format(I18n.WIZARD_CONFIRM_ROOT, resolvedProjectRoot().toString()));

        stepBody.getChildren().setAll(
                projectName,
                projectLanguage,
                projectStdlib,
                projectSourceRoot,
                projectIndentation,
                projectRuntime,
                projectLocation,
                projectRoot);
    }

    private void renderDetailsStep() {
        stepTitle.textProperty().unbind();
        stepDescription.textProperty().unbind();
        stepTitle.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_DETAILS_TITLE));
        stepDescription.textProperty().bind(Container.i18n().bind(I18n.WIZARD_STEP_DETAILS_DESCRIPTION));

        final Label indentationLabel = new Label(Container.i18n().text(I18n.WIZARD_INDENTATION_LABEL));
        final Label runtimeLabel = new Label(Container.i18n().text(I18n.WIZARD_RUNTIME_LABEL));
        final Button runtimeBrowseButton = new Button();
        runtimeBrowseButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_BROWSE));
        runtimeBrowseButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
        runtimeBrowseButton.setOnAction(ignored -> browseForRuntimePath());
        indentationWidthCombo.setMaxWidth(Double.MAX_VALUE);
        runtimePathField.setMaxWidth(Double.MAX_VALUE);
        final VBox indentationBox = new VBox(6, indentationLabel, indentationWidthCombo);
        final HBox runtimeRow = new HBox(12, runtimePathField, runtimeBrowseButton);
        runtimeRow.setAlignment(Pos.CENTER_LEFT);
        HBox.setHgrow(runtimePathField, Priority.ALWAYS);
        final VBox runtimeBox = new VBox(6, runtimeLabel, runtimeRow);
        stepBody.getChildren().setAll(indentationBox, runtimeBox);
    }

    private void goBack() {
        if (stepIndex == 0) {
            return;
        }
        stepIndex -= 1;
        renderStep();
    }

    private void goNext() {
        if (!validateCurrentStep()) {
            return;
        }
        stepIndex += 1;
        renderStep();
    }

    private boolean validateCurrentStep() {
        return switch (stepIndex) {
            case 0 -> validateName();
            case 1 -> validateLanguageSettings();
            case 2 -> validateDetails();
            case 3 -> validateLocation();
            default -> true;
        };
    }

    private boolean validateName() {
        if (sanitizedName().isBlank()) {
            feedbackLabel.setText(Container.i18n().text(I18n.WIZARD_ERROR_NAME_REQUIRED));
            return false;
        }
        return true;
    }

    private boolean validateLanguageSettings() {
        if (selectedLanguageId().isBlank()) {
            feedbackLabel.setText(Container.i18n().text(I18n.WIZARD_ERROR_LANGUAGE_REQUIRED));
            return false;
        }
        if (selectedSourceRoot().isBlank()) {
            feedbackLabel.setText(Container.i18n().text(I18n.WIZARD_ERROR_SOURCE_ROOT_REQUIRED));
            return false;
        }
        if (selectedStdlib() == null) {
            feedbackLabel.setText(Container.i18n().text(I18n.WIZARD_ERROR_STDLIB_REQUIRED));
            return false;
        }
        return true;
    }

    private boolean validateLocation() {
        final String rawLocation = locationField.getText().trim();
        if (rawLocation.isBlank()) {
            feedbackLabel.setText(Container.i18n().text(I18n.WIZARD_ERROR_LOCATION_REQUIRED));
            return false;
        }
        try {
            Path.of(rawLocation);
        } catch (RuntimeException runtimeException) {
            feedbackLabel.setText(runtimeException.getMessage());
            return false;
        }
        return true;
    }

    private boolean validateDetails() {
        if (selectedIndentationWidth() <= 0) {
            feedbackLabel.setText(Container.i18n().text(I18n.WIZARD_ERROR_INDENTATION_REQUIRED));
            return false;
        }
        return true;
    }

    private void finishCreate() {
        if (!validateName() || !validateLanguageSettings() || !validateLocation()) {
            return;
        }

        try {
            result.set(projectCatalogService.createProject(new ProjectCreationRequest(
                    projectNameField.getText(),
                    Path.of(locationField.getText().trim()),
                    selectedLanguageId(),
                    selectedStdlib().version(),
                    selectedSourceRoot(),
                    selectedIndentationWidth(),
                    selectedRuntimePath())));
            stage.close();
        } catch (IllegalArgumentException exception) {
            feedbackLabel.setText(exception.getMessage());
        }
    }

    private void browseForLocation() {
        final DirectoryChooser chooser = new DirectoryChooser();
        chooser.setTitle(Container.i18n().text(I18n.WIZARD_BROWSE));

        final String currentText = locationField.getText().trim();
        if (!currentText.isBlank()) {
            final File currentDirectory = Path.of(currentText).toFile();
            if (currentDirectory.isDirectory()) {
                chooser.setInitialDirectory(currentDirectory);
            }
        }

        final File selected = chooser.showDialog(stage);
        if (selected != null) {
            locationField.setText(selected.toPath().toAbsolutePath().normalize().toString());
        }
    }

    private void browseForRuntimePath() {
        final DirectoryChooser chooser = new DirectoryChooser();
        chooser.setTitle(Container.i18n().text(I18n.WIZARD_RUNTIME_LABEL));

        final String currentText = runtimePathField.getText().trim();
        if (!currentText.isBlank()) {
            final File currentDirectory = Path.of(currentText).toFile();
            if (currentDirectory.isDirectory()) {
                chooser.setInitialDirectory(currentDirectory);
            }
        }

        final File selected = chooser.showDialog(stage);
        if (selected != null) {
            runtimePathField.setText(selected.toPath().toAbsolutePath().normalize().toString());
        }
    }

    private String sanitizedName() {
        return projectNameField.getText() == null ? "" : projectNameField.getText().trim();
    }

    private void initializeLanguageTemplates() {
        final List<ProjectLanguageTemplate> templates = projectLanguageCatalogService.listLanguageTemplates();
        for (ProjectLanguageTemplate template : templates) {
            languageTemplatesById.put(template.languageId(), template);
        }
        languageCombo.getItems().setAll(languageTemplatesById.keySet());
        languageCombo.getSelectionModel().selectedItemProperty().addListener((ignored, oldValue, newValue) -> applyLanguageTemplate(newValue));
        if (!templates.isEmpty()) {
            languageCombo.getSelectionModel().select(projectLanguageCatalogService.getDefaultTemplate().languageId());
        }
    }

    private void applyLanguageTemplate(String languageId) {
        final ProjectLanguageTemplate template = languageTemplatesById.get(languageId);
        if (template == null) {
            stdlibCombo.getItems().clear();
            stdlibCombo.getSelectionModel().clearSelection();
            sourceRootCombo.getItems().clear();
            sourceRootCombo.getSelectionModel().clearSelection();
            return;
        }
        stdlibCombo.getItems().setAll(template.stdlibOptions());
        stdlibCombo.getSelectionModel().select(template.defaultStdlib());
        sourceRootCombo.getItems().setAll(template.sourceRoots());
        sourceRootCombo.getSelectionModel().select(template.defaultSourceRoot());
    }

    private String selectedLanguageId() {
        return languageCombo.getValue() == null ? "" : languageCombo.getValue().trim();
    }

    private String selectedSourceRoot() {
        return sourceRootCombo.getValue() == null ? "" : sourceRootCombo.getValue().trim();
    }

    private ProjectLanguageTemplate.StdlibOption selectedStdlib() {
        return stdlibCombo.getValue();
    }

    private String selectedStdlibLabel() {
        final ProjectLanguageTemplate.StdlibOption selectedStdlib = selectedStdlib();
        return selectedStdlib == null ? "" : selectedStdlib.toString();
    }

    private int selectedIndentationWidth() {
        return indentationWidthCombo.getValue() == null ? 4 : indentationWidthCombo.getValue();
    }

    private String selectedIndentationLabel() {
        return "Spaces: " + selectedIndentationWidth();
    }

    private String selectedRuntimePath() {
        if (runtimePathField.getText() == null) {
            return null;
        }
        final String trimmed = runtimePathField.getText().trim();
        return trimmed.isBlank() ? null : trimmed;
    }

    private String selectedRuntimePathLabel() {
        return selectedRuntimePath() == null ? "-" : selectedRuntimePath();
    }

    private Path resolvedProjectRoot() {
        return Path.of(locationField.getText().trim()).toAbsolutePath().normalize().resolve(sanitizedName());
    }
}
