package org.jabref.gui.preview;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import javafx.scene.input.ClipboardContent;

import org.jabref.logic.citationstyle.CitationStyleGenerator;
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.layout.Layout;
import org.jabref.logic.layout.LayoutFormatterPreferences;
import org.jabref.logic.layout.LayoutHelper;
import org.jabref.logic.os.OS;
import org.jabref.logic.preview.CitationStylePreviewLayout;
import org.jabref.logic.preview.PreviewLayout;
import org.jabref.logic.preview.TextBasedPreviewLayout;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.BibEntryTypesManager;

import com.airhacks.afterburner.injection.Injector;
import com.google.common.annotations.VisibleForTesting;

public record ClipboardContentGenerator(
        PreviewPreferences previewPreferences,
        LayoutFormatterPreferences layoutFormatterPreferences,
        JournalAbbreviationRepository abbreviationRepository) {

    public ClipboardContent generate(List<BibEntry> selectedEntries, CitationStyleOutputFormat outputFormat, BibDatabaseContext bibDatabaseContext) throws IOException {
        List<String> citations = generateCitations(selectedEntries, outputFormat, bibDatabaseContext);
        if (previewPreferences.getSelectedPreviewLayout() instanceof CitationStylePreviewLayout) {
            // if it's generated by a citation style take care of each output format
            return switch (outputFormat) {
                case HTML ->
                        processHtml(citations);
                case TEXT ->
                        processText(citations);
                case MARKDOWN ->
                        processMarkdown(citations);
            };
        } else {
            // if it is not a citation style take care of the preview
            return processPreview(citations);
        }
    }

    private List<String> generateCitations(List<BibEntry> selectedEntries, CitationStyleOutputFormat outputFormat, BibDatabaseContext bibDatabaseContext) throws IOException {
        // This worker stored the style as filename. The CSLAdapter and the CitationStyleCache store the source of the
        // style. Therefore, we extract the style source from the file.
        String styleSource = null;
        PreviewLayout previewLayout = previewPreferences.getSelectedPreviewLayout();

        if (previewLayout instanceof CitationStylePreviewLayout citationStyleLayout) {
            styleSource = citationStyleLayout.getText();
        }

        if (styleSource != null) {
            return CitationStyleGenerator.generateBibliography(
                    selectedEntries,
                    styleSource,
                    outputFormat,
                    bibDatabaseContext,
                    Injector.instantiateModelOrService(BibEntryTypesManager.class));
        } else {
            return generateTextBasedPreviewLayoutCitations(selectedEntries, bibDatabaseContext);
        }
    }

    /// Generates a plain text string out of the preview (based on {@link TextBasedPreviewLayout} or {@link org.jabref.logic.preview.BstPreviewLayout})
    /// and copies it additionally to the html to the clipboard (WYSIWYG Editors use the HTML, plain text editors the text)
    @VisibleForTesting
    static ClipboardContent processPreview(List<String> citations) {
        ClipboardContent content = new ClipboardContent();
        content.putHtml(String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations));
        content.putString(String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations));
        return content;
    }

    /// Joins every citation with a newline and returns it.
    @VisibleForTesting
    static ClipboardContent processText(List<String> citations) {
        ClipboardContent content = new ClipboardContent();
        content.putString(String.join(CitationStyleOutputFormat.TEXT.getLineSeparator(), citations));
        return content;
    }

    /// Inserts each citation into a HTML body and copies it to the clipboard.
    /// The given preview is based on {@link CitationStylePreviewLayout}.
    @VisibleForTesting
    static ClipboardContent processHtml(List<String> citations) {
        String result = "<!DOCTYPE html>" + OS.NEWLINE +
                "<html>" + OS.NEWLINE +
                "   <head>" + OS.NEWLINE +
                "      <meta charset=\"utf-8\">" + OS.NEWLINE +
                "   </head>" + OS.NEWLINE +
                "   <body>" + OS.NEWLINE + OS.NEWLINE;

        result += String.join(CitationStyleOutputFormat.HTML.getLineSeparator(), citations);
        result += OS.NEWLINE +
                "   </body>" + OS.NEWLINE +
                "</html>" + OS.NEWLINE;

        ClipboardContent content = new ClipboardContent();
        content.putString(result);
        content.putHtml(result);
        return content;
    }

    /// Joins each citation using the platform-specific newline into a single Markdown string (from citeproc) and copies it to the clipboard.
    ///
    /// A trailing newline is appended if missing to keep the behavior consistent with other output formats
    /// and to satisfy tests expecting a newline-terminated string.
    ///
    /// @param citations the list of already-formatted citation strings to be combined as Markdown
    /// @return clipboard content containing the Markdown representation in its plain string flavor
    @VisibleForTesting
    static ClipboardContent processMarkdown(List<String> citations) {
        String markdown = String.join(OS.NEWLINE, citations);

        // Ensure trailing newline at end for consistency with other output formats/tests
        if (!markdown.endsWith(OS.NEWLINE)) {
            markdown += OS.NEWLINE;
        }

        ClipboardContent content = new ClipboardContent();
        content.putString(markdown);
        return content;
    }

    private List<String> generateTextBasedPreviewLayoutCitations(List<BibEntry> selectedEntries, BibDatabaseContext bibDatabaseContext) throws IOException {
        Optional<TextBasedPreviewLayout> layoutOpt = previewPreferences.getLayoutCycle().stream()
                                                                       .filter(TextBasedPreviewLayout.class::isInstance)
                                                                       .map(TextBasedPreviewLayout.class::cast)
                                                                       .findFirst();
        TextBasedPreviewLayout customPreviewLayout = layoutOpt.orElse(TextBasedPreviewLayout.of(
                previewPreferences.getCustomPreviewLayout(),
                layoutFormatterPreferences,
                abbreviationRepository));

        Reader customLayoutReader = Reader.of(customPreviewLayout.getText().replace("__NEWLINE__", "\n"));
        Layout layout = new LayoutHelper(customLayoutReader, layoutFormatterPreferences, abbreviationRepository).getLayoutFromText();
        List<String> citations = new ArrayList<>(selectedEntries.size());
        for (BibEntry entry : selectedEntries) {
            citations.add(layout.doLayout(entry, bibDatabaseContext.getDatabase()));
        }
        return citations;
    }
}
