package de.duehl.vocabulary.japanese.startup.logic.steps;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.duehl.basics.datetime.time.watch.StopWatch;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.text.NumberString;
import de.duehl.basics.text.Text;
import de.duehl.swing.ui.GuiTools;
import de.duehl.vocabulary.japanese.common.persistence.Options;
import de.duehl.vocabulary.japanese.data.Vocable;
import de.duehl.vocabulary.japanese.data.Vocabulary;
import de.duehl.vocabulary.japanese.startup.ui.data.SplashScreenable;

/**
 * Diese Klasse steht für den Schritt zum Finden der MP3 und Zuordnen dieser zu den Vokabeln beim
 * Startup des Vokabeltrainers.
 *
 * @version 1.01     2025-11-24
 * @author Christian Dühl
 */

public class Step03FindMP3AndStoreInVocables extends StartupStep {

    private static final boolean INFORM_ABOUT_EACH_VOCABULARY_SOUND_FILE_CREATION = false;
    private static final int MAXIMUM_ERRORS_TO_SHOW = 25;
    private static final int MAXIMUM_WARNINGS_TO_SHOW = 10;

    /** Die Liste mit den bekannten Vokabularien. */
    private final List<Vocabulary> vocabularies;

    /**
     * Die Liste der Fehlermeldungen zu mehrfach vorkommenden Dateinamen ohne Pfad von MP3-Dateien.
     */
    private List<String> doubleMp3BareFilenamesMessages;

    /** Das Verzeichnis welches den Sound-Dateinamen ohne Pfad solche mit Pfad zuordnet. */
    private Map<String, String> bareFilename2FilenameMap;

    /** Die Liste mit den Meldungen zu fehlenden MP3-Dateinamen ohne Pfad. */
    private List<String> missingMp3BareFilesMessages;

    /** Die Liste mit den Meldungen zu fehlenden MP3-Dateinamen mit Pfad. */
    private List<String> missingMp3FilesMessages;

    /**
     * Konstruktor.
     *
     * @param step
     *            Der Schritt der durchgeführt wird.
     * @param options
     *            Die Programmoptionen.
     * @param splashScreen
     *            Die grafische Oberfläche beim Start in der die Meldungen angezeigt werden.
     * @param watch
     *            Misst die Laufzeit des gesamten Startups.
     * @param vocabularies
     *            Die Liste mit den bekannten Vokabularien.
     */
    public Step03FindMP3AndStoreInVocables(String step, Options options,
            SplashScreenable splashScreen, StopWatch watch, List<Vocabulary> vocabularies) {
        super(step, options, splashScreen, watch);
        this.vocabularies = vocabularies;
    }

    /** Führt den eigentlichen Inhalt des Schritts aus. */
    @Override
    protected void runInternalStep() {
        if (!isErrorsInStep()) determineAllSoundFiles();
        if (!isErrorsInStep()) adjustMp3InAllVocables();
    }

    private void determineAllSoundFiles() {
        appendMessage("Suche die Sound-Dateien zu allen Vokabeln ...");
        doubleMp3BareFilenamesMessages = new ArrayList<>();

        String directory = options.getVocabulariesPath();
        List<String> soundFilenames = FileHelper.findFilesNio2(directory, ".mp3");
        bareFilename2FilenameMap = new HashMap<>();
        for (String soundFilename : soundFilenames) {
            String bareSoundFilename = FileHelper.getBareName(soundFilename);
            if (bareFilename2FilenameMap.containsKey(bareSoundFilename)) {
                doubleMp3BareFilenamesMessages.add(""
                        + "    Die MP3-Datei " + bareSoundFilename + " ist zweimal "
                                + "vorhanden:\n"
                        + "        " + "Datei 1: "
                                + bareFilename2FilenameMap.get(bareSoundFilename) + "\n"
                        + "        " + "Datei 2: " + soundFilename + "\n");
            }
            bareFilename2FilenameMap.put(bareSoundFilename, soundFilename);
        }

        if (!doubleMp3BareFilenamesMessages.isEmpty()) {
            reportDoubleMp3BareFilenames();
        }
    }

    /**
     * Da gibt es tatsächlich Fälle wo das in Ordnung ist, etwa bei shi:
     *     1) 四 (し, shi) = vier, 四 ergibt aber den Klang "yon".
     *     2) 市 (し, shi) = Stadt, 市 ergibt aber den Klang "ichi".
     * Oder bei ka:
     *     1) 火 (か, ka) = Dienstag (Kurzform von 火曜日 (かようび, kayoubi)
     *     2) か als Question Marker
     * Oder bei sh(i)ta:
     *     1) 下 (した, shita) = unten (mit Kanji kommt da ein falscher Klang)
     *     2) した = getan haben (Ta-Form von 「為る」 (「する」, suru))
     *
     * In anderen Fällen können es aber echte Dubletten sein, deren erste Übersetzung sich
     * unterscheidet.
     *
     * Neuerdings sehe ich das als fatalen Fehler und möchte, dass man je eine der Dateien
     * entfernt!
     */
    private void reportDoubleMp3BareFilenames() {
        String description =
                "Achtung, es kommen Dateinamen (ohne Pfad) von MP3-Dateien mehrfach vor:";
        if (options.isInformAboutDoubleMp3AtStartup()) {
            reportErrors(description, doubleMp3BareFilenamesMessages);
        }
        else {
            reportWarnings(description, doubleMp3BareFilenamesMessages);
        }
    }

    private void adjustMp3InAllVocables() {
        appendMessage("Trage die gefundenen Sound-Dateien in allen Vokabeln ein ...");
        missingMp3BareFilesMessages = new ArrayList<>();
        missingMp3FilesMessages = new ArrayList<>();

        for (Vocabulary vocabulary : vocabularies) {
            if (INFORM_ABOUT_EACH_VOCABULARY_SOUND_FILE_CREATION) {
                appendMessage("Ergänze Sound-Files für " + vocabulary.getDescription());
            }
            for (Vocable vocable : vocabulary.getVocables()) {
                searchAndStoreSoundFileWithPath(vocable);
            }
        }

        if (!missingMp3BareFilesMessages.isEmpty()) {
            reportMissingMp3BareFilesAndExit();
        }
        if (!missingMp3FilesMessages.isEmpty()) {
            reportMissingMp3FilesAndExit();
        }
    }

    private void searchAndStoreSoundFileWithPath(Vocable vocable) {
        String bareMp3 = vocable.getBareMp3();

        if (bareMp3.isBlank()) {
            missingMp3BareFilesMessages.add(""
                    + "    Die Sound-Datei ohne Pfad '" + bareMp3 + "' ist leer.\n"
                    + "        " + "vocable = " + vocable + "\n");
        }
        else {
            if (bareFilename2FilenameMap.containsKey(bareMp3)) {
                String filename = bareFilename2FilenameMap.get(bareMp3);
                vocable.setMp3(filename);
            }
            else {
                missingMp3FilesMessages.add(""
                        + "    Die Sound-Datei '" + bareMp3 + "' wurde nicht gefunden.\n"
                        + "        " + "vocable = " + vocable + "\n");
            }
        }
    }

    private void reportMissingMp3BareFilesAndExit() {
        reportErrors("Es gibt Vokabeln ohne eingetragenen Namen der MP3-Datei (ohne Pfad):",
                missingMp3BareFilesMessages);
    }

    private void reportMissingMp3FilesAndExit() {
        int numberOfMissingMp3Files = missingMp3FilesMessages.size();
        String title = "Es gibt " + numberOfMissingMp3Files
                + " Vokabeln zu denen keine MP3-Datei (mit Pfad) gefunden wurde";
        String description = title + ":";

        if (options.isAllowMissingMp3()) {
            if (options.isReportMissingMp3()) {
                reportWarnings(description, missingMp3FilesMessages);
            }
        }
        else {
            StringBuilder builder = new StringBuilder();
            builder.append(title).append(".\n\n");
            int max = Math.min(3, numberOfMissingMp3Files);
            boolean more = max < numberOfMissingMp3Files;
            for (int index = 0; index < max; ++index) {
                if (max > 1) {
                    builder.append((index + 1) + ")");
                }
                builder.append(missingMp3FilesMessages.get(index)).append("\n");
            }
            if (more) {
                builder.append("...\n");
            }
            builder.append("\nSoll trrotzdem gestartet werden?\n"
                    + "Dann wird auch die Option, dass trotz fehlender MP3-Dateien gestartet "
                    + "wird, gesetzt.");
            String message = builder.toString();
            boolean goOn = GuiTools.askUser(title, message);
            if (goOn) {
                options.setAllowMissingMp3(true);
            }
            else {
                /* Weil es nun zu viele Vokabeln sind, spare ich mir das:
                appendMessage("Übersicht der bekannten MP3-Namen:");
                for (String bareFilenameInMap :
                        CollectionsHelper.getSortedMapStringIndices(bareFilename2FilenameMap)) {
                    appendMessage(bareFilenameInMap + " -> "
                            + bareFilename2FilenameMap.get(bareFilenameInMap));
                }
                */
                reportErrors(description, doubleMp3BareFilenamesMessages);
            }
        }
    }

    private void reportErrors(String description, List<String> errorMessages) {
        List<String> fatalErrorLines = new ArrayList<>();
        fatalErrorLines.add(description);
        int numberOfErrors = errorMessages.size();
        boolean moreErrors = numberOfErrors > MAXIMUM_ERRORS_TO_SHOW;
        int numberOfErrorsToShow = Math.min(numberOfErrors, MAXIMUM_ERRORS_TO_SHOW);
        for (int index = 0; index < numberOfErrorsToShow; ++index) {
            if (errorMessages.size() > 1) {
                fatalErrorLines.add((index + 1) + ") ");
            }
            fatalErrorLines.add(errorMessages.get(index));
        }
        if (moreErrors) {
            fatalErrorLines.add("... und "
                    + NumberString.taupu(numberOfErrors - numberOfErrorsToShow)
                    + " weitere Fehler dieser Art.");
        }

        errorsInStep(Text.joinWithLineBreak(fatalErrorLines));
    }

    private void reportWarnings(String description, List<String> warningLines) {
        List<String> warnings = new ArrayList<>();

        warnings.add(description);
        int numberOfWarnings = warningLines.size();
        boolean moreWarnings = numberOfWarnings > MAXIMUM_WARNINGS_TO_SHOW;
        int numberOfWarningsToShow = Math.min(numberOfWarnings, MAXIMUM_WARNINGS_TO_SHOW);
        for (int index = 0; index < numberOfWarningsToShow; ++index) {
            if (warningLines.size() > 1) {
                warnings.add((index + 1) + ") ");
            }
            warnings.add(warningLines.get(index));
        }
        if (moreWarnings) {
            warnings.add("... und "
                    + NumberString.taupu(numberOfWarnings - numberOfWarningsToShow)
                    + " weitere Warnungen dieser Art.");
        }

        String warningsString = Text.joinWithLineBreak(warnings);
        appendMessage(warningsString);
        warningsInStep(warningsString);
    }

}
