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.RoomView;
import thb.jeanluc.adventure.io.text.Span;
import thb.jeanluc.adventure.io.text.Style;
import thb.jeanluc.adventure.io.text.StyledText;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * {@link GameIO} for the terminal: ANSI colours, box-drawing room frames
 * (level "B"), and a dim HUD line. Colour and glyph fidelity are configurable
 * and degrade safely (no colours when output is piped; ASCII frames on request).
 */
public class ConsoleIO implements GameIO {

    /** Whether ANSI colour is emitted. */
    public enum ColorMode { ON, OFF, AUTO }

    /** Frame/glyph fidelity tier. */
    public enum GlyphMode { ASCII, UNICODE, GLYPH }

    private static final int FRAME_WIDTH = 44;

    private final BufferedReader in;
    private final PrintStream out;
    private boolean useColor;
    private GlyphMode glyphs;

    public ConsoleIO() {
        this(new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)),
                System.out, ColorMode.AUTO, GlyphMode.UNICODE);
    }

    /** Backward-compatible stream constructor (colour off, unicode frames). */
    public ConsoleIO(BufferedReader in, PrintStream out) {
        this(in, out, ColorMode.OFF, GlyphMode.UNICODE);
    }

    public ConsoleIO(BufferedReader in, PrintStream out, ColorMode colorMode, GlyphMode glyphs) {
        this.in = in;
        this.out = out;
        this.glyphs = glyphs;
        setColorMode(colorMode);
    }

    /** Re-resolves colour emission from the given mode (AUTO checks the console). */
    public void setColorMode(ColorMode mode) {
        this.useColor = switch (mode) {
            case ON -> true;
            case OFF -> false;
            case AUTO -> System.console() != null;
        };
    }

    /** Switches the frame/glyph fidelity tier live. */
    public void setGlyphMode(GlyphMode mode) {
        this.glyphs = mode;
    }

    @Override
    public String readLine() {
        out.print("> ");
        out.flush();
        try {
            // null propagates as true EOF (ends the game loop); "" stays "" (blank input re-prompts).
            return in.readLine();
        } catch (IOException e) {
            // Treat a stream failure as EOF so input ends cleanly rather than spinning.
            return null;
        }
    }

    @Override
    public void print(StyledText text) {
        StringBuilder sb = new StringBuilder();
        for (Span s : text.spans()) {
            sb.append(paint(s.style(), s.text()));
        }
        out.println(sb);
    }

    @Override
    public void showRoom(RoomView room) {
        out.println(paint(Style.HEADING, frameTop(room.name())));
        out.println(room.description().stripTrailing());
        if (!room.items().isEmpty()) {
            out.println(section("Items", room.items(), Style.ITEM));
        }
        if (!room.npcs().isEmpty()) {
            out.println(section("People", room.npcs(), Style.NPC));
        }
        if (!room.exits().isEmpty()) {
            out.println(section("Exits", room.exits(), Style.EXIT));
        } else {
            out.println("  " + paint(Style.DIM, "no obvious exits"));
        }
        out.println(paint(Style.HEADING, frameBottom()));
    }

    @Override
    public void setHud(Hud h) {
        String line = "[ " + h.location() + " · " + h.gold() + " gold · turn " + h.turn()
                + " · light: " + (h.lightOn() ? "on" : "off") + " ]";
        out.println(paint(Style.DIM, line));
    }

    @Override
    public void showMap(MapView view) {
        print(AsciiMap.render(view, glyphs != GlyphMode.ASCII));
    }

    private String section(String label, List<String> xs, Style st) {
        StringBuilder sb = new StringBuilder("  ").append(paint(Style.DIM, label)).append("  ");
        for (int i = 0; i < xs.size(); i++) {
            if (i > 0) {
                sb.append(" · ");
            }
            sb.append(paint(st, xs.get(i)));
        }
        return sb.toString();
    }

    private String frameTop(String title) {
        String tl = glyphs == GlyphMode.ASCII ? "+" : "┌";
        String tr = glyphs == GlyphMode.ASCII ? "+" : "┐";
        String h = glyphs == GlyphMode.ASCII ? "-" : "─";
        StringBuilder sb = new StringBuilder(tl).append(h).append(' ')
                .append(title.toUpperCase()).append(' ');
        while (sb.length() < FRAME_WIDTH - 1) {
            sb.append(h);
        }
        return sb.append(tr).toString();
    }

    private String frameBottom() {
        String bl = glyphs == GlyphMode.ASCII ? "+" : "└";
        String br = glyphs == GlyphMode.ASCII ? "+" : "┘";
        String h = glyphs == GlyphMode.ASCII ? "-" : "─";
        StringBuilder sb = new StringBuilder(bl);
        while (sb.length() < FRAME_WIDTH - 1) {
            sb.append(h);
        }
        return sb.append(br).toString();
    }

    private String paint(Style st, String s) {
        if (!useColor) {
            return s;
        }
        String code = switch (st) {
            case HEADING -> "[1;35m";
            case ITEM -> "[36m";
            case NPC -> "[33m";
            case EXIT -> "[32m";
            case DANGER -> "[31m";
            case DIM -> "[90m";
            case PLAIN -> "";
        };
        return code.isEmpty() ? s : code + s + "[0m";
    }
}
