package thb.jeanluc.adventure.io;

import thb.jeanluc.adventure.io.text.AsciiMap;
import thb.jeanluc.adventure.io.text.Hud;
import thb.jeanluc.adventure.io.text.MapView;
import thb.jeanluc.adventure.io.text.QuestText;
import thb.jeanluc.adventure.io.text.QuestView;
import thb.jeanluc.adventure.io.text.Renderings;
import thb.jeanluc.adventure.io.text.RoomView;
import thb.jeanluc.adventure.io.text.StyledText;

import java.util.List;

/**
 * Bidirectional channel between the engine and the player. Commands emit
 * semantic, role-styled output via {@link #print(StyledText)} (the primitive)
 * and the {@link #showRoom}/{@link #setHud} views; each frontend renders them.
 */
public interface GameIO {

    /** Blocks until a line of input is available; empty string at end of input. */
    String readLine();

    /** Renders one styled output block. The only method implementors must provide. */
    void print(StyledText text);

    /** Convenience: a plain, unstyled line. */
    default void write(String s) {
        print(StyledText.of(s));
    }

    /** Renders a room. Default flattens via {@link Renderings}; renderers may override. */
    default void showRoom(RoomView room) {
        print(Renderings.roomToStyledText(room));
    }

    /** Updates the status/HUD region. No-op by default; renderers may override. */
    default void setHud(Hud hud) {
        // no-op
    }

    /** Per-turn push of the current map. GUI repaints its panel; console ignores. */
    default void setMap(MapView view) {
        // no-op
    }

    /**
     * One frame of an auto-walk (the {@code go to} command), as the player moves
     * through a room en route. Default is a no-op: text mode travels silently.
     * The GUI overrides this to repaint the map and pace the animation.
     *
     * @param view the map snapshot at this step
     */
    default void travelStep(MapView view) {
        // no animation in text mode
    }

    /** On-demand map (the 'map' command). Default renders ASCII (Unicode frames). */
    default void showMap(MapView view) {
        print(AsciiMap.render(view, true));
    }

    /** Per-turn push of the quest log. GUI repaints its box; console ignores. */
    default void setQuests(QuestView view) {
        // no-op
    }

    /** Per-turn current-room music track; null/blank = silence. Console no-op; GUI plays. */
    default void setMusic(String track) {
        // no audio in text mode
    }

    /** Applies the music volume/level. Console no-op; GUI sets it. */
    default void setMusicLevel(MusicLevel level) {
        // no audio in text mode
    }

    /** On-demand quest log (the 'quests' command). Default renders styled text. */
    default void showQuests(QuestView view) {
        print(QuestText.render(view));
    }

    /**
     * Presents a titled list of options and returns the 0-based index of the
     * player's choice. Default (console) prints a numbered list and reads a
     * number, re-prompting on invalid input. EOF / empty input returns the
     * LAST option (menus place the safe/cancel option last). Frontends with
     * native widgets (e.g. buttons) may override.
     *
     * @param title   heading shown above the options
     * @param options non-empty list of option labels
     * @return 0-based index into {@code options}
     */
    default int choose(String title, List<String> options) {
        StyledText.Builder b = StyledText.builder().heading(title).plain("\n");
        for (int i = 0; i < options.size(); i++) {
            b.plain("  " + (i + 1) + ") " + options.get(i) + "\n");
        }
        print(b.build());
        while (true) {
            String line = readLine();
            if (line == null || line.isBlank()) {
                return options.size() - 1;
            }
            try {
                int n = Integer.parseInt(line.trim());
                if (n >= 1 && n <= options.size()) {
                    return n - 1;
                }
            } catch (NumberFormatException ignored) {
                // fall through to re-prompt
            }
            write("Please enter a number between 1 and " + options.size() + ".");
        }
    }

    /**
     * Tears the frontend down after the main-menu loop ends (the player chose
     * Quit). Console mode has nothing to release and exits naturally when
     * {@code main} returns; the GUI overrides this to close its window and
     * terminate the JVM, which would otherwise stay alive on the Swing EDT.
     */
    default void shutdown() {
        // no-op in text mode
    }
}
