/*
 * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#include <imgui.h>

#include <studio/dragdrop.hpp>
#include <studio/imguiutil.hpp>

#include <studio/filetreemodel.hpp>

namespace studio {

void FileExplorer::draw(Context &ctx, ImVec2 const &sz) const noexcept {
	ImGui::BeginChild("ProjectExplorer", sz, true);
	ImGui::SetNextItemOpen(true);
	if (m_treeModel) {
		m_treeModel->draw(ctx.tctx);
	}
	ImGui::EndChild();
}

void FileExplorer::setModel(ox::UPtr<FileTreeModel> &&model, bool const selectRoot) noexcept {
	m_treeModel = std::move(model);
	setSelectedNode(selectRoot ? m_treeModel.get() : nullptr);
}

ox::Error FileExplorer::setSelectedPath(ox::StringViewCR path) noexcept {
	return setSelectedPath(path, *m_treeModel).error;
}

[[nodiscard]]
ox::Optional<ox::String> FileExplorer::selectedPath() const {
	if (m_selected) {
		return ox::Optional<ox::String>{ox::in_place, m_selected->path()};
	}
	return ox::Optional<ox::String>{};
}

void FileExplorer::fileOpened(ox::StringViewCR) const noexcept {}

void FileExplorer::fileDeleted(ox::StringViewCR) const noexcept {}

void FileExplorer::fileMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {}

void FileExplorer::dirMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {}

void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept {
	ig::IDStackItem const idStackItem{path};
	fileContextMenu(path);
}

void FileExplorer::drawDirContextMenu(ox::CStringViewCR path) const noexcept {
	ig::IDStackItem const idStackItem{path};
	dirContextMenu(path);
}

void FileExplorer::fileContextMenu(ox::StringViewCR) const noexcept {}

void FileExplorer::dirContextMenu(ox::StringViewCR) const noexcept {}

ox::Result<bool> FileExplorer::setSelectedPath(
	ox::StringViewCR path, FileTreeModel const &node) noexcept {
	if (path == node.path()) {
		m_selected = &node;
		return {};
	}
	for (auto &c : node.children()) {
		OX_REQUIRE(done, setSelectedPath(path, *c));
		if (done) {
			break;
		}
	}
	return {};
}


FileTreeModel::FileTreeModel(
	FileExplorer &explorer,
	ox::StringParam name,
	ox::FileType const fileType,
	FileTreeModel const *const parent) noexcept:
	m_explorer{explorer},
	m_name{std::move(name)},
	m_fullPath{parent ? sfmt("{}/{}", parent->m_fullPath, m_name) : ox::String{}},
	m_fileType{fileType} {}

void FileTreeModel::draw(turbine::Context &tctx) const noexcept {
	constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
	auto const selected = m_explorer.selected(this) ? ImGuiTreeNodeFlags_Selected : 0;
	if (m_fileType == ox::FileType::Directory) {
		auto const nodeOpen = ImGui::TreeNodeEx(m_name.c_str(), dirFlags | selected);
		if (ImGui::IsItemActivated() || ImGui::IsItemClicked(1)) {
			m_explorer.setSelectedNode(this);
		}
		if (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete)) {
			m_explorer.fileDeleted(m_fullPath);
		}
		ig::IDStackItem const idStackItem{m_name};
		m_explorer.drawDirContextMenu(m_fullPath);
		if (m_explorer.fileDraggable()) {
			std::ignore = ig::dragDropSource([this] {
				ImGui::Text("%s", m_name.c_str());
				return ig::setDragDropPayload("FileRef", FileRef{ox::String{m_fullPath}, true});
			});
		}
		if (ig::DragDropTarget const dragDropTarget; dragDropTarget) {
			auto const [ref, err] = ig::getDragDropPayload<FileRef>("FileRef");
			if (!err) {
				auto const name = substr(
					ref.path, find(ref.path.rbegin(), ref.path.rend(), '/').offset() + 1);
				if (ref.isDir) {
					m_explorer.dirMoved(ref.path, sfmt("{}/{}", m_fullPath, name));
				} else {
					m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name));
				}
			}
		}
		if (nodeOpen) {
			for (auto const &child : m_children) {
				child->draw(tctx);
			}
			ImGui::TreePop();
		}
	} else {
		if (ImGui::TreeNodeEx(m_name.c_str(), ImGuiTreeNodeFlags_Leaf | selected)) {
			if (ImGui::IsItemActivated() || ImGui::IsItemClicked(1)) {
				m_explorer.setSelectedNode(this);
			}
			if ((ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) ||
			    (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter))) {
				m_explorer.fileOpened(m_fullPath);
			}
			if (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Delete)) {
				m_explorer.fileDeleted(m_fullPath);
			}
			m_explorer.drawFileContextMenu(m_fullPath);
			ImGui::TreePop();
			if (m_explorer.fileDraggable()) {
				std::ignore = ig::dragDropSource([this] {
					ImGui::Text("%s", m_name.c_str());
					return ig::setDragDropPayload("FileRef", FileRef{ox::String{m_fullPath}});
				});
			}
		}
	}
}

void FileTreeModel::setChildren(ox::Vector<ox::UPtr<FileTreeModel>> children) noexcept {
	m_children = std::move(children);
}

bool FileTreeModel::isEmptyDir() const noexcept {
	return m_children.empty() && m_fileType == ox::FileType::Directory;
}


ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
		FileExplorer &explorer,
		ox::StringParam name,
		ox::StringViewCR path,
		FileTreeModel *parent,
		std::function<bool(ox::StringViewCR, ox::FileStat const &)> const &filter,
		bool const showEmptyDirs) noexcept {
	auto const &fs = explorer.romFs();
	OX_REQUIRE(stat, fs.stat(path));
	auto out = ox::make_unique<FileTreeModel>(explorer, std::move(name), stat.fileType, parent);
	if (stat.fileType == ox::FileType::Directory) {
		OX_REQUIRE_M(children, fs.ls(path));
		std::sort(children.begin(), children.end());
		ox::Vector<ox::UPtr<FileTreeModel>> outChildren;
		for (auto const &childName : children) {
			if (childName[0] != '.') {
				auto const childPath = ox::sfmt("{}/{}", path, childName);
				OX_REQUIRE(childStat, fs.stat(childPath));
				if (filter(childPath, childStat)) {
					OX_REQUIRE_M(child, buildFileTreeModel(
						explorer,
						childName,
						childPath,
						out.get(),
						filter,
						showEmptyDirs));
					auto const emptyDir = child->isEmptyDir();
					if (!emptyDir || showEmptyDirs) {
						outChildren.emplace_back(std::move(child));
					}
				}
			}
		}
		out->setChildren(std::move(outChildren));
	}
	return out;
}

ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
		FileExplorer &explorer,
		std::function<bool(ox::StringViewCR, ox::FileStat const&)> const &filter,
		bool const showEmptyDirs) noexcept {
	return buildFileTreeModel(explorer, "Project", "/", nullptr, filter, showEmptyDirs);
}

}
