#include "core/timeline.h"
#include "core/tokens.h"

#include <imgui.h>
#include <imgui_internal.h>

#include <algorithm>
#include <cmath>
#include <cstdio>

namespace fn {

// ---------------------------------------------------------------------------
// Pure interp
// ---------------------------------------------------------------------------

float track_value_at(const Track& track, float t) {
    if (track.keys.empty()) return 0.0f;
    if (track.keys.size() == 1) return track.keys[0].value;

    if (t <= track.keys.front().time) return track.keys.front().value;
    if (t >= track.keys.back().time)  return track.keys.back().value;

    // Encontrar el segmento [k_i, k_{i+1}] con k_i.time <= t < k_{i+1}.time
    for (size_t i = 0; i + 1 < track.keys.size(); i++) {
        const Keyframe& a = track.keys[i];
        const Keyframe& b = track.keys[i + 1];
        if (t >= a.time && t <= b.time) {
            float dt = b.time - a.time;
            float u  = (dt > 1e-6f) ? (t - a.time) / dt : 0.0f;
            float k  = tween::apply(b.ease, u); // ease de la key destino
            return a.value + (b.value - a.value) * k;
        }
    }
    return track.keys.back().value;
}

// ---------------------------------------------------------------------------
// Update
// ---------------------------------------------------------------------------

void timeline_update(TimelineState& s, float dt) {
    if (!s.playing) return;
    s.current_time += dt;
    if (s.current_time >= s.duration) {
        if (s.loop) {
            // Wrap por modulo (puede saltar varios loops si dt es enorme).
            if (s.duration > 1e-6f)
                s.current_time = std::fmod(s.current_time, s.duration);
            else
                s.current_time = 0.0f;
        } else {
            s.current_time = s.duration;
            s.playing = false;
        }
    }
    if (s.current_time < 0.0f) s.current_time = 0.0f;
}

// ---------------------------------------------------------------------------
// Render helpers
// ---------------------------------------------------------------------------

namespace {

constexpr float k_label_w     = 80.0f;   // ancho de la columna de nombre de track
constexpr float k_track_h     = 36.0f;   // alto de cada track
constexpr float k_ruler_h     = 22.0f;   // alto del ruler superior
constexpr float k_header_h    = 28.0f;   // alto de la cabecera (play/pause + tiempo)
constexpr float k_diamond_r   = 6.0f;    // radio del diamante de keyframe

float time_to_x(float time, float duration, float track_x0, float track_w) {
    if (duration <= 1e-6f) return track_x0;
    return track_x0 + (time / duration) * track_w;
}

float x_to_time(float x, float duration, float track_x0, float track_w) {
    if (track_w <= 1e-6f) return 0.0f;
    float t = (x - track_x0) / track_w * duration;
    if (t < 0.0f) t = 0.0f;
    if (t > duration) t = duration;
    return t;
}

void draw_diamond(ImDrawList* dl, ImVec2 c, float r, ImU32 col) {
    ImVec2 pts[4] = {
        ImVec2(c.x,     c.y - r),
        ImVec2(c.x + r, c.y),
        ImVec2(c.x,     c.y + r),
        ImVec2(c.x - r, c.y),
    };
    dl->AddConvexPolyFilled(pts, 4, col);
}

} // namespace

bool timeline_widget(const char* id, TimelineState& s, ImVec2 size) {
    using namespace fn_tokens;

    ImGui::PushID(id);
    bool changed = false;

    // ---- size resolution
    ImVec2 region = ImGui::GetContentRegionAvail();
    if (size.x <= 0.0f) size.x = region.x;
    if (size.y <= 0.0f) size.y = 200.0f;

    ImVec2 origin = ImGui::GetCursorScreenPos();
    ImDrawList* dl = ImGui::GetWindowDrawList();

    // ---- Background
    ImVec2 bg_max = ImVec2(origin.x + size.x, origin.y + size.y);
    dl->AddRectFilled(origin, bg_max, ImGui::GetColorU32(colors::surface), radius::sm);
    dl->AddRect(origin, bg_max, ImGui::GetColorU32(colors::border), radius::sm);

    // ---- Header (play/pause + tiempo)
    ImGui::SetCursorScreenPos(ImVec2(origin.x + spacing::sm, origin.y + spacing::xs));
    const char* play_label = s.playing ? "Pause##tl_play" : "Play##tl_play";
    if (ImGui::Button(play_label, ImVec2(60.0f, k_header_h - spacing::xs))) {
        s.playing = !s.playing;
        changed = true;
    }
    ImGui::SameLine();
    if (ImGui::Button("Reset##tl_reset", ImVec2(60.0f, k_header_h - spacing::xs))) {
        s.current_time = 0.0f;
        changed = true;
    }
    ImGui::SameLine();
    ImGui::SetNextItemWidth(60.0f);
    if (ImGui::DragFloat("##tl_dur", &s.duration, 0.1f, 0.1f, 600.0f, "dur %.2fs")) {
        if (s.duration < 0.1f) s.duration = 0.1f;
        changed = true;
    }
    ImGui::SameLine();
    ImGui::PushStyleColor(ImGuiCol_Text, colors::text_muted);
    ImGui::Text("t=%.3fs", s.current_time);
    ImGui::PopStyleColor();
    ImGui::SameLine();
    ImGui::Checkbox("loop##tl_loop", &s.loop);

    // ---- Layout
    float track_x0 = origin.x + k_label_w;
    float track_w  = size.x - k_label_w - spacing::sm;
    float ruler_y0 = origin.y + k_header_h;
    float ruler_y1 = ruler_y0 + k_ruler_h;

    // ---- Ruler
    dl->AddRectFilled(ImVec2(origin.x, ruler_y0), ImVec2(bg_max.x, ruler_y1),
                      ImGui::GetColorU32(colors::bg));
    dl->AddLine(ImVec2(origin.x, ruler_y1), ImVec2(bg_max.x, ruler_y1),
                ImGui::GetColorU32(colors::border));

    // Tick marks cada 0.5s
    ImU32 tick_col = ImGui::GetColorU32(colors::text_dim);
    int n_ticks = (int)std::floor(s.duration / 0.5f);
    for (int i = 0; i <= n_ticks; i++) {
        float ts = (float)i * 0.5f;
        if (ts > s.duration) break;
        float x = time_to_x(ts, s.duration, track_x0, track_w);
        bool whole = (i % 2 == 0);
        float h = whole ? 8.0f : 4.0f;
        dl->AddLine(ImVec2(x, ruler_y1 - h), ImVec2(x, ruler_y1), tick_col, 1.0f);
        if (whole) {
            char buf[16];
            std::snprintf(buf, sizeof(buf), "%.0fs", ts);
            dl->AddText(ImVec2(x + 2.0f, ruler_y0 + 2.0f), tick_col, buf);
        }
    }

    // Scrub interaction en el ruler
    {
        ImGui::SetCursorScreenPos(ImVec2(track_x0, ruler_y0));
        ImGui::InvisibleButton("##tl_ruler_scrub", ImVec2(track_w, k_ruler_h));
        if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0.0f)) {
            float mx = ImGui::GetIO().MousePos.x;
            s.current_time = x_to_time(mx, s.duration, track_x0, track_w);
            changed = true;
        } else if (ImGui::IsItemClicked()) {
            float mx = ImGui::GetIO().MousePos.x;
            s.current_time = x_to_time(mx, s.duration, track_x0, track_w);
            changed = true;
        }
    }

    // ---- Tracks
    ImU32 col_text       = ImGui::GetColorU32(colors::text);
    ImU32 col_track_bg   = ImGui::GetColorU32(colors::bg);
    ImU32 col_key        = ImGui::GetColorU32(colors::primary);
    ImU32 col_key_hover  = ImGui::GetColorU32(colors::primary_hover);
    ImU32 col_separator  = ImGui::GetColorU32(colors::border);

    float track_y = ruler_y1;
    float available_h = (origin.y + size.y) - ruler_y1;
    int n_tracks = (int)s.tracks.size();
    float row_h = (n_tracks > 0) ? std::min(k_track_h, available_h / (float)n_tracks)
                                 : k_track_h;
    if (row_h < 18.0f) row_h = 18.0f;

    for (int ti = 0; ti < n_tracks; ti++) {
        Track& tr = s.tracks[ti];
        float y0 = track_y;
        float y1 = y0 + row_h;

        // bg
        dl->AddRectFilled(ImVec2(track_x0, y0), ImVec2(bg_max.x, y1), col_track_bg);
        dl->AddLine(ImVec2(origin.x, y1), ImVec2(bg_max.x, y1), col_separator);

        // label
        dl->AddText(ImVec2(origin.x + spacing::sm, y0 + (row_h - ImGui::GetTextLineHeight()) * 0.5f),
                    col_text, tr.name.c_str());

        // keyframes
        for (size_t ki = 0; ki < tr.keys.size(); ki++) {
            Keyframe& k = tr.keys[ki];
            float kx = time_to_x(k.time, s.duration, track_x0, track_w);
            ImVec2 c = ImVec2(kx, y0 + row_h * 0.5f);

            char btn_id[64];
            std::snprintf(btn_id, sizeof(btn_id), "##tl_k_%d_%zu", ti, ki);
            ImGui::SetCursorScreenPos(ImVec2(c.x - k_diamond_r, c.y - k_diamond_r));
            ImGui::InvisibleButton(btn_id, ImVec2(k_diamond_r * 2.0f, k_diamond_r * 2.0f));

            bool hovered = ImGui::IsItemHovered();
            draw_diamond(dl, c, k_diamond_r, hovered ? col_key_hover : col_key);

            if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left, 0.0f)) {
                float mx = ImGui::GetIO().MousePos.x;
                float new_t = x_to_time(mx, s.duration, track_x0, track_w);
                k.time = new_t;
                changed = true;
            }
        }

        // Reordenar keys por tiempo si el usuario solto el drag (consistencia).
        // Lo hacemos en cada frame que hubo cambio: cheap, O(n log n) per track.
        if (changed) {
            std::sort(tr.keys.begin(), tr.keys.end(),
                      [](const Keyframe& a, const Keyframe& b) { return a.time < b.time; });
        }

        track_y = y1;
    }

    // ---- Playhead vertical
    {
        float px = time_to_x(s.current_time, s.duration, track_x0, track_w);
        ImU32 ph_col = ImGui::GetColorU32(colors::primary_light);
        dl->AddLine(ImVec2(px, ruler_y0), ImVec2(px, bg_max.y), ph_col, 1.5f);
        // pequeno triangulo en el ruler
        ImVec2 t0(px - 4.0f, ruler_y0);
        ImVec2 t1(px + 4.0f, ruler_y0);
        ImVec2 t2(px,        ruler_y0 + 6.0f);
        dl->AddTriangleFilled(t0, t1, t2, ph_col);
    }

    // Reservar espacio en el layout vertical
    ImGui::SetCursorScreenPos(ImVec2(origin.x, origin.y + size.y + spacing::xs));
    ImGui::Dummy(ImVec2(size.x, 1.0f));

    ImGui::PopID();
    return changed;
}

} // namespace fn
