package p.studio.workspaces.assets.wizards;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
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.packer.messages.AssetReference;
import p.packer.messages.CreateAssetRequest;
import p.packer.messages.CreateAssetResult;
import p.packer.messages.PackerOperationStatus;
import p.packer.messages.assets.AssetFamilyCatalog;
import p.packer.messages.assets.OutputCodecCatalog;
import p.packer.messages.assets.OutputFormatCatalog;
import p.studio.Container;
import p.studio.projects.ProjectReference;
import p.studio.utilities.i18n.I18n;
import p.studio.workspaces.assets.details.AssetDetailsUiSupport;
import p.studio.workspaces.assets.metadata.AssetStudioGlyphSpecialization;
import p.studio.workspaces.assets.metadata.AssetStudioMetadataService;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

public final class AddAssetWizard {
    private final ProjectReference projectReference;
    private final Stage stage;
    private final AtomicReference<AssetReference> 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 assetRootField = new TextField();
    private final TextField assetNameField = new TextField();
    private final ComboBox<AssetFamilyCatalog> assetFamilyCombo = new ComboBox<>();
    private final ComboBox<AssetStudioGlyphSpecialization> glyphSpecializationCombo = new ComboBox<>();
    private final ComboBox<OutputFormatCatalog> outputFormatCombo = new ComboBox<>();
    private final ComboBox<OutputCodecCatalog> outputCodecCombo = new ComboBox<>();
    private final CheckBox preloadCheckBox = new CheckBox();
    private final AssetStudioMetadataService studioMetadataService = new AssetStudioMetadataService();

    private int stepIndex;
    private boolean creating;

    private AddAssetWizard(Window owner, ProjectReference projectReference) {
        this.projectReference = Objects.requireNonNull(projectReference, "projectReference");
        this.stage = new Stage();
        stage.initOwner(owner);
        stage.initModality(Modality.WINDOW_MODAL);
        stage.setTitle(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_TITLE));
        stage.setScene(new Scene(buildRoot(), 720, 500));
        stage.getScene().getStylesheets().add(Container.theme().getDefaultTheme());

        preloadCheckBox.setSelected(false);
        configureAssetFamilyCombo();
        configureGlyphSpecializationCombo();
        configureOutputFormatCombo();
        configureOutputCodecCombo();
        renderStep();
    }

    public static Optional<AssetReference> showAndWait(Window owner, ProjectReference projectReference) {
        final AddAssetWizard wizard = new AddAssetWizard(owner, projectReference);
        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.ASSETS_ADD_WIZARD_BUTTON_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 configureAssetFamilyCombo() {
        assetFamilyCombo.setItems(FXCollections.observableArrayList(
                java.util.Arrays.stream(AssetFamilyCatalog.values())
                        .filter(candidate -> candidate != AssetFamilyCatalog.UNKNOWN)
                        .toList()));
        assetFamilyCombo.setMaxWidth(Double.MAX_VALUE);
        assetFamilyCombo.setPromptText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_PROMPT_TYPE));
        assetFamilyCombo.setCellFactory(ignored -> new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(AssetFamilyCatalog item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : AssetDetailsUiSupport.typeLabel(item));
            }
        });
        assetFamilyCombo.setButtonCell(new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(AssetFamilyCatalog item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : AssetDetailsUiSupport.typeLabel(item));
            }
        });
        assetFamilyCombo.valueProperty().addListener((ignored, oldValue, newValue) -> {
            if (!Objects.equals(oldValue, newValue)) {
                if (newValue != AssetFamilyCatalog.GLYPH_BANK) {
                    glyphSpecializationCombo.getSelectionModel().clearSelection();
                } else if (glyphSpecializationCombo.getValue() == null) {
                    glyphSpecializationCombo.getSelectionModel().select(AssetStudioGlyphSpecialization.SPRITES);
                }
                refreshOutputFormats();
                if (stepIndex == 1) {
                    renderStep();
                }
            }
        });
    }

    private void configureGlyphSpecializationCombo() {
        glyphSpecializationCombo.setItems(FXCollections.observableArrayList(
                java.util.Arrays.stream(AssetStudioGlyphSpecialization.values())
                        .filter(candidate -> candidate != AssetStudioGlyphSpecialization.NONE)
                        .toList()));
        glyphSpecializationCombo.setMaxWidth(Double.MAX_VALUE);
        glyphSpecializationCombo.setPromptText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_PROMPT_SPECIALIZATION));
        glyphSpecializationCombo.getSelectionModel().select(AssetStudioGlyphSpecialization.SPRITES);
        glyphSpecializationCombo.setCellFactory(ignored -> new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(AssetStudioGlyphSpecialization item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : AssetDetailsUiSupport.specializationLabel(item));
            }
        });
        glyphSpecializationCombo.setButtonCell(new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(AssetStudioGlyphSpecialization item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : AssetDetailsUiSupport.specializationLabel(item));
            }
        });
    }

    private void configureOutputFormatCombo() {
        outputFormatCombo.setMaxWidth(Double.MAX_VALUE);
        outputFormatCombo.setPromptText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_PROMPT_FORMAT));
        outputFormatCombo.setCellFactory(ignored -> new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(OutputFormatCatalog item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : item.displayName());
            }
        });
        outputFormatCombo.setButtonCell(new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(OutputFormatCatalog item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : item.displayName());
            }
        });
        outputFormatCombo.valueProperty().addListener((ignored, oldValue, newValue) -> {
            if (!Objects.equals(oldValue, newValue)) {
                refreshOutputCodecs();
            }
        });
    }

    private void configureOutputCodecCombo() {
        outputCodecCombo.setMaxWidth(Double.MAX_VALUE);
        outputCodecCombo.setPromptText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_PROMPT_CODEC));
        outputCodecCombo.setCellFactory(ignored -> new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(OutputCodecCatalog item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : item.displayName());
            }
        });
        outputCodecCombo.setButtonCell(new javafx.scene.control.ListCell<>() {
            @Override
            protected void updateItem(OutputCodecCatalog item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty || item == null ? null : item.displayName());
            }
        });
    }

    private void renderStep() {
        final boolean rootStep = stepIndex == 0;
        backButton.setDisable(rootStep || creating);
        nextButton.setDisable(creating);
        createButton.setDisable(creating);
        nextButton.setVisible(stepIndex < 1);
        nextButton.setManaged(stepIndex < 1);
        createButton.setVisible(stepIndex == 1);
        createButton.setManaged(stepIndex == 1);

        switch (stepIndex) {
            case 0 -> renderRootStep();
            case 1 -> renderDetailsStep();
            default -> throw new IllegalStateException("unknown add asset wizard step: " + stepIndex);
        }
    }

    private void renderRootStep() {
        stepTitle.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_STEP_ROOT_TITLE));
        stepDescription.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_STEP_ROOT_DESCRIPTION));

        final Label rootLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_ROOT));
        final Button browseButton = new Button();
        browseButton.textProperty().bind(Container.i18n().bind(I18n.WIZARD_BROWSE));
        browseButton.getStyleClass().addAll("studio-button", "studio-button-secondary");
        browseButton.setDisable(creating);
        browseButton.setOnAction(ignored -> browseForRoot());

        final HBox row = new HBox(12, assetRootField, browseButton);
        HBox.setHgrow(assetRootField, Priority.ALWAYS);

        final Label assetsRootHint = new Label(Container.i18n().format(
                I18n.ASSETS_ADD_WIZARD_ASSETS_ROOT_HINT,
                projectReference.rootPath().resolve("assets")));
        assetsRootHint.getStyleClass().add("studio-launcher-subtitle");

        stepBody.getChildren().setAll(rootLabel, row, assetsRootHint);
    }

    private void renderDetailsStep() {
        stepTitle.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_STEP_DETAILS_TITLE));
        stepDescription.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_STEP_DETAILS_DESCRIPTION));

        final Label nameLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_NAME));
        final Label typeLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_TYPE));
        final Label specializationLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_SPECIALIZATION));
        final Label formatLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_FORMAT));
        final Label codecLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_CODEC));
        final Label preloadLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_LABEL_PRELOAD));
        final Label noteLabel = new Label(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_NOTE));
        noteLabel.setWrapText(true);
        noteLabel.getStyleClass().add("studio-launcher-subtitle");
        final VBox specializationBox = new VBox(6, specializationLabel, glyphSpecializationCombo);
        final boolean specializationVisible = selectedFamily() == AssetFamilyCatalog.GLYPH_BANK;
        specializationBox.setVisible(specializationVisible);
        specializationBox.setManaged(specializationVisible);

        preloadCheckBox.setText("");
        stepBody.getChildren().setAll(
                new VBox(6, nameLabel, assetNameField),
                new VBox(6, typeLabel, assetFamilyCombo),
                specializationBox,
                new VBox(6, formatLabel, outputFormatCombo),
                new VBox(6, codecLabel, outputCodecCombo),
                new VBox(6, preloadLabel, preloadCheckBox),
                noteLabel);
    }

    private void goBack() {
        if (stepIndex == 0 || creating) {
            return;
        }
        feedbackLabel.setText("");
        stepIndex -= 1;
        renderStep();
    }

    private void goNext() {
        if (creating || !validateCurrentStep()) {
            return;
        }
        feedbackLabel.setText("");
        stepIndex += 1;
        renderStep();
    }

    private boolean validateCurrentStep() {
        return switch (stepIndex) {
            case 0 -> validateRoot();
            case 1 -> validateDetails();
            default -> true;
        };
    }

    private boolean validateRoot() {
        final String relativeRoot = normalizedRelativeRoot(assetRootField.getText());
        if (relativeRoot == null) {
            feedbackLabel.setText(Container.i18n().text(
                    assetRootField.getText().trim().isBlank()
                            ? I18n.ASSETS_ADD_WIZARD_ERROR_ROOT
                            : I18n.ASSETS_ADD_WIZARD_ERROR_ROOT_RELATIVE));
            return false;
        }
        final Path assetRoot = ensureAssetsRoot().resolve(relativeRoot).toAbsolutePath().normalize();
        if (Files.isRegularFile(assetRoot.resolve("asset.json"))) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_ROOT_ALREADY_ASSET));
            return false;
        }
        return true;
    }

    private boolean validateDetails() {
        if (assetNameField.getText().trim().isBlank()) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_NAME));
            return false;
        }
        if (selectedFamily() == null || selectedFamily() == AssetFamilyCatalog.UNKNOWN) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_TYPE));
            return false;
        }
        if (selectedFamily() == AssetFamilyCatalog.GLYPH_BANK
                && (selectedGlyphSpecialization() == null || selectedGlyphSpecialization() == AssetStudioGlyphSpecialization.NONE)) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_SPECIALIZATION));
            return false;
        }
        if (selectedFormat() == null || selectedFormat() == OutputFormatCatalog.UNKNOWN) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_FORMAT));
            return false;
        }
        if (selectedCodec() == null || selectedCodec() == OutputCodecCatalog.UNKNOWN) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_CODEC));
            return false;
        }
        if (!selectedFormat().supports(selectedCodec())) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_UNSUPPORTED_COMBINATION));
            return false;
        }
        return true;
    }

    private void browseForRoot() {
        final Path assetsRoot = ensureAssetsRoot();
        final DirectoryChooser chooser = new DirectoryChooser();
        chooser.setTitle(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_BROWSE_TITLE));
        chooser.setInitialDirectory((assetsRoot.toFile().isDirectory() ? assetsRoot : projectReference.rootPath()).toFile());
        final File selected = chooser.showDialog(stage);
        if (selected == null) {
            return;
        }
        final Path selectedPath = selected.toPath().toAbsolutePath().normalize();
        if (!selectedPath.startsWith(assetsRoot)) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_ROOT_OUTSIDE_ASSETS));
            return;
        }
        if (Files.isRegularFile(selectedPath.resolve("asset.json"))) {
            feedbackLabel.setText(Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_ROOT_ALREADY_ASSET));
            return;
        }
        assetRootField.setText(assetsRoot.relativize(selectedPath).toString().replace('\\', '/'));
    }

    private void finishCreate() {
        if (creating || !validateRoot() || !validateDetails()) {
            return;
        }

        creating = true;
        feedbackLabel.setText("");
        renderStep();
        final CreateAssetRequest request = new CreateAssetRequest(
                projectReference.toPackerProjectContext(),
                normalizedRelativeRoot(assetRootField.getText()),
                assetNameField.getText().trim(),
                selectedFamily(),
                selectedFormat(),
                selectedCodec(),
                preloadCheckBox.isSelected());
        Container.backgroundTasks().submit(() -> createAsset(request));
    }

    private void createAsset(CreateAssetRequest request) {
        try {
            final CreateAssetResult createResult = Container.packer().workspaceService().createAsset(request);
            Platform.runLater(() -> applyCreateResult(createResult));
        } catch (RuntimeException exception) {
            Platform.runLater(() -> applyCreateFailure(exception));
        }
    }

    private void applyCreateResult(CreateAssetResult createResult) {
        creating = false;
        if (createResult.status() == PackerOperationStatus.SUCCESS && createResult.assetReference() != null) {
            try {
                persistStudioMetadata(createResult.assetRoot());
            } catch (IOException exception) {
                feedbackLabel.setText(exception.getMessage() == null || exception.getMessage().isBlank()
                        ? "Unable to persist Studio metadata."
                        : exception.getMessage());
                renderStep();
                return;
            }
            result.set(createResult.assetReference());
            stage.close();
            return;
        }
        feedbackLabel.setText(Objects.requireNonNullElse(createResult.summary(), "Unable to create asset."));
        renderStep();
    }

    private void applyCreateFailure(RuntimeException exception) {
        creating = false;
        feedbackLabel.setText(exception.getMessage() == null || exception.getMessage().isBlank()
                ? "Unable to create asset."
                : exception.getMessage());
        renderStep();
    }

    private void refreshOutputFormats() {
        outputFormatCombo.setItems(FXCollections.observableArrayList(OutputFormatCatalog.supportedFor(selectedFamily())));
        if (!outputFormatCombo.getItems().isEmpty()) {
            outputFormatCombo.getSelectionModel().selectFirst();
        } else {
            outputFormatCombo.getSelectionModel().clearSelection();
        }
        refreshOutputCodecs();
    }

    private void refreshOutputCodecs() {
        final OutputFormatCatalog selectedFormat = selectedFormat();
        outputCodecCombo.setItems(FXCollections.observableArrayList(
                selectedFormat == null ? java.util.List.of() : selectedFormat.availableCodecs()));
        if (!outputCodecCombo.getItems().isEmpty()) {
            outputCodecCombo.getSelectionModel().selectFirst();
        } else {
            outputCodecCombo.getSelectionModel().clearSelection();
        }
    }

    private AssetFamilyCatalog selectedFamily() {
        return assetFamilyCombo.getValue();
    }

    private OutputFormatCatalog selectedFormat() {
        return outputFormatCombo.getValue();
    }

    private OutputCodecCatalog selectedCodec() {
        return outputCodecCombo.getValue();
    }

    private AssetStudioGlyphSpecialization selectedGlyphSpecialization() {
        return glyphSpecializationCombo.getValue();
    }

    private void persistStudioMetadata(Path assetRoot) throws IOException {
        if (selectedFamily() != AssetFamilyCatalog.GLYPH_BANK) {
            return;
        }
        studioMetadataService.writeGlyphSpecialization(assetRoot, selectedGlyphSpecialization());
    }

    private String normalizedRelativeRoot(String candidate) {
        final String raw = Objects.requireNonNullElse(candidate, "").trim().replace('\\', '/');
        if (raw.isBlank()) {
            return null;
        }
        final Path normalized = Path.of(raw).normalize();
        if (normalized.isAbsolute() || normalized.startsWith("..")) {
            return null;
        }
        final String value = normalized.toString().replace('\\', '/');
        return value.isBlank() ? null : value;
    }

    private Path ensureAssetsRoot() {
        final Path assetsRoot = projectReference.rootPath().resolve("assets").toAbsolutePath().normalize();
        try {
            Files.createDirectories(assetsRoot);
        } catch (IOException exception) {
            feedbackLabel.setText(exception.getMessage() == null || exception.getMessage().isBlank()
                    ? Container.i18n().text(I18n.ASSETS_ADD_WIZARD_ERROR_ROOT_OUTSIDE_ASSETS)
                    : exception.getMessage());
        }
        return assetsRoot;
    }
}
