/*
 * Editor.h - top-level editor state and buffer management
 *
 * Editor is the top-level coordinator in kte. It manages:
 *
 * - Buffer collection: Multiple open documents (buffers_), current buffer selection
 * - UI state: Dimensions, status messages, prompts, search state
 * - Kill ring: Shared clipboard for cut/copy/paste operations across buffers
 * - Universal argument: Repeat count mechanism (C-u)
 * - Mode flags: Editor modes (normal, k-command, search, prompt, etc.)
 * - Swap/crash recovery: SwapManager integration for journaling
 * - File operations: Opening files, managing pending opens, recovery prompts
 *
 * Key responsibilities:
 *
 * 1. Buffer lifecycle:
 *    - AddBuffer(): Add new buffers to the collection
 *    - OpenFile(): Load files into buffers
 *    - SwitchTo(): Change active buffer
 *    - CloseBuffer(): Remove buffers with dirty checks
 *
 * 2. UI coordination:
 *    - SetDimensions(): Terminal/window size for viewport calculations
 *    - SetStatus(): Status line messages with timestamps
 *    - Prompt system: Multi-step prompts for file open, buffer switch, etc.
 *    - Search state: Active search, query, match position, origin tracking
 *
 * 3. Shared editor state:
 *    - Kill ring: Circular buffer of killed text (max 60 entries)
 *    - Universal argument: C-u digit collection for command repetition
 *    - Mode tracking: Current input mode (normal, k-command, ESC, prompt)
 *
 * 4. Integration points:
 *    - Commands operate on Editor and current Buffer
 *    - Frontend (Terminal/GUI) queries Editor for rendering
 *    - SwapManager journals all buffer modifications
 *
 * Design note: Editor owns the buffer collection but doesn't directly edit content.
 * Commands modify buffers through Buffer's API, and Editor coordinates the UI state.
 */
#pragma once
#include <cstddef>
#include <ctime>
#include <deque>
#include <string>
#include <vector>

#include "Buffer.h"
#include "Swap.h"


class Editor {
public:
	Editor();

	// Dimensions (terminal or viewport)
	void SetDimensions(std::size_t rows, std::size_t cols);


	[[nodiscard]] std::size_t Rows() const
	{
		return rows_;
	}


	[[nodiscard]] std::size_t Cols() const
	{
		return cols_;
	}


	[[nodiscard]] std::size_t ContentRows() const
	{
		// Always compute from current rows_ to avoid stale values.
		// Reserve 1 row for status line.
		if (rows_ == 0)
			return 1;
		return std::max<std::size_t>(1, rows_ - 1);
	}


	// Mode and flags (mirroring legacy fields)
	void SetMode(int m)
	{
		mode_ = m;
	}


	[[nodiscard]] int Mode() const
	{
		return mode_;
	}


	void SetKillChain(bool on)
	{
		kill_ = on ? 1 : 0;
	}


	[[nodiscard]] bool KillChain() const
	{
		return kill_ != 0;
	}


	void SetNoKill(bool on)
	{
		no_kill_ = on ? 1 : 0;
	}


	[[nodiscard]] bool NoKill() const
	{
		return no_kill_ != 0;
	}


	// Kill ring API
	void KillRingClear()
	{
		kill_ring_.clear();
	}


	void KillRingPush(const std::string &text)
	{
		if (text.empty())
			return;
		// push to front
		kill_ring_.insert(kill_ring_.begin(), text);
		if (kill_ring_.size() > kill_ring_max_)
			kill_ring_.resize(kill_ring_max_);
	}


	void KillRingAppend(const std::string &text)
	{
		if (text.empty())
			return;
		if (kill_ring_.empty()) {
			KillRingPush(text);
		} else {
			kill_ring_.front() += text;
		}
	}


	void KillRingPrepend(const std::string &text)
	{
		if (text.empty())
			return;
		if (kill_ring_.empty()) {
			KillRingPush(text);
		} else {
			kill_ring_.front() = text + kill_ring_.front();
		}
	}


	[[nodiscard]] std::string KillRingHead() const
	{
		return kill_ring_.empty() ? std::string() : kill_ring_.front();
	}


	void SetDirtyEx(int d)
	{
		dirtyex_ = d;
	}


	[[nodiscard]] int DirtyEx() const
	{
		return dirtyex_;
	}


	void SetUniversalArg(int uarg, int ucount)
	{
		uarg_   = uarg;
		ucount_ = ucount;
	}


	[[nodiscard]] int UArg() const
	{
		return uarg_;
	}


	[[nodiscard]] int UCount() const
	{
		return ucount_;
	}


	// --- Universal argument control (C-u) ---
	// Begin or extend a universal argument (like ke's uarg_start)
	void UArgStart();

	// Add a digit 0..9 to the current universal argument (like ke's uarg_digit)
	void UArgDigit(int d);

	// Clear universal-argument state (like ke's uarg_clear)
	void UArgClear();

	// Consume the current universal argument, returning count >= 1.
	// If no universal argument active, returns 1.
	int UArgGet();

	// Repeatable command flag: input layer can mark the next command as repeatable
	void SetRepeatable(bool on)
	{
		repeatable_ = on;
	}


	[[nodiscard]] bool Repeatable() const
	{
		return repeatable_;
	}


	// Status message storage. Rendering is renderer-dependent; the editor
	// merely stores the current message and its timestamp.
	void SetStatus(const std::string &message);


	[[nodiscard]] const std::string &Status() const
	{
		return msg_;
	}


	// --- Quit/Exit state ---
	void SetQuitRequested(bool on)
	{
		quit_requested_ = on;
	}


	[[nodiscard]] bool QuitRequested() const
	{
		return quit_requested_;
	}


	void SetNewWindowRequested(bool on)
	{
		new_window_requested_ = on;
	}


	[[nodiscard]] bool NewWindowRequested() const
	{
		return new_window_requested_;
	}


	void SetQuitConfirmPending(bool on)
	{
		quit_confirm_pending_ = on;
	}


	[[nodiscard]] bool QuitConfirmPending() const
	{
		return quit_confirm_pending_;
	}


	// --- Buffer close/save confirmation state ---
	void SetCloseConfirmPending(bool on)
	{
		close_confirm_pending_ = on;
	}


	[[nodiscard]] bool CloseConfirmPending() const
	{
		return close_confirm_pending_;
	}


	void SetCloseAfterSave(bool on)
	{
		close_after_save_ = on;
	}


	[[nodiscard]] bool CloseAfterSave() const
	{
		return close_after_save_;
	}


	[[nodiscard]] std::time_t StatusTime() const
	{
		return msgtm_;
	}


	// --- Minimal search state for incremental search (milestone 6) ---
	void SetSearchActive(bool on)
	{
		search_active_ = on;
	}


	[[nodiscard]] bool SearchActive() const
	{
		return search_active_;
	}


	void SetSearchQuery(const std::string &q)
	{
		search_query_ = q;
	}


	[[nodiscard]] const std::string &SearchQuery() const
	{
		return search_query_;
	}


	void SetSearchMatch(std::size_t y, std::size_t x, std::size_t len)
	{
		search_y_   = y;
		search_x_   = x;
		search_len_ = len;
	}


	[[nodiscard]] std::size_t SearchMatchY() const
	{
		return search_y_;
	}


	[[nodiscard]] std::size_t SearchMatchX() const
	{
		return search_x_;
	}


	[[nodiscard]] std::size_t SearchMatchLen() const
	{
		return search_len_;
	}


	// Additional helpers for search session bookkeeping
	void SetSearchOrigin(std::size_t x, std::size_t y, std::size_t rowoffs, std::size_t coloffs)
	{
		search_origin_set_   = true;
		search_orig_x_       = x;
		search_orig_y_       = y;
		search_orig_rowoffs_ = rowoffs;
		search_orig_coloffs_ = coloffs;
	}


	void ClearSearchOrigin()
	{
		search_origin_set_ = false;
		search_orig_x_     = search_orig_y_ = search_orig_rowoffs_ = search_orig_coloffs_ = 0;
	}


	[[nodiscard]] bool SearchOriginSet() const
	{
		return search_origin_set_;
	}


	[[nodiscard]] std::size_t SearchOrigX() const
	{
		return search_orig_x_;
	}


	[[nodiscard]] std::size_t SearchOrigY() const
	{
		return search_orig_y_;
	}


	[[nodiscard]] std::size_t SearchOrigRowoffs() const
	{
		return search_orig_rowoffs_;
	}


	[[nodiscard]] std::size_t SearchOrigColoffs() const
	{
		return search_orig_coloffs_;
	}


	void SetSearchIndex(int i)
	{
		search_index_ = i;
	}


	[[nodiscard]] int SearchIndex() const
	{
		return search_index_;
	}


	// --- Generic Prompt subsystem (for search, open-file, save-as, etc.) ---
	enum class PromptKind {
		None = 0,
		Search,
		RegexSearch,
		RegexReplaceFind, // step 1 of Regex Search & Replace: find pattern
		RegexReplaceWith, // step 2 of Regex Search & Replace: replacement text
		OpenFile,
		SaveAs,
		Confirm,
		BufferSwitch,
		GotoLine,
		Chdir,
		ReplaceFind, // step 1 of Search & Replace: find what
		ReplaceWith, // step 2 of Search & Replace: replace with
		Command // generic command prompt (": ")
	};


	void StartPrompt(PromptKind kind, const std::string &label, const std::string &initial)
	{
		prompt_active_ = true;
		prompt_kind_   = kind;
		prompt_label_  = label;
		prompt_text_   = initial;
	}


	void CancelPrompt()
	{
		prompt_active_ = false;
		prompt_kind_   = PromptKind::None;
		prompt_label_.clear();
		prompt_text_.clear();
	}


	void AcceptPrompt()
	{
		// Editor-level accept only ends prompt; commands act on value.
		prompt_active_ = false;
	}


	void SetPromptText(const std::string &t)
	{
		prompt_text_ = t;
	}


	void AppendPromptText(const std::string &t)
	{
		prompt_text_ += t;
	}


	void BackspacePromptText()
	{
		if (!prompt_text_.empty())
			prompt_text_.pop_back();
	}


	[[nodiscard]] bool PromptActive() const
	{
		return prompt_active_;
	}


	[[nodiscard]] PromptKind CurrentPromptKind() const
	{
		return prompt_kind_;
	}


	// --- Overwrite confirmation (save-as on existing file) ---
	void SetPendingOverwritePath(const std::string &path)
	{
		pending_overwrite_path_ = path;
	}


	void ClearPendingOverwritePath()
	{
		pending_overwrite_path_.clear();
	}


	[[nodiscard]] const std::string &PendingOverwritePath() const
	{
		return pending_overwrite_path_;
	}


	[[nodiscard]] const std::string &PromptLabel() const
	{
		return prompt_label_;
	}


	[[nodiscard]] const std::string &PromptText() const
	{
		return prompt_text_;
	}


	// Buffers
	[[nodiscard]] std::size_t BufferCount() const
	{
		return Buffers().size();
	}


	[[nodiscard]] std::size_t CurrentBufferIndex() const
	{
		return curbuf_;
	}


	// Clamp curbuf_ to valid range. Call when the shared buffer list may
	// have been modified by another editor (e.g., buffer closed in another window).
	void ValidateBufferIndex()
	{
		const auto &bufs = Buffers();
		if (bufs.empty()) {
			curbuf_ = 0;
		} else if (curbuf_ >= bufs.size()) {
			curbuf_ = bufs.size() - 1;
		}
	}


	Buffer *CurrentBuffer();

	const Buffer *CurrentBuffer() const;

	// Compute a display-friendly short name for a buffer path that is the
	// shortest unique suffix among all open buffers. If buffer has no name,
	// returns "[no name]".
	[[nodiscard]] std::string DisplayNameFor(const Buffer &buf) const;

	// Add an existing buffer (copy/move) or open from file path
	std::size_t AddBuffer(const Buffer &buf);

	std::size_t AddBuffer(Buffer &&buf);

	bool OpenFile(const std::string &path, std::string &err);

	// Request that a file be opened. The request is processed by calling
	// ProcessPendingOpens() (typically once per frontend frame).
	void RequestOpenFile(const std::string &path, std::size_t line1 = 0);

	// If no modal prompt is active, process queued open requests.
	// Returns true if a file was opened during this call.
	bool ProcessPendingOpens();

	[[nodiscard]] bool HasPendingOpens() const;

	// Swap recovery confirmation state. When non-None, a `PromptKind::Confirm`
	// prompt is active and the user's answer should be routed to ResolveRecoveryPrompt().
	enum class RecoveryPromptKind {
		None = 0,
		RecoverOrDiscard, // y = recover swap, else discard swap and open clean
		DeleteCorruptSwap // y = delete corrupt swap, else keep it
	};

	[[nodiscard]] RecoveryPromptKind PendingRecoveryPrompt() const;

	bool ResolveRecoveryPrompt(bool yes);

	void CancelRecoveryPrompt();

	// Buffer switching/closing
	bool SwitchTo(std::size_t index);

	bool CloseBuffer(std::size_t index);

	// Reset to initial state
	void Reset();

	// Direct access when needed (try to prefer methods above)
	[[nodiscard]] const std::vector<Buffer> &Buffers() const
	{
		return shared_buffers_ ? *shared_buffers_ : buffers_;
	}


	std::vector<Buffer> &Buffers()
	{
		return shared_buffers_ ? *shared_buffers_ : buffers_;
	}


	// Share another editor's buffer list. When set, this editor operates on
	// the provided vector instead of its own. Pass nullptr to detach.
	void SetSharedBuffers(std::vector<Buffer> *shared)
	{
		shared_buffers_ = shared;
		curbuf_         = 0;
	}


	// Swap manager access (for advanced integrations/tests)
	[[nodiscard]] kte::SwapManager *Swap()
	{
		return swap_.get();
	}


	// --- GUI: Visual File Picker state ---
	void SetFilePickerVisible(bool on)
	{
		file_picker_visible_ = on;
	}


	[[nodiscard]] bool FilePickerVisible() const
	{
		return file_picker_visible_;
	}


	void SetFilePickerDir(const std::string &path)
	{
		file_picker_dir_ = path;
	}


	[[nodiscard]] const std::string &FilePickerDir() const
	{
		return file_picker_dir_;
	}

private:
	struct PendingOpen {
		std::string path;
		std::size_t line1{0}; // 1-based; 0 = none
	};

	std::size_t rows_ = 0, cols_ = 0;
	int mode_         = 0;
	int kill_         = 0; // KILL CHAIN
	int no_kill_      = 0; // don't kill in delete_row
	int dirtyex_      = 0;
	std::string msg_;
	std::time_t msgtm_ = 0;
	int uarg_          = 0, ucount_ = 0; // C-u support
	bool repeatable_   = false; // whether the next command is repeatable

	std::vector<Buffer> buffers_;
	std::vector<Buffer> *shared_buffers_ = nullptr; // if set, use this instead of buffers_
	std::size_t curbuf_                  = 0; // index into buffers_

	// Swap journaling manager (lifetime = editor)
	std::unique_ptr<kte::SwapManager> swap_;

	// Kill ring (Emacs-like)
	std::vector<std::string> kill_ring_;
	std::size_t kill_ring_max_ = 60;

	// Quit state
	bool quit_requested_        = false;
	bool new_window_requested_  = false;
	bool quit_confirm_pending_  = false;
	bool close_confirm_pending_ = false; // awaiting y/N to save-before-close
	bool close_after_save_      = false; // if true, close buffer after successful Save/SaveAs

	// Search state
	bool search_active_ = false;
	std::string search_query_;
	std::size_t search_y_ = 0, search_x_ = 0, search_len_ = 0;
	// Search session bookkeeping
	bool search_origin_set_          = false;
	std::size_t search_orig_x_       = 0, search_orig_y_       = 0;
	std::size_t search_orig_rowoffs_ = 0, search_orig_coloffs_ = 0;
	int search_index_                = -1;

	// Prompt state
	bool prompt_active_     = false;
	PromptKind prompt_kind_ = PromptKind::None;
	std::string prompt_label_;
	std::string prompt_text_;
	std::string pending_overwrite_path_;

	// Deferred open + swap recovery prompt state
	std::deque<PendingOpen> pending_open_;
	RecoveryPromptKind pending_recovery_prompt_ = RecoveryPromptKind::None;
	PendingOpen pending_recovery_open_{};
	std::string pending_recovery_swap_path_;
	std::string pending_recovery_replay_err_;

	// GUI-only state (safe no-op in terminal builds)
	bool file_picker_visible_ = false;
	std::string file_picker_dir_;

	// Temporary state for Search & Replace flow
public:
	void SetReplaceFindTmp(const std::string &s)
	{
		replace_find_tmp_ = s;
	}


	void SetReplaceWithTmp(const std::string &s)
	{
		replace_with_tmp_ = s;
	}


	[[nodiscard]] const std::string &ReplaceFindTmp() const
	{
		return replace_find_tmp_;
	}


	[[nodiscard]] const std::string &ReplaceWithTmp() const
	{
		return replace_with_tmp_;
	}

private:
	std::string replace_find_tmp_;
	std::string replace_with_tmp_;
};