#pragma once

#include <functional>
#include <ostream>
#include <sstream>
#include <string>

namespace fgc {

enum class LogLevel { Trace = 0, Debug, Info, Warn, Error, Off };

// Wire-tracing categories. Orthogonal to LogLevel: a category emits verbatim
// TX/RX lines and is toggled independently of the linear level, so you can
// watch e.g. all serial traffic without turning everything else to Trace.
// Values are a bitmask (multiple categories enabled at once).
enum class LogCat : unsigned {
    None    = 0,
    Serial  = 1u << 0,
    Mqtt    = 1u << 1,
    Camera  = 1u << 2,
    Control = 1u << 3,
    All     = Serial | Mqtt | Camera | Control,
};

// Minimal leveled, thread-safe logger. Each log line is assembled in a
// per-statement buffer and written atomically under a shared mutex, so lines
// from different threads never interleave. Level filtering is global.
//
// Usage:   LOG_INFO << "starting, rate=" << rate;
// Lines at Warn/Error go to stderr; everything else to stdout.
class Logger {
public:
    static void     setLevel(LogLevel level);
    static LogLevel level();
    static bool     enabled(LogLevel level);

    // Parse "trace"|"debug"|"info"|"warn"|"error"|"off" (case-insensitive).
    // Returns false (and leaves the level unchanged) on an unknown string.
    static bool setLevelFromString(const std::string& s);

    // Wire-trace categories (independent of the linear level above).
    static void     setCategories(unsigned mask);
    static unsigned categories();
    static void     enableCategory(LogCat cat);
    static void     disableCategory(LogCat cat);
    static bool     categoryEnabled(LogCat cat);

    // Parse a comma list: "serial,mqtt" | "all" | "none" (case-insensitive,
    // whitespace-trimmed). Recognized tokens always apply; an unknown token
    // sets *ok=false (when ok != nullptr) but does not discard the rest.
    static unsigned parseCategories(const std::string& csv, bool* ok = nullptr);

    // Map a single category name to its enum value. Unknown => LogCat::None and
    // *ok=false (when ok != nullptr).
    static LogCat catFromString(const std::string& name, bool* ok = nullptr);

    // Output sink for finished log lines. The string is the fully formatted line
    // (timestamp + "[LABEL]" + message, no trailing newline); `level` lets a sink
    // colour or route by severity. Installing a sink REPLACES the default
    // stdout/stderr writer entirely - used by the TUI to divert logs into an
    // on-screen pane so they never corrupt the rendered screen. Pass an empty
    // function to restore the default writer. Thread-safe.
    using LogSink = std::function<void(LogLevel level, const std::string& line)>;
    static void setSink(LogSink sink);
};

// RAII helper that buffers one log line and flushes it on commit().
// Designed to be driven by the LOG_* macros' for-loop guard.
class LogStream {
public:
    explicit LogStream(LogLevel level);
    LogStream(LogLevel level, LogCat cat);   // category (wire-trace) line
    bool          pending() const { return enabled_ && !done_; }
    void          commit();
    std::ostream& stream() { return buffer_; }

private:
    LogLevel           level_;
    LogCat             cat_ = LogCat::None;  // None => gated by level only
    bool               enabled_;
    bool               done_ = false;
    std::ostringstream buffer_;
};

}  // namespace fgc

// Brace-safe: the whole macro is a single for-statement, so it composes
// correctly inside if/else without dangling-else hazards, and the message
// expression is never evaluated when the level is disabled.
#define FGC_LOG_AT(level)                                       \
    for (::fgc::LogStream _fgc_ls(level); _fgc_ls.pending(); _fgc_ls.commit()) \
    _fgc_ls.stream()

#define LOG_TRACE FGC_LOG_AT(::fgc::LogLevel::Trace)
#define LOG_DEBUG FGC_LOG_AT(::fgc::LogLevel::Debug)
#define LOG_INFO  FGC_LOG_AT(::fgc::LogLevel::Info)
#define LOG_WARN  FGC_LOG_AT(::fgc::LogLevel::Warn)
#define LOG_ERROR FGC_LOG_AT(::fgc::LogLevel::Error)

// Verbatim wire-trace line for one category. Prints when the category is
// enabled (independent of the linear level, except level Off silences all).
// The message expression is not evaluated when the category is off.
#define LOG_TRACE_CAT(cat)                                                          \
    for (::fgc::LogStream _fgc_ls(::fgc::LogLevel::Trace, (cat)); _fgc_ls.pending(); \
         _fgc_ls.commit())                                                          \
    _fgc_ls.stream()
