package com.dansoftware.mdeditor;

import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.IndexRange;
import javafx.scene.control.SkinBase;
import javafx.scene.control.SplitPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import leigh.mecha.lang.Transformer;
import leigh.mecha.log.MechaLogger;
import leigh.mecha.log.MechaLoggerFactory;

import java.util.Objects;

public class MarkdownEditorSkin extends SkinBase<MarkdownEditorControl> {
    private static final MechaLogger logger = MechaLoggerFactory.getLogger(MarkdownEditorSkin.class);

    private final Toolbar toolbar;
    private final SplitPane splitPane;
    private final EditorArea editorArea;
    private final PreviewArea previewArea;
    private final Transformer<String> stringTransformer;

    public MarkdownEditorSkin(MarkdownEditorControl control, Transformer<String> stringTransformer) {
        super(control);
        this.toolbar = buildToolbar(control);
        this.editorArea = buildEditorArea(control);
        this.previewArea = buildPreviewArea(control);
        this.stringTransformer = stringTransformer;
        this.splitPane = buildSplitPane();
        addListeners(control);
        setupBindings();
        buildUI();
        suitUI(control.getViewMode());
    }

    private void buildUI() {
        VBox vBox = new VBox();
        vBox.getChildren().add(toolbar);
        vBox.getChildren().add(splitPane);
        getChildren().add(vBox);
    }

    private void addListeners(MarkdownEditorControl control) {
        control.viewModeProperty().addListener((observable, oldValue, newValue) -> suitUI(newValue));
        editorArea.textProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                logger.info("Editor content change.");
                previewArea.markdownProperty().set(stringTransformer == null ? newValue : stringTransformer.transform(newValue));
                control.markdownProperty().set(newValue);
            }
        });
        control.markdownProperty().addListener((observable, oldValue, newValue) -> editorArea.setText(newValue));
    }

    private void setupBindings() {
        String newValue = editorArea.textProperty().getValue();
        previewArea.markdownProperty().set(stringTransformer == null ? newValue : stringTransformer.transform(newValue));
    }

    private void clearBindings() {
        previewArea.markdownProperty().unbind();
        previewArea.markdownProperty().set("");
    }

    private Toolbar buildToolbar(MarkdownEditorControl control) {
        var toolbar = new Toolbar(control);
        toolbar.visibleProperty().bind(control.toolbarVisibleProperty());
        toolbar.managedProperty().bind(control.toolbarVisibleProperty());
        return toolbar;
    }

    private SplitPane buildSplitPane() {
        var splitPane = new SplitPane();
        VBox.setVgrow(splitPane, Priority.ALWAYS);
        return splitPane;
    }

    private EditorArea buildEditorArea(MarkdownEditorControl control) {
        EditorArea editorArea = new EditorArea();
        editorArea.editableProperty().bind(control.editableProperty());
        editorArea.setText(control.getMarkdown());
        return editorArea;
    }

    private PreviewArea buildPreviewArea(MarkdownEditorControl control) {
        PreviewArea previewArea = new PreviewArea();
        previewArea.onLinkClickedProperty().bind(control.onLinkClickedProperty());
        return previewArea;
    }

    public void suitUI(MarkdownEditorControl.ViewMode viewMode) {
        Objects.requireNonNull(viewMode, "ViewMode shouldn't be null!");
        switch (viewMode) {
            case EDITOR_ONLY:
                clearBindings();
                splitPane.getItems().setAll(editorArea);
                break;
            case PREVIEW_ONLY:
                setupBindings();
                splitPane.getItems().setAll(previewArea);
                break;
            case BOTH:
                setupBindings();
                splitPane.getItems().setAll(editorArea, previewArea);
                break;
        }
    }

    public ObservableValue<IndexRange> selectionProperty() {
        return editorArea.selectionProperty();
    }

    public ObservableValue<String> selectedTextProperty() {
        return editorArea.selectedTextProperty();
    }

    public void insertText(String txt) {
        logger.info("Inserting text: {}", txt);
        int pos = editorArea.getContent().getCaretPosition();
        editorArea.getContent().insertText(pos, txt);
    }

    public void replaceText(int start, int end, String replacement) {
        editorArea.getContent().replaceText(start, end, replacement);
    }

    public void selectRange(int from, int to) {
        editorArea.selectRange(from, to);
    }
}
