// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
//
// ConsoleModel — the thread-safe line store behind ConsoleTab.
//
// The console is fed from multiple threads: user commands + drained daemon/lite log lines
// arrive on the main (render) thread, but RPC-trace and app-logger lines can be produced on
// background worker threads. Previously a single mutex guarded both the line container AND
// the UI-only interaction state (selection, scroll, auto-scroll counters), and it was held
// across the whole render pass — yet some of that UI state was still touched outside the
// lock in render(), racing the background producers.
//
// ConsoleModel splits the two concerns. Producers on ANY thread call ingest(), which only
// briefly locks a small pending queue. The main thread calls drain() once per frame to move
// pending lines into the visible deque and apply the line cap. The visible deque is then
// only ever read/written on the main thread, so ConsoleTab needs no lock during render and
// its selection/scroll fields become pure main-thread state.

#pragma once

#include "console_channel.h"

#include <cstddef>
#include <cstdint>
#include <deque>
#include <mutex>
#include <string>
#include <vector>

namespace dragonx {
namespace ui {

struct ConsoleModelLine {
    std::string    text;
    ConsoleChannel channel = ConsoleChannel::None;
    // JSON folding: for a line that opens a collapsible block, `foldSpan` is the offset to
    // its matching closing-bracket line (>= 2); 0 for non-openers. `collapsed` is the user's
    // fold toggle (main-thread UI state).
    int            foldSpan  = 0;
    bool           collapsed = false;
};

class ConsoleModel {
public:
    static constexpr std::size_t kDefaultMaxLines = 10000;

    explicit ConsoleModel(std::size_t maxLines = kDefaultMaxLines) : max_lines_(maxLines) {}

    // Append a line from ANY thread. Cheap: locks only the pending queue, does not touch
    // the visible deque. The line becomes visible after the next drain(). `foldSpan` marks a
    // JSON block opener (offset to its matching closer); 0 for ordinary lines.
    void ingest(const std::string& text, ConsoleChannel channel, int foldSpan = 0);

    struct DrainResult {
        std::size_t added  = 0;   // lines appended to the visible deque this drain
        std::size_t popped = 0;   // lines evicted from the front by the line cap
    };

    // Main thread only. Move pending lines into the visible deque, then enforce the cap.
    // Returns how many were added and how many were evicted from the front (so the caller
    // can shift any selection indices that referenced the evicted lines).
    DrainResult drain();

    // Main thread only. Clear the visible deque. Does NOT discard queued pending lines —
    // new output still appears — matching the console's view-only `clear` semantics.
    void clear();

    // Main thread only. Toggle a foldable line's collapsed state (no-op if out of range or
    // not a block opener). Returns the new collapsed state.
    bool toggleCollapsed(std::size_t i);

    // Visible-model access — main thread only (no lock; the deque is main-thread-owned).
    const std::deque<ConsoleModelLine>& lines() const { return lines_; }
    std::size_t size() const { return lines_.size(); }
    bool empty() const { return lines_.empty(); }
    const ConsoleModelLine& operator[](std::size_t i) const { return lines_[i]; }
    const ConsoleModelLine& back() const { return lines_.back(); }

    // Monotonic counter bumped whenever the visible deque changes (drain added/evicted lines, clear,
    // fold toggle). The view memoizes its per-frame filter + text-layout passes against this.
    std::uint64_t revision() const { return revision_; }

private:
    const std::size_t             max_lines_;
    std::deque<ConsoleModelLine>  lines_;      // visible model — main thread only
    std::vector<ConsoleModelLine> pending_;    // guarded by ingest_mutex_
    std::mutex                    ingest_mutex_;
    std::uint64_t                 revision_ = 0;
};

} // namespace ui
} // namespace dragonx
