use std::path::Path;
use crate::config::Config;
use super::helpers::{classify_shaders, scan_shaders};
pub fn build_shader_context(config: &Config) -> String {
let shaders_dir = Config::shaders_dir();
let available = scan_shaders(&shaders_dir);
let (bg_shaders, cursor_shaders) = classify_shaders(&available);
let mut ctx = String::with_capacity(2048);
ctx.push_str("[Shader Assistant Context]\n");
ctx.push_str(
"Use this block as reference context. Treat [Observation] sections as state,\n\
[Constraint] sections as hard rules, and [Instruction] sections as guidance.\n\n",
);
ctx.push_str("## [Observation] Current Shader State\n");
if let Some(ref name) = config.shader.custom_shader {
ctx.push_str(&format!("- Background shader: `{name}`"));
if config.shader.custom_shader_enabled {
ctx.push_str(" (enabled)\n");
} else {
ctx.push_str(" (disabled)\n");
}
if config.shader.custom_shader_enabled {
ctx.push_str(&format!(
" - animation_speed: {}\n",
config.shader.custom_shader_animation_speed
));
ctx.push_str(&format!(
" - brightness: {}\n",
config.shader.custom_shader_brightness
));
ctx.push_str(&format!(
" - text_opacity: {}\n",
config.shader.custom_shader_text_opacity
));
}
} else {
ctx.push_str("- Background shader: none\n");
}
if let Some(ref name) = config.shader.cursor_shader {
ctx.push_str(&format!("- Cursor shader: `{name}`"));
if config.shader.cursor_shader_enabled {
ctx.push_str(" (enabled)\n");
} else {
ctx.push_str(" (disabled)\n");
}
if config.shader.cursor_shader_enabled {
ctx.push_str(&format!(
" - animation_speed: {}\n",
config.shader.cursor_shader_animation_speed
));
ctx.push_str(&format!(
" - glow_radius: {}\n",
config.shader.cursor_shader_glow_radius
));
ctx.push_str(&format!(
" - glow_intensity: {}\n",
config.shader.cursor_shader_glow_intensity
));
}
} else {
ctx.push_str("- Cursor shader: none\n");
}
ctx.push('\n');
ctx.push_str("## [Observation] Available Shaders\n");
if bg_shaders.is_empty() && cursor_shaders.is_empty() {
ctx.push_str("No shaders found in the shaders directory.\n");
} else {
if !bg_shaders.is_empty() {
ctx.push_str("Background shaders:\n");
for s in &bg_shaders {
ctx.push_str(&format!(" - {s}\n"));
}
}
if !cursor_shaders.is_empty() {
ctx.push_str("Cursor shaders:\n");
for s in &cursor_shaders {
ctx.push_str(&format!(" - {s}\n"));
}
}
}
ctx.push('\n');
ctx.push_str("## [Observation] Debug Files\n");
if let Some(ref name) = config.shader.custom_shader {
let stem = Path::new(name)
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| name.clone());
ctx.push_str(&format!(
"- Transpiled WGSL: `/tmp/par_term_{stem}_shader.wgsl`\n"
));
}
if let Some(ref name) = config.shader.cursor_shader {
let stem = Path::new(name)
.file_stem()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| name.clone());
ctx.push_str(&format!(
"- Cursor transpiled WGSL: `/tmp/par_term_{stem}_shader.wgsl`\n"
));
}
ctx.push_str("- Wrapped GLSL (last shader): `/tmp/par_term_debug_wrapped.glsl`\n");
ctx.push('\n');
ctx.push_str("## [Observation] Available Uniforms\n");
ctx.push_str("Common (all shaders):\n");
ctx.push_str(" - `iTime` (float) - elapsed time in seconds\n");
ctx.push_str(
" - `iResolution` (vec3) - viewport resolution in pixels (xy) and aspect ratio (z)\n",
);
ctx.push_str(
" - `iMouse` (vec4) - mouse position: xy=current, zw=click (Shadertoy-compatible)\n",
);
ctx.push_str(" - `iChannel0`..`iChannel3` (sampler2D) - user texture channels\n");
ctx.push_str(" - `iChannel4` (sampler2D) - terminal content texture (par-term specific)\n");
ctx.push_str(" - `iChannelResolution[0..4]` (vec3) - per-channel texture sizes\n");
ctx.push_str(
" - `iProgress` (vec4) - progress state [state, percent, isActive, activeCount]\n",
);
ctx.push_str("Cursor shader extras:\n");
ctx.push_str(" - `iCurrentCursor` (vec4) - current cursor (xy=top-left px, zw=size px)\n");
ctx.push_str(" - `iPreviousCursor` (vec4) - previous cursor (xy=top-left px, zw=size px)\n");
ctx.push_str(
" - `iCurrentCursorColor` (vec4) - current cursor RGBA (alpha includes blink opacity)\n",
);
ctx.push_str(" - `iPreviousCursorColor` (vec4) - previous cursor RGBA\n");
ctx.push_str(" - `iTimeCursorChange` (float) - time since last cursor move\n");
ctx.push_str(" - `iCursorTrailDuration` (float), `iCursorGlowRadius` (float), `iCursorGlowIntensity` (float)\n");
ctx.push('\n');
ctx.push_str("## [Constraint] GLSL Compatibility Rules\n");
ctx.push_str("- Avoid passing sampler uniforms (e.g. `sampler2D`) as function parameters.\n");
ctx.push_str(" Some GLSL versions / toolchains reject sampler-typed function arguments.\n");
ctx.push_str("- Prefer sampling global uniforms like `iChannel0`..`iChannel4` directly.\n");
ctx.push_str("Safe helper pattern:\n");
ctx.push_str("```glsl\n");
ctx.push_str("vec4 sampleTerminal(vec2 uv) {\n");
ctx.push_str(" return texture(iChannel4, uv);\n");
ctx.push_str("}\n");
ctx.push_str("```\n");
ctx.push_str("- UV/channel sampling rules:\n");
ctx.push_str(" - `texture()` expects normalized UVs in `[0,1]`.\n");
ctx.push_str(" - Start from `uv = fragCoord / iResolution.xy` for screen-space sampling.\n");
ctx.push_str(" - After transforms (rotation/scale/offset), clamp before sampling:\n");
ctx.push_str(" `vec2 suv = clamp(transformedUv, vec2(0.0), vec2(1.0));`\n");
ctx.push_str(" - Do not mix pixel-space and UV-space in one variable.\n");
ctx.push_str(" Convert pixel coords with `/ iResolution.xy` before `texture()`.\n");
ctx.push_str(
" - Avoid arbitrary `+0.5` UV offsets unless intentionally correcting a known sampling artifact.\n",
);
ctx.push_str(
" Random `+0.5` shifts usually move sampling into the wrong coordinate space.\n",
);
ctx.push_str("- Coordinate-space contract:\n");
ctx.push_str(" - `fragCoord` and cursor uniforms are pixel-space values.\n");
ctx.push_str(
" - Texture sampling is UV-space. Convert once with `uv = fragCoord / iResolution.xy`.\n",
);
ctx.push_str(" - Keep pixel and UV vars separate (`cursorPx`, `cursorUv`, `distPx`, etc.).\n");
ctx.push_str(
" - If mixing cursor data with UV math, convert explicitly: `cursorUv = (iCurrentCursor.xy + 0.5 * iCurrentCursor.zw) / iResolution.xy`.\n",
);
ctx.push_str(
" - Avoid implicit/double Y-flips. Use one coordinate convention per calculation path.\n",
);
ctx.push_str("- Optional channel textures:\n");
ctx.push_str(" - Unset iChannel0-3 default to transparent 1x1 placeholders.\n");
ctx.push_str(" - Detect a real configured texture with resolution > 1px, not `> 0.0`.\n");
ctx.push_str(" Example: `bool hasTex0 = iChannelResolution[0].x > 1.0 && iChannelResolution[0].y > 1.0;`\n");
ctx.push('\n');
ctx.push_str("## [Instruction] Minimal Shader Template\n");
ctx.push_str("```glsl\n");
ctx.push_str("void mainImage(out vec4 fragColor, in vec2 fragCoord) {\n");
ctx.push_str(" vec2 uv = fragCoord / iResolution.xy;\n");
ctx.push_str(" vec4 tex = texture(iChannel4, uv);\n");
ctx.push_str(" fragColor = tex;\n");
ctx.push_str("}\n");
ctx.push_str("```\n");
ctx.push('\n');
ctx.push_str("## [Instruction] How to Apply Changes\n");
ctx.push_str(&format!(
"1. Write shader GLSL files to: `{}`\n",
shaders_dir.display()
));
ctx.push_str("2. Use the `config_update` MCP tool to activate the shader:\n");
ctx.push_str(" ```json\n");
ctx.push_str(
" config_update({\"updates\": {\"custom_shader\": \"filename.glsl\", \"custom_shader_enabled\": true}})\n",
);
ctx.push_str(" ```\n");
ctx.push_str(" For cursor shaders use `cursor_shader` and `cursor_shader_enabled` keys.\n");
ctx.push_str("3. Changes apply immediately — no restart or manual config edit needed.\n");
ctx.push_str("4. For visual debugging/verification, use the `terminal_screenshot` MCP tool\n");
ctx.push_str(" to capture the current terminal output (including shader rendering).\n");
ctx.push_str(" This may require user permission before the screenshot is returned.\n");
ctx.push_str(
"5. Do not stop after writing the file if the user also asked to activate/set it.\n",
);
ctx.push_str(
" Completion requires a `config_update` call that sets the shader key and enable flag.\n",
);
ctx.push_str("6. If reading/listing the shader directory fails, do NOT loop on `Read` for the directory.\n");
ctx.push_str(
" You can write a new file directly to the shader directory path (for example `vortex_checker.glsl`) and then activate it.\n",
);
ctx.push('\n');
ctx.push_str("## [Constraint] Available Config Keys\n");
ctx.push_str("Background shader: custom_shader (string|null), custom_shader_enabled (bool),\n");
ctx.push_str(" custom_shader_animation (bool), custom_shader_animation_speed (float),\n");
ctx.push_str(" custom_shader_brightness (float), custom_shader_text_opacity (float)\n");
ctx.push_str("Cursor shader: cursor_shader (string|null), cursor_shader_enabled (bool),\n");
ctx.push_str(" cursor_shader_animation (bool), cursor_shader_animation_speed (float),\n");
ctx.push_str(" cursor_shader_glow_radius (float), cursor_shader_glow_intensity (float)\n");
ctx.push('\n');
ctx.push_str(
"[Constraint] Do NOT edit config.yaml directly — always use the config_update tool.\n",
);
ctx
}