#include "ViewportPanel.h"
#include "../utils/Logger.h"
#include <imgui.h>
#include <algorithm>
#include <string>
#include <fstream>
#include <sstream>

namespace UsdLayerManager {

// ---------------------------------------------------------------------------
// Construction
// ---------------------------------------------------------------------------
ViewportPanel::ViewportPanel()
{
    EnsureTileCount(1); // start with a single tile
}

ViewportPanel::~ViewportPanel() = default;

// ---------------------------------------------------------------------------
// EnsureTileCount / WireCallbacks
// ---------------------------------------------------------------------------
void ViewportPanel::EnsureTileCount(int count)
{
    // Grow
    while (static_cast<int>(m_tiles.size()) < count) {
        int idx = static_cast<int>(m_tiles.size());
        m_tiles.push_back(std::make_unique<ViewportTile>());
        if (m_stage)           m_tiles.back()->SetStage(m_stage);
        if (m_iconManager)     m_tiles.back()->SetIconManager(m_iconManager);
        if (m_commandHistory)  m_tiles.back()->SetCommandHistory(m_commandHistory);
        m_tiles.back()->SetTimeCodes(m_displayTime, m_editTime);
        m_tiles.back()->SetSelectedPaths(m_selectedSdfPaths, m_selectedPrimPath);
        WireCallbacks(idx);
    }
    // Shrink
    while (static_cast<int>(m_tiles.size()) > count)
        m_tiles.pop_back();

    // Clamp indices
    m_focusedTileIndex = std::min(m_focusedTileIndex,
                                  std::max(0, static_cast<int>(m_tiles.size()) - 1));
    if (m_maximizedTileIndex >= static_cast<int>(m_tiles.size()))
        m_maximizedTileIndex = -1;
}

void ViewportPanel::WireCallbacks(int i)
{
    m_tiles[i]->OnPrimPicked = [this](const std::string& path) {
        m_selectedPrimPath = path;
        m_selectedSdfPaths.clear();
        if (!path.empty())
            m_selectedSdfPaths.push_back(pxr::SdfPath(path));

        BroadcastSelection();
        if (OnPrimPicked) OnPrimPicked(path);
    };

    m_tiles[i]->OnPrimsPickedRect = [this](const std::vector<std::string>& paths) {
        m_selectedSdfPaths.clear();
        for (const auto& p : paths)
            m_selectedSdfPaths.push_back(pxr::SdfPath(p));
        m_selectedPrimPath = m_selectedSdfPaths.empty()
            ? "" : m_selectedSdfPaths.front().GetString();

        BroadcastSelection();
        if (OnPrimsPickedRect) OnPrimsPickedRect(paths);
    };

}

// ---------------------------------------------------------------------------
// BroadcastSelection / UpdateFocusTile
// ---------------------------------------------------------------------------
void ViewportPanel::BroadcastSelection()
{
    for (auto& t : m_tiles)
        t->SetSelectedPaths(m_selectedSdfPaths, m_selectedPrimPath);

    pxr::SdfPath primary = m_selectedSdfPaths.empty()
        ? pxr::SdfPath() : m_selectedSdfPaths.front();
    m_manipulator.SetSelectedPrim(primary);
}

void ViewportPanel::UpdateFocusTile(int idx)
{
    if (idx < 0 || idx >= static_cast<int>(m_tiles.size())) return;
    m_focusedTileIndex = idx;
}

// ---------------------------------------------------------------------------
// Public setup
// ---------------------------------------------------------------------------
void ViewportPanel::SetStage(pxr::UsdStageRefPtr stage)
{
    m_stage = stage;
    m_manipulator.SetStage(stage);
    for (auto& t : m_tiles) t->SetStage(stage);
    m_selectedSdfPaths.clear();
    m_selectedPrimPath.clear();
    BroadcastSelection();
}

void ViewportPanel::FrameScene()
{
    for (auto& t : m_tiles) t->FrameScene();
}

void ViewportPanel::SetCommandHistory(CommandHistory* history)
{
    m_commandHistory = history;
    m_manipulator.SetCommandHistory(history);
    for (auto& t : m_tiles) t->SetCommandHistory(history);
}

void ViewportPanel::SetIconManager(IconManager* icons)
{
    m_iconManager = icons;
    for (auto& t : m_tiles) t->SetIconManager(icons);
}

void ViewportPanel::SetTimeCodes(pxr::UsdTimeCode displayTime,
                                 pxr::UsdTimeCode editTime)
{
    m_displayTime = displayTime;
    m_editTime    = editTime;
    for (auto& t : m_tiles) t->SetTimeCodes(displayTime, editTime);
    m_manipulator.SetTimeCodes(displayTime, editTime);
}

void ViewportPanel::SetSelectedPrimPath(const std::string& path)
{
    m_selectedPrimPath = path;
    m_selectedSdfPaths.clear();
    if (!path.empty())
        m_selectedSdfPaths.push_back(pxr::SdfPath(path));
    BroadcastSelection();
}

void ViewportPanel::SetSelectedPrimPaths(const std::vector<std::string>& paths)
{
    m_selectedSdfPaths.clear();
    m_selectedPrimPath.clear();
    for (const auto& p : paths)
        m_selectedSdfPaths.push_back(pxr::SdfPath(p));
    if (!paths.empty())
        m_selectedPrimPath = paths.front();
    BroadcastSelection();
}

// ---------------------------------------------------------------------------
// Forwarding accessors
// ---------------------------------------------------------------------------
ViewportCamera& ViewportPanel::GetCamera()
{
    return m_tiles[static_cast<size_t>(m_focusedTileIndex)]->GetCamera();
}

UsdSceneRenderer& ViewportPanel::GetRenderer()
{
    return m_tiles[static_cast<size_t>(m_focusedTileIndex)]->GetRenderer();
}

// ---------------------------------------------------------------------------
// SetLayout
// ---------------------------------------------------------------------------
void ViewportPanel::SetLayout(LayoutMode mode)
{
    m_layout             = mode;
    m_maximizedTileIndex = -1;

    switch (mode) {
    case LayoutMode::Single: EnsureTileCount(1); break;
    case LayoutMode::HSplit: EnsureTileCount(2); break;
    case LayoutMode::VSplit: EnsureTileCount(2); break;
    case LayoutMode::Quad:   EnsureTileCount(4); break;
    }
}

// ---------------------------------------------------------------------------
// ComputeTileRects
// ---------------------------------------------------------------------------
std::vector<ViewportPanel::TileRect>
ViewportPanel::ComputeTileRects(ImVec2 origin, ImVec2 total) const
{
    std::vector<TileRect> rects;

    switch (m_layout) {
    case LayoutMode::Single:
        rects.push_back({ origin, total });
        break;

    case LayoutMode::HSplit: {
        float leftW  = total.x * m_splitH;
        float rightW = total.x - leftW;
        rects.push_back({ origin,                           ImVec2(leftW,  total.y) });
        rects.push_back({ ImVec2(origin.x + leftW, origin.y), ImVec2(rightW, total.y) });
        break;
    }

    case LayoutMode::VSplit: {
        float topH    = total.y * m_splitV;
        float bottomH = total.y - topH;
        rects.push_back({ origin,                           ImVec2(total.x, topH)    });
        rects.push_back({ ImVec2(origin.x, origin.y + topH), ImVec2(total.x, bottomH) });
        break;
    }

    case LayoutMode::Quad: {
        float leftW   = total.x * m_splitH;
        float rightW  = total.x - leftW;
        float topH    = total.y * m_splitV;
        float bottomH = total.y - topH;
        rects.push_back({ origin,                                ImVec2(leftW,  topH)    });
        rects.push_back({ ImVec2(origin.x + leftW, origin.y),   ImVec2(rightW, topH)    });
        rects.push_back({ ImVec2(origin.x, origin.y + topH),    ImVec2(leftW,  bottomH) });
        rects.push_back({ ImVec2(origin.x + leftW, origin.y + topH), ImVec2(rightW, bottomH) });
        break;
    }
    }

    // In multi-tile layouts inset every tile by 2px on all sides.
    // Adjacent tiles then have a 4px gap (2px inset from each side) so both
    // the focused border (2px) and the hovered border (1px) are fully visible.
    if (m_layout != LayoutMode::Single) {
        for (auto& r : rects) {
            // r.pos.x  += 2.f;
            // r.pos.y  += 2.f;
            // r.size.x -= 4.f;
            // r.size.y -= 4.f;
            r.pos.x  += 2.f;
            r.pos.y  += 2.f;
            r.size.x -= 2.f;
            r.size.y -= 2.f;
        }
    }

    return rects;
}


// ---------------------------------------------------------------------------
bool ViewportPanel::IsMouseOverDivider(ImVec2 origin, ImVec2 total) const
{
    if (m_layout == LayoutMode::Single)    return false;
    if (m_maximizedTileIndex >= 0)         return false;
    if (m_draggingDivH || m_draggingDivV)  return true;

    // The hit-zones below are raw screen rects; without this gate a floating
    // window stacked over the viewport (e.g. the Material Editor) wouldn't
    // mask them and the divider would still react underneath it.
    if (!ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows))
        return false;

    const float kDivHalf = 3.0f;
    ImVec2 mouse = ImGui::GetMousePos();

    if (m_layout == LayoutMode::HSplit || m_layout == LayoutMode::Quad) {
        float divX = origin.x + total.x * m_splitH;
        if (mouse.x >= divX - kDivHalf && mouse.x <= divX + kDivHalf &&
            mouse.y >= origin.y         && mouse.y <= origin.y + total.y)
            return true;
    }
    if (m_layout == LayoutMode::VSplit || m_layout == LayoutMode::Quad) {
        float divY = origin.y + total.y * m_splitV;
        if (mouse.y >= divY - kDivHalf && mouse.y <= divY + kDivHalf &&
            mouse.x >= origin.x         && mouse.x <= origin.x + total.x)
            return true;
    }
    return false;
}

// ---------------------------------------------------------------------------
// DrawDividers
// ---------------------------------------------------------------------------
void ViewportPanel::DrawDividers(ImVec2 origin, ImVec2 total)
{
    const float kDivThick  = 6.0f;
    const float kDivVisual = 2.0f;
    const float kMinFrac   = 0.1f;
    const float kMaxFrac   = 0.9f;

    ImDrawList* dl          = ImGui::GetWindowDrawList();
    ImVec2      mousePos    = ImGui::GetMousePos();
    // Use IsMouseClicked (edge-triggered) instead of IsMouseDown so that a
    // divider drag only starts on a fresh press.  If LMB is already held
    // (e.g. the user is mid-rect-select in a tile) the divider is never
    // accidentally triggered when the mouse drifts over the hit-zone.
    bool        lmbClicked  = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
    bool        lmbReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
    // Raw screen-rect hit-testing below — require this window (or its tiles)
    // to actually be the hovered window, so a floating window stacked on top
    // (e.g. the Material Editor) masks the dividers. In-progress drags are
    // exempt: they must keep tracking until LMB release wherever the mouse is.
    bool        windowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows);

    // Vertical divider (HSplit / Quad)
    if (m_layout == LayoutMode::HSplit || m_layout == LayoutMode::Quad) {
        float  divX = origin.x + total.x * m_splitH;
        ImVec2 hMin(divX - kDivThick * 0.5f, origin.y);
        ImVec2 hMax(divX + kDivThick * 0.5f, origin.y + total.y);

        bool hovering = windowHovered && !m_draggingDivV &&
                        mousePos.x >= hMin.x && mousePos.x <= hMax.x &&
                        mousePos.y >= hMin.y && mousePos.y <= hMax.y;

        if ((hovering || m_draggingDivH) && !m_draggingDivV)
            ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
        if (hovering && lmbClicked && !m_draggingDivH && !m_draggingDivV)
            m_draggingDivH = true;
        if (m_draggingDivH) {
            float f = (mousePos.x - origin.x) / total.x;
            m_splitH = std::max(kMinFrac, std::min(kMaxFrac, f));
            if (lmbReleased) m_draggingDivH = false;
        }

        // ImU32 col = (hovering || m_draggingDivH) ? IM_COL32(66,150,250,200) : IM_COL32(80,80,80,180);
        ImU32 col = (hovering || m_draggingDivH) ? IM_COL32(250,150,66,200) : IM_COL32(80,80,80,180);
        dl->AddLine(ImVec2(divX, origin.y), ImVec2(divX, origin.y + total.y), col, kDivVisual);
    }

    // Horizontal divider (VSplit / Quad)
    if (m_layout == LayoutMode::VSplit || m_layout == LayoutMode::Quad) {
        float  divY = origin.y + total.y * m_splitV;
        ImVec2 hMin(origin.x,           divY - kDivThick * 0.5f);
        ImVec2 hMax(origin.x + total.x, divY + kDivThick * 0.5f);

        bool hovering = windowHovered && !m_draggingDivH &&
                        mousePos.x >= hMin.x && mousePos.x <= hMax.x &&
                        mousePos.y >= hMin.y && mousePos.y <= hMax.y;

        if ((hovering || m_draggingDivV) && !m_draggingDivH)
            ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
        if (hovering && lmbClicked && !m_draggingDivH && !m_draggingDivV)
            m_draggingDivV = true;
        if (m_draggingDivV) {
            float f = (mousePos.y - origin.y) / total.y;
            m_splitV = std::max(kMinFrac, std::min(kMaxFrac, f));
            if (lmbReleased) m_draggingDivV = false;
        }

        // ImU32 col = (hovering || m_draggingDivV) ? IM_COL32(66,150,250,200) : IM_COL32(80,80,80,180);
        ImU32 col = (hovering || m_draggingDivV) ? IM_COL32(250,150,66,200) : IM_COL32(80,80,80,180);
        dl->AddLine(ImVec2(origin.x, divY), ImVec2(origin.x + total.x, divY), col, kDivVisual);
    }
}

// ---------------------------------------------------------------------------
// HandleMaximizeInput
// ---------------------------------------------------------------------------
void ViewportPanel::HandleMaximizeInput(int hoveredTileIndex)
{
    ImGuiIO& io = ImGui::GetIO();
    if (io.WantTextInput) return;

    if (ImGui::IsKeyPressed(ImGuiKey_Space)) {
        if (m_maximizedTileIndex >= 0) {
            m_maximizedTileIndex = -1;
            m_layout  = m_layoutBeforeMaximize;
            m_splitH  = m_splitHBefore;
            m_splitV  = m_splitVBefore;
            switch (m_layout) {
            case LayoutMode::Single: EnsureTileCount(1); break;
            case LayoutMode::HSplit: EnsureTileCount(2); break;
            case LayoutMode::VSplit: EnsureTileCount(2); break;
            case LayoutMode::Quad:   EnsureTileCount(4); break;
            }
        } else if (m_layout != LayoutMode::Single && hoveredTileIndex >= 0) {
            m_layoutBeforeMaximize = m_layout;
            m_splitHBefore         = m_splitH;
            m_splitVBefore         = m_splitV;
            m_maximizedTileIndex   = hoveredTileIndex;
            UpdateFocusTile(hoveredTileIndex);
        }
    }

    if (m_maximizedTileIndex >= 0 && ImGui::IsKeyPressed(ImGuiKey_Escape)) {
        m_maximizedTileIndex = -1;
        m_layout = m_layoutBeforeMaximize;
        m_splitH = m_splitHBefore;
        m_splitV = m_splitVBefore;
        switch (m_layout) {
        case LayoutMode::Single: EnsureTileCount(1); break;
        case LayoutMode::HSplit: EnsureTileCount(2); break;
        case LayoutMode::VSplit: EnsureTileCount(2); break;
        case LayoutMode::Quad:   EnsureTileCount(4); break;
        }
    }
}

// ---------------------------------------------------------------------------
// RenderGlobalLeftToolbar
// ---------------------------------------------------------------------------
// Draws a single vertical icon-button toolbar on the left edge of the
// viewport content area.  Sections (top to bottom):
//   [1][H][V][4]  — layout mode
//   ─────────────
//   [Q][W][E][R]  — manipulator tool  (global, like Maya)
//   ─────────────
//   [W|O]         — transform space toggle
// ---------------------------------------------------------------------------
void ViewportPanel::RenderGlobalLeftToolbar(ImVec2 contentPos, ImVec2 /*contentSize*/)
{
    const float kBtnSize  = 32.0f;
    const float kIconPad  =  5.0f;
    const float kRounding =  4.0f;
    const float kSpacing  =  3.0f;
    const float kPadX     =  4.0f;  // left padding inside the strip
    const float kPadY     = 10.0f;  // top padding
    const float kSepH     =  1.0f;  // separator line height
    const float kSepGap   =  6.0f;  // space around separator

    const ImVec2 kBtnSz(kBtnSize, kBtnSize);

    ImDrawList* dl    = ImGui::GetWindowDrawList();
    ImVec2      mouse = ImGui::GetMousePos();
    bool        lmbClk = ImGui::IsMouseClicked(ImGuiMouseButton_Left);

    // Current Y cursor
    float x  = contentPos.x + kPadX;
    float y  = contentPos.y + kPadY;

    // Helper: draw one square icon button, return true if clicked.
    // `active` tints the button blue.  Falls back to centred text if no icon.
    auto DrawBtn = [&](const char* id,
                       Icon        iconEnum,
                       const char* fallbackLabel,
                       bool        active,
                       const char* tooltip) -> bool
    {
        ImVec2 bMin(x, y);
        ImVec2 bMax(x + kBtnSize, y + kBtnSize);
        bool   hov     = mouse.x >= bMin.x && mouse.x <= bMax.x &&
                         mouse.y >= bMin.y && mouse.y <= bMax.y;
        bool   clicked = hov && lmbClk;

        ImU32 bg = active  ? IM_COL32( 66, 150, 250, 230) :
                   hov     ? IM_COL32( 70,  70,  70, 220) :
                             IM_COL32( 32,  32,  32, 178);
        dl->AddRectFilled(bMin, bMax, bg, kRounding);
        if (active)
            dl->AddRect(bMin, bMax, IM_COL32(100, 180, 255, 200), kRounding, 0, 1.5f);

        if (m_iconManager) {
            ImTextureID tex = m_iconManager->Get(iconEnum);
            dl->AddImage(ImTextureRef(tex),
                         ImVec2(bMin.x + kIconPad, bMin.y + kIconPad),
                         ImVec2(bMax.x - kIconPad, bMax.y - kIconPad));
        } else {
            ImVec2 ts = ImGui::CalcTextSize(fallbackLabel);
            dl->AddText(
                ImVec2(bMin.x + (kBtnSize - ts.x) * 0.5f,
                       bMin.y + (kBtnSize - ts.y) * 0.5f),
                IM_COL32(255, 255, 255, 255), fallbackLabel);
        }

        if (hov) ImGui::SetTooltip("%s", tooltip);
        y += kBtnSize + kSpacing;
        (void)id;
        return clicked;
    };

    // Helper: thin horizontal separator
    auto DrawSep = [&]() {
        y += kSepGap;
        dl->AddLine(ImVec2(x - 2.f, y), ImVec2(x + kBtnSize + 2.f, y),
                    IM_COL32(80, 80, 80, 160), kSepH);
        y += kSepH + kSepGap;
    };

    // ── Section 1: Layout mode ───────────────────────────────────────────────
    // Use Layout icons if available, otherwise render small Unicode glyphs.
    // We don't currently have dedicated layout icons in IconManager so we use
    // the fallback text path with descriptive single-character labels.

    struct LayoutEntry {
        LayoutMode  mode;
        Icon        icon;
        const char* label;   // fallback text when no icon manager
        const char* tooltip;
    };
    static const LayoutEntry kLayouts[] = {
        { LayoutMode::Single, Icon::LayoutSingle, "1", "Single viewport  [1]" },
        { LayoutMode::HSplit, Icon::LayoutHSplit, "H", "Split left|right [H]" },
        { LayoutMode::VSplit, Icon::LayoutVSplit, "V", "Split top/bottom [V]" },
        { LayoutMode::Quad,   Icon::LayoutQuad,   "4", "4-quadrant grid  [4]" },
    };
    for (const auto& lk : kLayouts) {
        bool active = (m_layout == lk.mode);
        ImVec2 bMin(x, y);
        ImVec2 bMax(x + kBtnSize, y + kBtnSize);
        bool   hov     = mouse.x >= bMin.x && mouse.x <= bMax.x &&
                         mouse.y >= bMin.y && mouse.y <= bMax.y;
        bool   clicked = hov && lmbClk;

        ImU32 bg = active  ? IM_COL32( 66, 150, 250, 230) :
                   hov     ? IM_COL32( 70,  70,  70, 220) :
                             IM_COL32( 32,  32,  32, 178);
        dl->AddRectFilled(bMin, bMax, bg, kRounding);
        if (active)
            dl->AddRect(bMin, bMax, IM_COL32(100, 180, 255, 200), kRounding, 0, 1.5f);

        if (m_iconManager) {
            ImTextureID tex = m_iconManager->Get(lk.icon);
            dl->AddImage(ImTextureRef(tex),
                         ImVec2(bMin.x + kIconPad, bMin.y + kIconPad),
                         ImVec2(bMax.x - kIconPad, bMax.y - kIconPad));
        } else {
            ImVec2 ts = ImGui::CalcTextSize(lk.label);
            dl->AddText(
                ImVec2(bMin.x + (kBtnSize - ts.x) * 0.5f,
                       bMin.y + (kBtnSize - ts.y) * 0.5f),
                IM_COL32(255, 255, 255, 255), lk.label);
        }

        if (hov) ImGui::SetTooltip("%s", lk.tooltip);
        if (clicked) SetLayout(lk.mode);
        y += kBtnSize + kSpacing;
    }

    DrawSep();

    // ── Section 2: Manipulator tool mode (global, Q/W/E/R) ──────────────────
    struct ToolEntry {
        ManipulatorMode mode;
        Icon            icon;
        const char*     label;
        const char*     tooltip;
    };
    static const ToolEntry kTools[] = {
        { ManipulatorMode::Select, Icon::ToolSelect, "Q", "Select  (Q)" },
        { ManipulatorMode::Move,   Icon::ToolMove,   "W", "Move    (W)" },
        { ManipulatorMode::Rotate, Icon::ToolRotate, "E", "Rotate  (E)" },
        { ManipulatorMode::Scale,  Icon::ToolScale,  "R", "Scale   (R)" },
    };
    ManipulatorMode curMode = m_manipulator.GetMode();
    for (const auto& tk : kTools) {
        if (DrawBtn(tk.label, tk.icon, tk.label, curMode == tk.mode, tk.tooltip))
            m_manipulator.SetMode(tk.mode);
    }

    DrawSep();

    // ── Section 3: Transform space toggle ───────────────────────────────────
    bool isWorld = (m_manipulator.GetTransformSpace() == TransformSpace::World);
    if (DrawBtn("WO",
                isWorld ? Icon::WorldSpace : Icon::LocalSpace,
                isWorld ? "W" : "O",
                isWorld,
                isWorld ? "World space (click → Object)" : "Object space (click → World)"))
    {
        m_manipulator.SetTransformSpace(isWorld ? TransformSpace::Object
                                                : TransformSpace::World);
    }
}

// ---------------------------------------------------------------------------
// Render
// ---------------------------------------------------------------------------
void ViewportPanel::Render(bool* p_open)
{
    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
    ImGui::Begin("Viewport", p_open,
                 ImGuiWindowFlags_NoCollapse      |
                 ImGuiWindowFlags_NoScrollbar     |
                 ImGuiWindowFlags_NoScrollWithMouse);

    // Full content area (no top toolbar — layout buttons are now in the left toolbar)
    ImVec2 contentPos  = ImGui::GetCursorScreenPos();
    ImVec2 contentSize = ImGui::GetContentRegionAvail();

    // Reserve kToolbarW pixels on the left for the global toolbar.
    // Tiles occupy the remaining area to the right.
    ImVec2 tilesPos (contentPos.x  + kToolbarW, contentPos.y);
    ImVec2 tilesSize(contentSize.x - kToolbarW, contentSize.y);

    // ── Global keyboard shortcuts (Q/W/E/R — no hover gate, truly global) ────
    {
        ImGuiIO& io = ImGui::GetIO();
        if (!io.WantTextInput) {
            if (ImGui::IsKeyPressed(ImGuiKey_Q)) m_manipulator.SetMode(ManipulatorMode::Select);
            if (ImGui::IsKeyPressed(ImGuiKey_W)) m_manipulator.SetMode(ManipulatorMode::Move);
            if (ImGui::IsKeyPressed(ImGuiKey_E)) m_manipulator.SetMode(ManipulatorMode::Rotate);
            if (ImGui::IsKeyPressed(ImGuiKey_R)) m_manipulator.SetMode(ManipulatorMode::Scale);
        }
    }

    // ── Render tiles ──────────────────────────────────────────────────────────
    int hoveredTileIndex = -1;

    if (m_maximizedTileIndex >= 0 &&
        m_maximizedTileIndex < static_cast<int>(m_tiles.size()))
    {
        // Maximised: tile fills the tile area (not the toolbar strip)
        int i = m_maximizedTileIndex;
        m_tiles[i]->Render(i, tilesPos, tilesSize, /*isFocused=*/true, m_manipulator,
                           /*dividerActive=*/false);
        if (m_tiles[i]->WasClickedThisFrame())  UpdateFocusTile(i);
        if (m_tiles[i]->IsHoveredThisFrame())   hoveredTileIndex = i;

        ImDrawList* dl = ImGui::GetWindowDrawList();
        dl->AddText(
            ImVec2(tilesPos.x + tilesSize.x - 200.f, tilesPos.y + 5.f),
            IM_COL32(255, 200, 0, 160),
            "Maximized  [Space / Esc] restore");
    }
    else
    {
        bool dividerActive = IsMouseOverDivider(tilesPos, tilesSize);

        auto rects = ComputeTileRects(tilesPos, tilesSize);
        for (int i = 0; i < static_cast<int>(m_tiles.size()); ++i) {
            bool focused = (i == m_focusedTileIndex);
            m_tiles[i]->Render(i, rects[i].pos, rects[i].size, focused, m_manipulator,
                               dividerActive);
            if (m_tiles[i]->WasClickedThisFrame()) UpdateFocusTile(i);
            if (m_tiles[i]->IsHoveredThisFrame())  hoveredTileIndex = i;
        }
        if (m_layout != LayoutMode::Single)
            DrawDividers(tilesPos, tilesSize);
    }

    // ── Global left toolbar (layout + Q/W/E/R + space) ────────────────────────
    // Drawn after tiles so it renders on top; uses raw screen-pos hit-testing
    // so it is not inside any tile's BeginChild scope.
    RenderGlobalLeftToolbar(contentPos, contentSize);

    // ── Space / Escape maximize ───────────────────────────────────────────────
    HandleMaximizeInput(hoveredTileIndex);

    ImGui::End();
    ImGui::PopStyleVar(); // outer WindowPadding
}

// ---------------------------------------------------------------------------
// Global preferences
// ---------------------------------------------------------------------------
void ViewportPanel::ApplyDefaultDelegate(const std::string& delegate)
{
    if (delegate.empty()) return;
    for (auto& tile : m_tiles) {
        if (tile->GetRenderer().GetCurrentRendererId().IsEmpty())
            tile->GetRenderer().SetRendererPlugin(pxr::TfToken(delegate));
    }
}

void ViewportPanel::ApplyGlobalColorCorrection(int ccMode,
                                               const std::string& ocioDisplay,
                                               const std::string& ocioView,
                                               const std::string& ocioColorSpace,
                                               const std::string& ocioLook)
{
    for (auto& tile : m_tiles) {
        auto& r = tile->GetRenderer();
        r.SetColorCorrectionMode(static_cast<ColorCorrectionMode>(ccMode));
        r.SetOcioDisplay(ocioDisplay);
        r.SetOcioView(ocioView);
        r.SetOcioColorSpace(ocioColorSpace);
        r.SetOcioLook(ocioLook);
    }
}

// ---------------------------------------------------------------------------
// Settings persistence
// ---------------------------------------------------------------------------
void ViewportPanel::SaveSettings(const std::string& path) const
{
    std::ofstream f(path);
    if (!f) return;

    f << "[Viewport]\n";
    f << "Layout=" << static_cast<int>(m_layout) << "\n";
    f << "SplitH=" << m_splitH << "\n";
    f << "SplitV=" << m_splitV << "\n";

    for (int i = 0; i < static_cast<int>(m_tiles.size()); ++i) {
        ViewportTileSettings s = m_tiles[i]->GetSettings();
        f << "\n[Tile" << i << "]\n";
        f << "RenderDelegate="   << s.renderDelegate     << "\n";
        f << "ShowGrid="         << (s.showGrid        ? 1 : 0) << "\n";
        f << "ShowCameraGuide="  << (s.showCameraGuide ? 1 : 0) << "\n";
        f << "Aov="              << s.aov              << "\n";
        f << "ShowGuides="       << (s.showGuides      ? 1 : 0) << "\n";
        f << "ShowProxy="        << (s.showProxy       ? 1 : 0) << "\n";
        f << "ShowRender="       << (s.showRender      ? 1 : 0) << "\n";
        f << "AAEnabled="        << (s.aaEnabled        ? 1 : 0) << "\n";
        f << "BgColorR="         << s.bgColorR          << "\n";
        f << "BgColorG="         << s.bgColorG          << "\n";
        f << "BgColorB="         << s.bgColorB          << "\n";
        f << "BBoxMode="         << s.bboxMode           << "\n";
        f << "AmbientLightOnly=" << (s.ambientLightOnly ? 1 : 0) << "\n";
        f << "DomeLightEnabled=" << (s.domeLightEnabled ? 1 : 0) << "\n";
        f << "ShadingMode="      << s.shadingMode        << "\n";
        f << "CullStyle="        << s.cullStyle          << "\n";
    }
}

void ViewportPanel::LoadSettings(const std::string& path)
{
    std::ifstream f(path);
    if (!f) return;

    int   layoutMode = static_cast<int>(m_layout);
    float splitH     = m_splitH;
    float splitV     = m_splitV;
    std::vector<ViewportTileSettings> tileSettings; // indexed by tile number

    int         section    = -1; // -1=Viewport, >=0 = tile index
    std::string line;
    while (std::getline(f, line)) {
        // Strip leading whitespace and CR
        while (!line.empty() && (line.front() == ' ' || line.front() == '\t'))
            line.erase(line.begin());
        if (!line.empty() && line.back() == '\r')
            line.pop_back();
        if (line.empty() || line.front() == '#') continue;

        if (line.front() == '[') {
            size_t close = line.find(']');
            if (close == std::string::npos) continue;
            std::string name = line.substr(1, close - 1);
            if (name == "Viewport") {
                section = -1;
            } else if (name.size() > 4 && name.substr(0, 4) == "Tile") {
                try {
                    int idx = std::stoi(name.substr(4));
                    if (idx >= 0) {
                        section = idx;
                        if (static_cast<int>(tileSettings.size()) <= idx)
                            tileSettings.resize(idx + 1);
                    }
                } catch (...) { section = -1; }
            }
            continue;
        }

        size_t eq = line.find('=');
        if (eq == std::string::npos) continue;
        std::string key = line.substr(0, eq);
        std::string val = line.substr(eq + 1);

        auto toInt   = [](const std::string& v, int   def) { try { return std::stoi(v); } catch (...) { return def; } };
        auto toFloat = [](const std::string& v, float def) { try { return std::stof(v); } catch (...) { return def; } };

        if (section == -1) {
            if      (key == "Layout") layoutMode = toInt(val, layoutMode);
            else if (key == "SplitH") splitH     = toFloat(val, splitH);
            else if (key == "SplitV") splitV     = toFloat(val, splitV);
        } else {
            auto& ts = tileSettings[section];
            if      (key == "RenderDelegate")   ts.renderDelegate   = val;
            else if (key == "ShowGrid")         ts.showGrid         = (val == "1");
            else if (key == "ShowCameraGuide")  ts.showCameraGuide  = (val == "1");
            else if (key == "Aov")              ts.aov              = val;
            else if (key == "ShowGuides")       ts.showGuides       = (val == "1");
            else if (key == "ShowProxy")        ts.showProxy        = (val == "1");
            else if (key == "ShowRender")       ts.showRender       = (val == "1");
            else if (key == "AAEnabled")        ts.aaEnabled        = (val == "1");
            else if (key == "BgColorR")         ts.bgColorR         = toFloat(val, ts.bgColorR);
            else if (key == "BgColorG")         ts.bgColorG         = toFloat(val, ts.bgColorG);
            else if (key == "BgColorB")         ts.bgColorB         = toFloat(val, ts.bgColorB);
            else if (key == "BBoxMode")         ts.bboxMode         = toInt(val, ts.bboxMode);
            else if (key == "AmbientLightOnly") ts.ambientLightOnly = (val == "1");
            else if (key == "DomeLightEnabled") ts.domeLightEnabled = (val == "1");
            else if (key == "ShadingMode")      ts.shadingMode      = toInt(val, ts.shadingMode);
            else if (key == "CullStyle")        ts.cullStyle        = toInt(val, ts.cullStyle);
        }
    }

    // Apply layout (creates/destroys tiles as needed)
    m_splitH = splitH;
    m_splitV = splitV;
    SetLayout(static_cast<LayoutMode>(std::clamp(layoutMode, 0, 3)));

    // Apply per-tile settings to whichever tiles now exist
    for (int i = 0; i < static_cast<int>(tileSettings.size()); ++i) {
        if (i < static_cast<int>(m_tiles.size()))
            m_tiles[i]->ApplySettings(tileSettings[i]);
    }
}

} // namespace UsdLayerManager
