use super::{customization::HtmlCustomization, get_default_css, get_default_js};
use std::{format, string::String};
pub(super) fn generate_template(
role_badge: &str,
project_name: &str,
version: &str,
data_json: &str,
languages: &[&str],
customization: Option<&HtmlCustomization>,
) -> String {
let prism_scripts = if languages.is_empty() {
String::new()
} else {
let mut scripts = String::from(
" <!-- Optional Prism.js for syntax highlighting (loaded async, non-blocking) -->\n\
<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\" \n\
crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\" media=\"print\" onload=\"this.media='all'\">\n\
<script defer src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js\" \n\
crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n",
);
for lang in languages {
let normalized = lang.to_lowercase();
scripts.push_str(&format!(
" <script defer src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-{}.min.js\" \n\
crossorigin=\"anonymous\" referrerpolicy=\"no-referrer\"></script>\n",
normalized
));
}
scripts
};
format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{project_name} Error Documentation</title>
<style>
{css}
</style>
{prism_scripts}
</head>
<body>
<div class="app">
<!-- HEADER -->
<header class="header">
{custom_header}
<div class="header-left">
{logo}
<div class="header-brand">
<span class="brand-name">waddling-errors</span>
<span class="brand-divider">/</span>
<span class="project-name">{project_name}</span>
</div>
{role_badge}
</div>
<div class="header-right">
<span class="version">v{version}</span>
<button id="formatToggle" class="format-toggle" title="Toggle numeric/semantic format">🔢 001</button>
<button id="themeToggle" class="theme-toggle" title="Toggle light/dark theme">🌙</button>
<button id="hamburgerBtn" class="hamburger-btn" title="Toggle sidebar" aria-label="Toggle sidebar">☰</button>
</div>
</header>
<!-- MAIN CONTENT -->
<main class="main">
<!-- Search Section -->
<section class="search-section">
<div class="search-input-row">
<div class="search-box">
<span class="search-icon">🔍</span>
<input
type="text"
id="searchInput"
class="search-input"
placeholder="Search: *.*.*.* or 'authentication' or #hash"
value="*.*.*.*"
aria-label="Search error codes"
autocomplete="off"
>
<button id="searchClear" class="search-clear-btn" title="Clear search" aria-label="Clear search">❌</button>
</div>
<button id="queryBuilderToggle" class="query-builder-toggle" title="Build Query">
⚡ Builder
</button>
</div>
<!-- Active Filter Chips - shows current category/tag filters -->
<div id="activeFilters" class="active-filters" aria-live="polite"></div>
<!-- Autocomplete Dropdown -->
<div id="autocompleteDropdown" class="autocomplete-dropdown" role="listbox"></div>
<!-- Query Builder Dropdown (inline) -->
<div id="queryBuilderDropdown" class="query-builder-dropdown">
<div class="builder-header">
<div class="builder-preview">
<span class="builder-preview-label">Pattern:</span>
<span id="builderPreview" class="builder-preview-value">*.*.*.*</span>
</div>
<button id="builderCloseBtn" class="builder-close-btn" title="Close builder" aria-label="Close query builder">×</button>
</div>
<!-- Mobile tabs for column selection -->
<div class="builder-tabs" id="builderTabs">
<button class="builder-tab active" data-col="severity">Severity</button>
<button class="builder-tab" data-col="component">Component</button>
<button class="builder-tab" data-col="primary">Primary</button>
<button class="builder-tab" data-col="sequence">Sequence</button>
</div>
<div class="builder-columns">
<div class="builder-column active" data-col="severity">
<div class="column-title title-severity"><span>Severity</span><span class="column-current" id="sevCurrent">[*]</span></div>
<div class="column-options" id="sevCol"></div>
</div>
<div class="builder-column" data-col="component">
<div class="column-title title-component"><span>Component</span><span class="column-current" id="compCurrent">[*]</span></div>
<div class="column-options" id="compCol"></div>
</div>
<div class="builder-column" data-col="primary">
<div class="column-title title-primary"><span>Primary</span><span class="column-current" id="primCurrent">[*]</span></div>
<div class="column-options" id="primCol"></div>
</div>
<div class="builder-column" data-col="sequence">
<div class="column-title title-sequence"><span>Sequence</span><span class="column-current" id="seqCurrent">[*]</span></div>
<div class="column-options" id="seqCol"></div>
</div>
</div>
<div class="builder-actions">
<button id="builderClearBtn" class="btn-clear">Clear</button>
<button id="builderSearchBtn" class="btn-search">Search</button>
</div>
</div>
<!-- Severity Filter Bar -->
<div id="severityFilterBar" class="severity-filter-bar" role="group" aria-label="Filter by severity"></div>
<!-- View As Role Switcher (only shown in dev/internal docs) -->
<div id="visibilityRow" class="visibility-row" role="group" aria-label="View as role"></div>
</section>
<!-- Results Section -->
<section class="results-section">
<div class="results-header">
<span id="resultCount" class="result-count" role="status" aria-live="polite">0 errors</span>
</div>
<div id="results" class="results-list" role="region" aria-label="Search results"></div>
</section>
</main>
<!-- SIDEBAR -->
<aside class="sidebar" id="sidebar">
<section class="sidebar-section">
<h2 class="sidebar-title">Overview</h2>
<div class="stats-row" id="statsRow">
<div class="stat-item">
<div class="stat-value" id="statErrors">0</div>
<div class="stat-label">Errors</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statComponents">0</div>
<div class="stat-label">Components</div>
</div>
<div class="stat-item">
<div class="stat-value" id="statDeprecated">0</div>
<div class="stat-label">Deprecated</div>
</div>
</div>
</section>
<section class="sidebar-section">
<h2 class="sidebar-title">Browse</h2>
<div class="browse-tabs" id="browseTabs">
<button class="browse-tab active" data-tab="components">Components</button>
<button class="browse-tab" data-tab="primaries">Primaries</button>
<button class="browse-tab" data-tab="sequences">Sequences</button>
</div>
<div class="browse-list" id="browseList"></div>
</section>
<section class="sidebar-section" id="detailSection" style="display: none;">
<h2 class="sidebar-title">Details</h2>
<div class="detail-panel" id="detailPanel">
<div id="detailContent" class="detail-content"></div>
</div>
</section>
</aside>
</div>
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
{custom_footer}
<script>
// Error database (inline for file:// compatibility)
const DATA = {data_json};
const errorDatabase = DATA.errors || [];
const componentsDatabase = DATA.components || {{}};
const primariesDatabase = DATA.primaries || {{}};
const sequencesDatabase = DATA.sequences || {{}};
const severities = DATA.severities || {{}};
{js}
</script>
</body>
</html>"#,
project_name = project_name,
css = _generate_css(customization),
prism_scripts = prism_scripts,
custom_header = _generate_header(customization),
logo = _generate_logo(customization),
role_badge = role_badge,
version = version,
custom_footer = _generate_footer(customization),
data_json = data_json,
js = _generate_js(customization)
)
}
fn _generate_css(customization: Option<&HtmlCustomization>) -> String {
if let Some(custom) = customization {
if let Some(ref replace) = custom.replace_css {
replace.clone()
} else {
let mut css = get_default_css();
let vars = custom.generate_css_variables();
if !vars.is_empty() {
css.push_str("\n/* Custom Theme Variables */\n");
css.push_str(&vars);
}
if let Some(ref custom_css) = custom.custom_css {
css.push_str("\n/* Custom User Styles */\n");
css.push_str(custom_css);
}
css
}
} else {
get_default_css()
}
}
fn _generate_js(customization: Option<&HtmlCustomization>) -> String {
if let Some(custom) = customization {
if let Some(ref replace) = custom.replace_js {
replace.clone()
} else {
let mut js = get_default_js();
if let Some(ref custom_js) = custom.custom_js {
js.push_str("\n/* Custom User JavaScript */\n");
js.push_str(custom_js);
}
js
}
} else {
get_default_js()
}
}
fn _generate_logo(customization: Option<&HtmlCustomization>) -> String {
if let Some(custom) = customization
&& let Some(ref logo_url) = custom.logo_url
{
let alt = custom.logo_alt.as_deref().unwrap_or("Project Logo");
return format!(
"<img src=\"{}\" alt=\"{}\" style=\"height: 40px; width: auto; vertical-align: middle; margin-right: 10px;\">",
logo_url, alt
);
}
String::new()
}
fn _generate_header(customization: Option<&HtmlCustomization>) -> String {
if let Some(custom) = customization
&& let Some(ref header) = custom.header_html
{
return header.clone();
}
String::new()
}
fn _generate_footer(customization: Option<&HtmlCustomization>) -> String {
if let Some(custom) = customization
&& let Some(ref footer) = custom.footer_html
{
return footer.clone();
}
String::new()
}