Skip to content
use crate::wasm::grid;
use horrorshow::prelude::*;
use horrorshow::Raw;

unsafe extern "C" {
    fn mount_view(ptr: *const u8, len: usize);
}

/// The Tachyons stylesheet, embedded into the binary at compile time so the
/// injected markup carries its own styles and needs nothing from the host page.
const STYLES: &str = include_str!("../t.css");

/// Flyout-specific chrome that Tachyons can't express cleanly: the fixed
/// launcher/panel positioning, exact icon size, scroll behaviour, the
/// open/closed toggle (driven by the `hecatomb-open` class, added in JS) and the
/// colour theme. All rules are scoped to `.hecatomb-*` so nothing leaks into the
/// host page.
///
/// Colour palette — a warm "Daily Stoic" parchment scheme. The functional
/// element classes (`.hecatomb-rescan`, `.heca-generate`, ...) double as the
/// styling hooks, so the markup carries only layout utilities, not colours.
///
/// | Role                         | Hex       | Used for                                    |
/// |------------------------------|-----------|---------------------------------------------|
/// | Ink (primary text)           | `#241f17` | body text, headings, primary-button labels  |
/// | Muted text                   | `#7a7159` | captions, labels, close glyph, Back button   |
/// | Parchment (panel background) | `#f3ecdc` | the flyout panel surface                    |
/// | Paper (card background)      | `#fbf6ea` | detail card + list rows (`.heca-grid-row`)  |
/// | Field (input background)     | `#fffdf7` | the row-count input + search box            |
/// | Border tan                   | `#d8ccb0` | panel border, input border, outline buttons |
/// | Border soft                  | `#e6dcc6` | card borders                                 |
/// | Amber (accent)               | `#d29a34` | launcher + primary buttons                  |
/// | Amber hover                  | `#e0a83f` | launcher + primary button hover             |
/// | Ink card (badge background)  | `#3a342a` | row-model badge                             |
/// | Parchment text on badge      | `#efe6d2` | row-model badge label                       |
const FLYOUT_CSS: &str = r#"
.hecatomb-flyout {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 2147483647;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  color: #241f17;
}
.hecatomb-launcher {
  width: 48px;
  height: 48px;
  border: 0;
  border-radius: 12px;
  background: #d29a34;
  color: #241f17;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.5px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hecatomb-launcher:hover { background: #e0a83f; }
.hecatomb-panel {
  display: none;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 76px;
  max-height: 70vh;
  overflow: auto;
  background: #f3ecdc;
  border: 1px solid #d8ccb0;
  color: #241f17;
}
.hecatomb-flyout.hecatomb-open .hecatomb-panel {
  display: block;
  animation: hecatomb-pop 0.12s ease-out;
}
.hecatomb-flyout .heca-muted { color: #7a7159; }
.hecatomb-flyout .heca-card {
  background: #fbf6ea;
  border-color: #e6dcc6;
}
.hecatomb-flyout .heca-badge {
  background: #3a342a;
  color: #efe6d2;
}
.hecatomb-flyout .heca-count,
.hecatomb-flyout .heca-search {
  background: #fffdf7;
  border: 1px solid #d8ccb0;
  color: #241f17;
}
.hecatomb-flyout .heca-search {
  font: inherit;
  box-sizing: border-box;
}
.hecatomb-flyout .heca-search::placeholder { color: #a89e84; }
.hecatomb-flyout .heca-grid-row {
  font: inherit;
  background: #fbf6ea;
  border-color: #e6dcc6;
  color: #241f17;
}
.hecatomb-flyout .heca-grid-row:hover { background: #f0e7d3; }
.hecatomb-flyout .hecatomb-rescan,
.hecatomb-flyout .heca-generate {
  background: #d29a34;
  color: #241f17;
  font-weight: 700;
}
.hecatomb-flyout .hecatomb-rescan:hover,
.hecatomb-flyout .heca-generate:hover { background: #e0a83f; }
.hecatomb-flyout .heca-save,
.hecatomb-flyout .heca-load {
  background: transparent;
  border: 1px solid #d8ccb0;
  color: #5c5340;
}
.hecatomb-flyout .heca-save:hover,
.hecatomb-flyout .heca-load:hover { background: #ece2cc; }
.hecatomb-flyout .heca-back {
  font: inherit;
  background: transparent;
  color: #7a7159;
}
.hecatomb-flyout .heca-back:hover { color: #241f17; }
.hecatomb-close {
  width: 1.75rem;
  height: 1.75rem;
  line-height: 1;
  background: transparent;
  color: #7a7159;
}
.hecatomb-close:hover { color: #241f17; }
@keyframes hecatomb-pop {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}
"#;

/// Render the Hecatomb UI into every `.hecatomb` mount point on the page.
///
/// The actual DOM insertion happens on the JS side (see `mount_view` in
/// `template.js`); here we build the markup with horrorshow and hand it over.
pub fn render() {
    let grids = grid::find();
    let markup = build(&grids);
    unsafe {
        mount_view(markup.as_ptr(), markup.len());
    }
}

/// Build the styled markup for the flyout. A small square launcher icon floats
/// at the bottom-right of the page; pressing it opens the panel.
///
/// The panel is a two-level master/detail: the **list view** shows every
/// discovered grid as a searchable, clickable row; selecting one swaps to that
/// grid's **detail view**, which carries the controls to generate synthetic data
/// and save/load a per-table config file. Both views are emitted here on every
/// render; which one is visible (and the search text) is JS state re-applied
/// after each mount — see `applyViewState`/`applySearch` in `template.js`. The
/// launcher is fixed-position, so wherever the `.hecatomb` mount point sits in
/// the host DOM is irrelevant.
fn build(grids: &[grid::Grid]) -> String {
    (html! {
        // Inlined so the injected content can be styled with Tachyons.
        style : Raw(STYLES);
        style : Raw(FLYOUT_CSS);
        div(class="hecatomb-flyout sans-serif") {
            // Collapsible panel, hidden until `.hecatomb-open` is toggled on.
            div(class="hecatomb-panel br3 shadow-4") {
                div(class="pa3") {
                    div(class="flex items-center justify-between mb2") {
                        h4(class="f4 mv0") : "Hecatomb";
                        div(class="flex items-center") {
                            button(class="hecatomb-rescan f6 bn br2 ph3 pv2 pointer grow mr2") : "Rescan";
                            button(
                                class="hecatomb-toggle hecatomb-close f4 bn br2 pointer grow",
                                title="Close"
                            ) : Raw("×");
                        }
                    }
                    // List view: a searchable, clickable index of grids. JS hides
                    // it while a detail view is open.
                    div(class="heca-list") {
                        @ if grids.is_empty() {
                            p(class="i heca-muted mt0") : "No AG Grid tables found on this page.";
                        } else {
                            input(
                                class="heca-search w-100 pa2 br2 mb2",
                                type="search",
                                placeholder="Search tables\u{2026}",
                                autocomplete="off"
                            );
                            p(class="f6 heca-muted mt0 mb2") {
                                : format_args!("{} AG Grid table(s) found", grids.len());
                            }
                            ul(class="list pl0 mv0") {
                                @ for (index, grid) in grids.iter().enumerate() {
                                    li(class="mb2") {
                                        button(
                                            class="heca-grid-row w-100 tl pa2 ba br2 pointer flex items-center justify-between",
                                            data-index=index
                                        ) {
                                            span(class="flex items-center") {
                                                span(class="b") : grid.label.as_str();
                                                span(class="f7 ml2 ph2 pv1 br2 heca-badge") : grid.row_model.as_str();
                                            }
                                            span(class="heca-chevron f3 heca-muted ml2") : Raw("›");
                                        }
                                    }
                                }
                            }
                            p(class="heca-empty i heca-muted f6 mt2", style="display:none") {
                                : "No tables match your search.";
                            }
                        }
                    }
                    // One detail view per grid, all hidden until JS reveals the
                    // selected one. Holds the generate / save / load actions.
                    @ for (index, grid) in grids.iter().enumerate() {
                        div(class="heca-detail", data-index=index, style="display:none") {
                            button(class="heca-back f6 bn br2 ph2 pv1 pointer mb3") : Raw("‹ Back to list");
                            div(class="flex items-center justify-between mb1") {
                                span(class="b f5") : grid.label.as_str();
                                span(class="f7 ml2 ph2 pv1 br2 heca-badge") : grid.row_model.as_str();
                            }
                            div(class="mt3 pa3 ba br2 heca-card") {
                                div(class="flex items-center flex-wrap") {
                                    label(class="f6 heca-muted mr2") : "rows";
                                    input(
                                        class="heca-count w3 mr2 pa1 br2",
                                        type="number",
                                        min="1",
                                        value=grid.default_count,
                                        data-index=index
                                    );
                                    button(
                                        class="heca-generate f6 bn br2 ph3 pv2 mr2 pointer grow",
                                        data-index=index
                                    ) : "Generate";
                                    button(
                                        class="heca-save f6 br2 ph3 pv2 mr2 pointer grow",
                                        data-index=index
                                    ) : "Save config";
                                    button(
                                        class="heca-load f6 br2 ph3 pv2 pointer grow",
                                        data-index=index
                                    ) : "Load config";
                                }
                            }
                        }
                    }
                }
            }
            // Always-visible square launcher; toggles the panel open/closed.
            button(class="hecatomb-toggle hecatomb-launcher shadow-3", title="Hecatomb") : "HB";
        }
    })
    .into_string()
    .unwrap_or_default()
}