// Copyright (c) 2026 C. Klukas. All rights reserved.
// SPDX-License-Identifier: MIT
#include "cvision/widgets/message_box.hpp"
#include "graphics_app.hpp"

#include "../example_about.hpp"

#include <cstdint>
#include <memory>
#include <vector>

#include "cvision/widgets/canvas.hpp"
#include "cvision/widgets/image_view.hpp"
#include "cvision/widgets/label.hpp"
#include "cvision/widgets/menu.hpp"
#include "cvision/widgets/status_line.hpp"
#include "cvision/widgets/static_text.hpp"
#include "cvision/widgets/tab_control.hpp"
#include "cvision/widgets/window.hpp"

namespace ckv::graphics {

GraphicsApp::GraphicsApp(ui::Application& app) : app_(app), roles_(ui::intern_standard_roles(app.roles())) {
    app_.theme() = ui::make_classic_theme(app_.roles(), roles_);

    auto desktop = std::make_unique<widgets::Desktop>(app_.root().bounds());
    desktop_ = desktop.get();
    app_.root().add_child(std::move(desktop));

    build_chrome();
    build_window();

    app_.commands().set_handler(app_.commands().standard().quit, [this] { app_.request_quit(); });

    // F1 answers with something. Silence is the one response a reader
    // cannot tell apart from a key that never arrived.
    widgets::install_about_help(app_, *desktop_, roles_,
                                "ckVision Graphics example",
                                ckv::examples::about_text(
                                    "ImageView and Canvas, with the fallback a terminal without graphics gets."));
}

void GraphicsApp::build_chrome() {
    widgets::MenuBarItem file_menu{"&File", {}};
    file_menu.items.push_back(widgets::MenuItem::command(widgets::CommandPresentation{
        app_.commands().standard().help, "&About..."}));
    file_menu.items.push_back(widgets::MenuItem::separator());
    file_menu.items.push_back(widgets::MenuItem::command(widgets::CommandPresentation{app_.commands().standard().quit}));
    desktop_->dock_top(std::make_unique<widgets::MenuBar>(std::vector<widgets::MenuBarItem>{std::move(file_menu)}));

    auto status = std::make_unique<widgets::StatusLine>();
    status->set_items({widgets::StatusLineItem{widgets::CommandPresentation{app_.commands().standard().menu}},
                       widgets::StatusLineItem{widgets::CommandPresentation{app_.commands().standard().quit}}});
    desktop_->dock_bottom(std::move(status));
}

void GraphicsApp::build_window() {
    demo_image_ = make_demo_image();

    auto window = std::make_unique<widgets::Window>("Graphics");
    window->set_bounds(Rect{4, 3, 62, 17});
    auto tabs = std::make_unique<widgets::TabControl>();
    tabs->set_bounds(Rect{0, 0, 60, 15});
    tabs_ = tabs.get();

    auto image_page = std::make_unique<ui::View>();

    auto image_label = std::make_unique<widgets::Label>("ImageView");
    image_label->set_bounds(Rect{1, 1, 16, 1});
    image_page->add_child(std::move(image_label));

    auto image = std::make_unique<widgets::ImageView>();
    image->set_bounds(Rect{1, 2, 54, 10});
    image->set_image(demo_image_);
    // Every event carries the cell and, where the terminal reports pixels,
    // the pixel position; the view maps the latter into the picture.
    image->on_click = [this](const MouseEvent& event) {
        ++image_clicks_;
        last_image_pointer_ = PointerReport{event, image_view_->image_pixel_at(event)};
    };
    image_view_ = image.get();
    image_page->add_child(std::move(image));

    auto canvas_page = std::make_unique<ui::View>();

    auto canvas_label = std::make_unique<widgets::Label>("Canvas");
    canvas_label->set_bounds(Rect{1, 1, 16, 1});
    canvas_page->add_child(std::move(canvas_label));

    auto canvas = std::make_unique<widgets::Canvas>();
    canvas->set_bounds(Rect{1, 2, 54, 10});
    canvas->set_cell_metrics(PixelSize{2, 3});
    canvas->set_draw_callback([](Image& target) {
        for (int y = 0; y < target.height(); ++y) {
            for (int x = 0; x < target.width(); ++x) {
                const bool stripe = ((x / 4) + (y / 4)) % 2 == 0;
                target.set_pixel(x, y,
                                 Image::Rgba{stripe ? std::uint8_t{240} : std::uint8_t{40},
                                             std::uint8_t(x * 255 / (target.width() > 1 ? target.width() - 1 : 1)),
                                             std::uint8_t(y * 255 / (target.height() > 1 ? target.height() - 1 : 1)),
                                             255});
            }
        }
    });
    canvas->on_click = [this](const MouseEvent& event) {
        ++canvas_clicks_;
        last_canvas_pointer_ = PointerReport{event, canvas_->image_pixel_at(event)};
    };
    canvas_ = canvas.get();
    canvas_page->add_child(std::move(canvas));

    auto note = std::make_unique<widgets::StaticText>(
        "Run with Sixel or NoGraphics terminal profiles to see raster output and fallback.");
    note->set_bounds(Rect{1, 13, 56, 1});
    canvas_page->add_child(std::move(note));

    tabs->add_tab("&Image", std::move(image_page));
    tabs->add_tab("&Canvas", std::move(canvas_page));
    window->set_content(std::move(tabs));
    window_ = desktop_->add_window(std::move(window));
}

std::shared_ptr<const Image> GraphicsApp::make_demo_image() const {
    auto image = std::make_shared<Image>(PixelSize{40, 20});
    for (int y = 0; y < image->height(); ++y) {
        for (int x = 0; x < image->width(); ++x) {
            image->set_pixel(x, y,
                             Image::Rgba{std::uint8_t(x * 255 / (image->width() - 1)),
                                         std::uint8_t(180),
                                         std::uint8_t(255 - y * 255 / (image->height() - 1)),
                                         255});
        }
    }
    return image;
}

}  // namespace ckv::graphics
