import gleam/list import gleam/float import gleam/int pub type SketchStroke { SketchStroke( points: List(#(Float, Float)), color: String, thickness: Float, timestamp: String, ) } pub type GhostWritingSession { GhostWritingSession( session_id: String, reference_image: String, user_strokes: List(SketchStroke), ghost_image_opacity: Float, current_page: Int, total_pages: Int, ) } pub type SimilarityScore { SimilarityScore( overall_score: Float, structural_accuracy: Float, proportional_accuracy: Float, detail_completeness: Float, feedback: List(String), ) } pub fn start_ghost_writing_session( image_url: String, ) -> GhostWritingSession { GhostWritingSession( session_id: "SESSION_2026", reference_image: image_url, user_strokes: [], ghost_image_opacity: 0.3, current_page: 1, total_pages: 1, ) } pub fn create_stroke( points: List(#(Float, Float)), color: String, thickness: Float, ) -> SketchStroke { SketchStroke( points: points, color: color, thickness: thickness, timestamp: "2026-03-19T00:00:00Z", ) } pub fn add_stroke_to_session( session: GhostWritingSession, stroke: SketchStroke, ) -> GhostWritingSession { GhostWritingSession( ..session, user_strokes: [stroke, ..session.user_strokes], ) } pub fn adjust_ghost_opacity( session: GhostWritingSession, opacity: Float, ) -> GhostWritingSession { let clamped_opacity = case opacity { o if o <. 0.0 -> 0.0 o if o >. 1.0 -> 1.0 o -> o } GhostWritingSession(..session, ghost_image_opacity: clamped_opacity) } pub fn calculate_similarity( user_drawing: List(SketchStroke), _reference_image: String, ) -> SimilarityScore { let stroke_count = list.length(user_drawing) let structural_accuracy = case stroke_count { c if c > 15 -> 85.0 c if c > 8 -> 65.0 c if c > 3 -> 45.0 _ -> 20.0 } let proportional_accuracy = case stroke_count { c if c > 10 -> 80.0 _ -> 50.0 } let detail_completeness = case stroke_count { c if c > 15 -> 90.0 c if c > 8 -> 70.0 _ -> 40.0 } let overall = {structural_accuracy +. proportional_accuracy +. detail_completeness} /. 3.0 SimilarityScore( overall_score: overall, structural_accuracy: structural_accuracy, proportional_accuracy: proportional_accuracy, detail_completeness: detail_completeness, feedback: generate_drawing_feedback(overall, stroke_count), ) } fn generate_drawing_feedback(score: Float, stroke_count: Int) -> List(String) { let base_feedback = case score { s if s >. 85.0 -> [ "🏆 Exceptional job! Your drawing matches the reference perfectly!", "Your spatial understanding is excellent.", ] s if s >. 70.0 -> [ "💪 Great work! Your sketch is very accurate.", "Minor details could be refined.", ] s if s >. 50.0 -> [ "📈 Good effort! The main structure is recognizable.", "Try adding more detail.", ] _ -> [ "Keep practicing! Reference the ghost image more carefully.", "Add more strokes to complete the drawing.", ] } case stroke_count { c if c < 3 -> list.append(base_feedback, ["Remember: Draw more strokes for better results!"]) _ -> base_feedback } } pub fn generate_comparison_visualization( user_drawing: List(SketchStroke), _reference_image: String, ) -> String { let canvas = "" let user_svg = render_strokes(user_drawing) "
" <> canvas <> user_svg <> "
" } fn render_strokes(strokes: List(SketchStroke)) -> String { "" <> list.fold(strokes, "", fn(acc, stroke) { acc <> render_stroke(stroke) }) <> "" } fn render_stroke(stroke: SketchStroke) -> String { case stroke.points { [] -> "" [first, ..rest] -> { let #(x, y) = first let path_start = "M " <> float.to_string(x) <> " " <> float.to_string(y) let path_rest = list.fold(rest, "", fn(acc, point) { let #(px, py) = point acc <> " L " <> float.to_string(px) <> " " <> float.to_string(py) }) "" } } } pub fn export_drawing_as_image(strokes: List(SketchStroke)) -> String { // Would generate PNG in production render_strokes(strokes) } pub fn compare_drawings( drawing1: List(SketchStroke), drawing2: List(SketchStroke), ) -> Float { // Simplified similarity metric let denominator = int.max( list.length(drawing1), list.length(drawing2), ) case denominator == 0 { True -> 0.0 False -> int.to_float( int.min( list.length(drawing1), list.length(drawing2), ), ) /. int.to_float(denominator) *. 100.0 } } pub fn suggest_next_diagram( user_level: Float, ) -> String { case user_level { l if l >. 85.0 -> "Next: Advanced Anatomy (Heart Cross-Section)" l if l >. 70.0 -> "Next: Intermediate Diagram (Chemical Structure)" l if l >. 50.0 -> "Next: Basic Sketch (Simple Geometric Shape)" _ -> "Practice more with simpler diagrams first." } } pub fn track_drawing_history(session: GhostWritingSession) -> String { let stroke_count = list.length(session.user_strokes) "Drawing Progress: " <> int.to_string(stroke_count) <> " strokes | Page " <> int.to_string(session.current_page) <> "/" <> int.to_string(session.total_pages) } pub fn create_drawing_canvas_html() -> String { "
Reference
" } pub fn analyze_drawing_curves(strokes: List(SketchStroke)) -> String { let smoothness = list.fold(strokes, 0.0, fn(acc, stroke) { acc +. calculate_curve_smoothness(stroke.points) }) /. int.to_float(list.length(strokes)) "Curve Analysis: " <> float.to_string(smoothness) <> "% smoothness" } fn calculate_curve_smoothness(points: List(#(Float, Float))) -> Float { // Simplified - would use actual curvature analysis case list.length(points) { n if n > 10 -> 85.0 n if n > 5 -> 70.0 _ -> 50.0 } } pub fn get_tracing_hints( _reference_image: String, current_progress: Float, ) -> List(String) { case current_progress { p if p <. 30.0 -> [ "💡 Start by outlining the basic shape", "Focus on the main contours first", ] p if p <. 70.0 -> [ "💡 Add the internal structures", "Pay attention to proportions", ] _ -> [ "💡 Refine details and shading", "Your sketch looks great!", ] } }