#include "LevelManagerDialog.h"
#include <wx/statline.h>
#include <wx/spinctrl.h>
#include <wx/clrpicker.h>
#include <cmath>
#include <algorithm>
#include <vector>

// Column indices
enum { COL_NAME=0, COL_HEIGHT=1, COL_COLOR=2, COL_ID=3 };

LevelManagerDialog::LevelManagerDialog(wxWindow* parent, MC::CanvasState& state)
    : wxDialog(parent, wxID_ANY, "Manage Levels",
               wxDefaultPosition, wxDefaultSize,
               wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
    , m_state(state)
{
    m_state.EnsureDefaultLevel();

    wxBoxSizer* top = new wxBoxSizer(wxVERTICAL);

    top->Add(new wxStaticText(this, wxID_ANY,
        "Check to show a level.  Double-click a row to edit its properties\n"
        "(name, height, and track color).  Single-click sets the active level\n"
        "for new pieces."),
        0, wxALL, 8);

    m_list = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(420, 200),
                            wxLC_REPORT | wxLC_SINGLE_SEL);
    m_list->EnableCheckBoxes(true);
    m_list->InsertColumn(COL_NAME,   "Level Name", wxLIST_FORMAT_LEFT,  200);
    m_list->InsertColumn(COL_HEIGHT, "Height",     wxLIST_FORMAT_RIGHT, 90);
    m_list->InsertColumn(COL_COLOR,  "Track Color",wxLIST_FORMAT_LEFT,  90);
    m_list->InsertColumn(COL_ID,     "ID",         wxLIST_FORMAT_RIGHT, 40);
    top->Add(m_list, 1, wxEXPAND | wxLEFT | wxRIGHT, 8);

    // Buttons row
    wxBoxSizer* btns = new wxBoxSizer(wxHORIZONTAL);
    wxButton* btnAdd = new wxButton(this, wxID_ANY, "Add Level");
    m_btnDel         = new wxButton(this, wxID_ANY, "Remove Level");
    btns->Add(btnAdd,    0, wxRIGHT, 6);
    btns->Add(m_btnDel,  0);
    btns->AddStretchSpacer();
    wxButton* btnClose = new wxButton(this, wxID_OK, "Close");
    btns->Add(btnClose, 0);
    top->Add(btns, 0, wxEXPAND | wxALL, 8);

    SetSizerAndFit(top);
    SetSize(wxSize(500, 360));
    Centre();

    Populate();

    btnAdd ->Bind(wxEVT_BUTTON,                &LevelManagerDialog::OnAdd,        this);
    m_btnDel->Bind(wxEVT_BUTTON,               &LevelManagerDialog::OnRemove,     this);
    btnClose->Bind(wxEVT_BUTTON,               &LevelManagerDialog::OnClose,      this);
    m_list->Bind(wxEVT_LIST_ITEM_CHECKED,      &LevelManagerDialog::OnChecked,    this);
    m_list->Bind(wxEVT_LIST_ITEM_UNCHECKED,    &LevelManagerDialog::OnUnchecked,  this);
    m_list->Bind(wxEVT_LIST_ITEM_SELECTED,     &LevelManagerDialog::OnSelChanged, this);
    m_list->Bind(wxEVT_LIST_ITEM_ACTIVATED,    &LevelManagerDialog::OnProperties, this);
}

void LevelManagerDialog::Populate() {
    m_list->DeleteAllItems();
    for (int i = 0; i < (int)m_state.levels.size(); ++i) {
        auto& l = m_state.levels[i];
        long row = m_list->InsertItem(i, wxString::FromUTF8(l.name));

        // Height in the layout's active units.
        wxString hStr;
        if      (m_state.units == 1) hStr = wxString::Format("%.1f cm", l.heightMm/10.0);
        else if (m_state.units == 2) hStr = wxString::Format("%.2f in", l.heightMm/25.4);
        else                         hStr = wxString::Format("%.0f mm", l.heightMm);
        m_list->SetItem(row, COL_HEIGHT, hStr);

        m_list->SetItem(row, COL_COLOR, l.hasTrackColor ? "custom" : "-");
        m_list->SetItem(row, COL_ID, wxString::Format("%d", l.id));
        m_list->CheckItem(row, l.visible);
        if (l.id == m_state.activeLevelId)
            m_list->SetItemFont(row, wxFont(wxNORMAL_FONT->GetPointSize(),
                wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
    }
    m_btnDel->Enable(m_state.levels.size() > 1);
}

void LevelManagerDialog::OnChecked(wxListEvent& e) {
    long row = e.GetIndex();
    if (row >= 0 && row < (int)m_state.levels.size())
        m_state.levels[row].visible = true;
}

void LevelManagerDialog::OnUnchecked(wxListEvent& e) {
    long row = e.GetIndex();
    if (row >= 0 && row < (int)m_state.levels.size())
        m_state.levels[row].visible = false;
}

void LevelManagerDialog::OnSelChanged(wxListEvent& e) {
    long row = e.GetIndex();
    if (row >= 0 && row < (int)m_state.levels.size())
        m_state.activeLevelId = m_state.levels[row].id;
    m_btnDel->Enable(m_state.levels.size() > 1);
}

void LevelManagerDialog::OnAdd(wxCommandEvent&) {
    wxString name = wxGetTextFromUser(
        "Name for new level:", "Add Level",
        wxString::Format("Level %d", m_state.NextLevelId()),
        this);
    if (name.IsEmpty()) return;

    MC::Level l;
    l.id      = m_state.NextLevelId();
    l.name    = name.ToStdString();
    l.visible = true;
    // Cycle through some distinct colours
    static const uint8_t kCols[][3] = {
        {180,200,220}, {220,180,180}, {180,220,180},
        {220,210,160}, {200,180,220}, {160,220,220},
    };
    int ci = (l.id - 1) % 6;
    l.r = kCols[ci][0]; l.g = kCols[ci][1]; l.b = kCols[ci][2];

    m_state.levels.push_back(l);
    m_state.activeLevelId = l.id;
    Populate();
}

void LevelManagerDialog::OnRemove(wxCommandEvent&) {
    if (m_state.levels.size() <= 1) {
        wxMessageBox("A layout must have at least one level.",
                     "Remove Level", wxOK | wxICON_INFORMATION, this);
        return;
    }
    long row = m_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
    if (row < 0) return;
    int removedId = m_state.levels[row].id;
    wxString lvlName = wxString::FromUTF8(m_state.levels[row].name);

    // Count what will be destroyed so the warning is concrete.
    int trackCount = 0, sceneryCount = 0, helixCount = 0;
    for (const auto& p : m_state.pieces)
        if (p.levelId == removedId && p.helixId == -1) ++trackCount;
    for (const auto& s : m_state.scenery)
        if (s.levelId == removedId) ++sceneryCount;
    for (const auto& h : m_state.helices) {
        const MC::PlacedPiece* bs = nullptr;
        for (const auto& p : m_state.pieces)
            if (p.id == h.bottomStubR1) { bs = &p; break; }
        if (bs && bs->levelId == removedId) ++helixCount;
    }

    wxString msg;
    msg.Printf(
        "Remove level \"%s\"?\n\n"
        "This will PERMANENTLY DELETE everything on this level:\n"
        "    \u2022 %d track piece(s)\n"
        "    \u2022 %d scenery item(s)\n"
        "    \u2022 %d helix/helices\n\n"
        "This cannot be undone from this dialog. Are you sure?",
        lvlName, trackCount, sceneryCount, helixCount);

    wxMessageDialog confirm(this, msg, "Remove Level \u2014 Confirm Deletion",
                            wxYES_NO | wxNO_DEFAULT | wxICON_EXCLAMATION);
    confirm.SetYesNoLabels("Delete Level && Contents", "Cancel");
    if (confirm.ShowModal() != wxID_YES) return;

    // Delete helices whose stubs are on this level (and their stub pieces),
    // then track and scenery on this level.
    std::vector<int> helixIdsToRemove;
    for (const auto& h : m_state.helices) {
        for (const auto& p : m_state.pieces) {
            if (p.id == h.bottomStubR1) {
                if (p.levelId == removedId) helixIdsToRemove.push_back(h.id);
                break;
            }
        }
    }
    auto isDoomedHelix = [&](int hid) {
        for (int x : helixIdsToRemove) if (x == hid) return true;
        return false;
    };
    m_state.pieces.erase(std::remove_if(m_state.pieces.begin(), m_state.pieces.end(),
        [&](const MC::PlacedPiece& p){
            if (p.helixId != -1) return isDoomedHelix(p.helixId);
            return p.levelId == removedId;
        }), m_state.pieces.end());
    m_state.helices.erase(std::remove_if(m_state.helices.begin(), m_state.helices.end(),
        [&](const MC::Helix& h){ return isDoomedHelix(h.id); }), m_state.helices.end());
    m_state.scenery.erase(std::remove_if(m_state.scenery.begin(), m_state.scenery.end(),
        [&](const MC::PlacedScenery& s){ return s.levelId == removedId; }), m_state.scenery.end());

    m_state.levels.erase(m_state.levels.begin() + row);
    if (m_state.activeLevelId == removedId)
        m_state.activeLevelId = m_state.levels[0].id;
    Populate();
}

void LevelManagerDialog::OnProperties(wxCommandEvent&) {
    long row = m_list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
    if (row < 0 || row >= (int)m_state.levels.size()) {
        wxMessageBox("Select a level first.", "Level Properties",
                     wxOK | wxICON_INFORMATION, this);
        return;
    }
    MC::Level& l = m_state.levels[row];

    wxDialog dlg(this, wxID_ANY,
                 wxString::Format("Level Properties: %s",
                                  wxString::FromUTF8(l.name)),
                 wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE);
    wxBoxSizer* top = new wxBoxSizer(wxVERTICAL);

    // ── Name ──────────────────────────────────────────────────────────────
    wxBoxSizer* nRow = new wxBoxSizer(wxHORIZONTAL);
    nRow->Add(new wxStaticText(&dlg, wxID_ANY, "Name:"),
              0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 8);
    wxTextCtrl* txtName = new wxTextCtrl(&dlg, wxID_ANY,
        wxString::FromUTF8(l.name), wxDefaultPosition, wxSize(220, -1));
    nRow->Add(txtName, 1, wxALIGN_CENTER_VERTICAL);
    top->Add(nRow, 0, wxEXPAND | wxALL, 10);

    // ── Height off the ground ────────────────────────────────────────────
    wxString hUnit = (m_state.units == 1) ? "cm"
                   : (m_state.units == 2) ? "in" : "mm";
    double hShown  = (m_state.units == 1) ? l.heightMm/10.0
                   : (m_state.units == 2) ? l.heightMm/25.4 : l.heightMm;
    double hMax    = (m_state.units == 1) ? 1000.0
                   : (m_state.units == 2) ? 400.0 : 10000.0;
    double hInc    = (m_state.units == 2) ? 0.1 : 1.0;

    wxBoxSizer* hRow = new wxBoxSizer(wxHORIZONTAL);
    hRow->Add(new wxStaticText(&dlg, wxID_ANY,
        wxString::Format("Height off the ground (%s):", hUnit)),
        0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 8);
    wxSpinCtrlDouble* spinH = new wxSpinCtrlDouble(&dlg, wxID_ANY, "",
        wxDefaultPosition, wxSize(110,-1), wxSP_ARROW_KEYS,
        0.0, hMax, hShown, hInc);
    spinH->SetDigits(m_state.units == 2 ? 2 : 1);
    hRow->Add(spinH, 0, wxALIGN_CENTER_VERTICAL);
    top->Add(hRow, 0, wxALL, 10);

    // ── Track color ──────────────────────────────────────────────────────
    wxBoxSizer* cRow = new wxBoxSizer(wxHORIZONTAL);
    wxCheckBox* chkColor = new wxCheckBox(&dlg, wxID_ANY, "Color track on this level:");
    chkColor->SetValue(l.hasTrackColor);
    cRow->Add(chkColor, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 8);
    wxColourPickerCtrl* colorPick = new wxColourPickerCtrl(&dlg, wxID_ANY,
        wxColour(l.r, l.g, l.b));
    cRow->Add(colorPick, 0, wxALIGN_CENTER_VERTICAL);
    top->Add(cRow, 0, wxLEFT | wxRIGHT | wxBOTTOM, 10);

    top->Add(new wxStaticLine(&dlg), 0, wxEXPAND | wxALL, 4);
    wxSizer* okc = dlg.CreateButtonSizer(wxOK | wxCANCEL);
    if (okc) top->Add(okc, 0, wxEXPAND | wxALL, 8);

    dlg.SetSizerAndFit(top);
    dlg.Centre();
    if (dlg.ShowModal() != wxID_OK) return;

    // Apply name.
    {
        wxString nm = txtName->GetValue().Trim().Trim(false);
        if (!nm.IsEmpty()) l.name = nm.ToStdString();
    }

    // Apply height (convert back to mm).
    double newH = spinH->GetValue();
    if      (m_state.units == 1) newH *= 10.0;
    else if (m_state.units == 2) newH *= 25.4;
    l.heightMm = newH;

    // Apply track color.
    l.hasTrackColor = chkColor->GetValue();
    wxColour c = colorPick->GetColour();
    l.r = c.Red(); l.g = c.Green(); l.b = c.Blue();

    // Push the new height onto flat track already on this level (graded
    // pieces keep their computed z — they're ramps). Helix stubs are left
    // alone; the helix manages its own stub z.
    for (auto& p : m_state.pieces) {
        if (p.levelId != l.id) continue;
        if (p.helixId != -1) continue;
        if (std::abs(p.gradeDeg) < 0.001) p.z_mm = l.heightMm;
    }
    // Scenery height is read from the level at 3D render time; nothing to
    // write here.

    Populate();
}

void LevelManagerDialog::OnClose(wxCommandEvent&) {
    EndModal(wxID_OK);
}
