// =============================================================================
// Task Manager API - A Showcase of Lux's Unique Features
// =============================================================================
//
// This example demonstrates Lux's three killer features:
//
// 1. ALGEBRAIC EFFECTS - Every side effect is explicit in function signatures
//    - No hidden I/O, no surprise database calls
//    - Testing is trivial: just swap handlers
//
// 2. BEHAVIORAL TYPES - Compile-time guarantees about function behavior
//    - `is pure` - no side effects, safe to cache
//    - `is total` - always terminates, never fails
//    - `is idempotent` - safe to retry without side effects
//    - `is deterministic` - same input = same output
//
// 3. SCHEMA EVOLUTION - Versioned types with automatic migration
//    - Data structures evolve safely over time
//    - Old data automatically upgrades
//
// To run: lux run examples/showcase/task_manager.lux
// =============================================================================


// =============================================================================
// PART 1: VERSIONED DATA TYPES (Schema Evolution)
// =============================================================================

// Task v1: Our original data model (simple)
type Task @v1 {
    id: String,
    title: String,
    done: Bool
}

// Task v2: Added priority field
// The `from @v1` clause defines how to migrate old data automatically
type Task @v2 {
    id: String,
    title: String,
    done: Bool,
    priority: String,  // New field: "low", "medium", "high"

    // Migration: old tasks get "medium" priority by default
    from @v1 = {
        id: old.id,
        title: old.title,
        done: old.done,
        priority: "medium"
    }
}

// Task v3: Added due date and tags
// Migrations chain automatically: v1 → v2 → v3
type Task @v3 {
    id: String,
    title: String,
    done: Bool,
    priority: String,
    dueDate: Option<Int>,   // Unix timestamp, optional
    tags: List<String>,      // New: categorization

    from @v2 = {
        id: old.id,
        title: old.title,
        done: old.done,
        priority: old.priority,
        dueDate: None,  // No due date for migrated tasks
        tags: []        // Empty tags for migrated tasks
    }
}

// Use @latest to always refer to the newest version
type TaskList = List<Task@latest>


// =============================================================================
// PART 2: PURE FUNCTIONS WITH BEHAVIORAL TYPES
// =============================================================================

// Pure function: no side effects, safe to cache, parallelize, eliminate if unused
// The compiler verifies `is pure` - if you try to call an effect, it errors.
fn formatTask(task: Task@latest): String
    is pure
    is deterministic
    is total = {
    let status = if task.done then "[x]" else "[ ]"
    let priority = match task.priority {
        "high" => "!!",
        "medium" => "!",
        _ => ""
    }
    status + " " + priority + task.title
}

// Idempotent function: f(f(x)) = f(x)
// Safe to apply multiple times without changing the result
// Critical for retry logic - the compiler verifies this property
fn normalizeTitle(title: String): String
    is pure
    is idempotent = {
    title
    |> String.trim
    |> String.toLower
}

// Total function: always terminates, never throws
// No Fail effect allowed, recursion must be structurally decreasing
fn countCompleted(tasks: TaskList): Int
    is pure
    is total = {
    match tasks {
        [] => 0,
        [task, ...rest] =>
            (if task.done then 1 else 0) + countCompleted(rest)
    }
}

// Commutative function: f(a, b) = f(b, a)
// Enables parallel reduction and argument reordering optimizations
fn maxPriority(a: String, b: String): String
    is pure
    is commutative = {
    let priorityValue = fn(p: String): Int =>
        match p {
            "high" => 3,
            "medium" => 2,
            "low" => 1,
            _ => 0
        }
    if priorityValue(a) > priorityValue(b) then a else b
}

// Filter tasks by criteria - pure, can be cached and parallelized
fn filterByPriority(tasks: TaskList, priority: String): TaskList
    is pure
    is deterministic = {
    List.filter(tasks, fn(t: Task@latest): Bool => t.priority == priority)
}

fn filterPending(tasks: TaskList): TaskList
    is pure
    is deterministic = {
    List.filter(tasks, fn(t: Task@latest): Bool => !t.done)
}

fn filterCompleted(tasks: TaskList): TaskList
    is pure
    is deterministic = {
    List.filter(tasks, fn(t: Task@latest): Bool => t.done)
}


// =============================================================================
// PART 3: EFFECTS - EXPLICIT SIDE EFFECTS
// =============================================================================

// Custom effect for task storage
// This declares WHAT operations are available, not HOW they work
effect TaskStore {
    fn save(task: Task@latest): Result<Task@latest, String>
    fn getById(id: String): Option<Task@latest>
    fn getAll(): TaskList
    fn delete(id: String): Bool
}

// Service functions declare their effects in the type signature
// Anyone reading the signature knows exactly what side effects can occur

// Create a new task - requires TaskStore and Random effects
fn createTask(title: String, priority: String): Task@latest
    with {TaskStore, Random} = {
    let id = "task_" + toString(Random.int(10000, 99999))
    let task = {
        id: id,
        title: normalizeTitle(title),  // Uses our idempotent normalizer
        done: false,
        priority: priority,
        dueDate: None,
        tags: []
    }
    match TaskStore.save(task) {
        Ok(saved) => saved,
        Err(_) => task  // Return unsaved if storage fails
    }
}

// Complete a task - idempotent, safe to retry
// If the network fails mid-request, retry is safe
fn completeTask(id: String): Option<Task@latest>
    is idempotent  // Compiler verifies this is safe to retry
    with {TaskStore} = {
    match TaskStore.getById(id) {
        None => None,
        Some(task) => {
            // Setting done = true is idempotent: already done? stays done
            let updated = { ...task, done: true }
            match TaskStore.save(updated) {
                Ok(saved) => Some(saved),
                Err(_) => None
            }
        }
    }
}

// Get task summary - logging effect, but computation is pure
fn getTaskSummary(): { total: Int, completed: Int, pending: Int, highPriority: Int }
    with {TaskStore, Logger} = {
    let tasks = TaskStore.getAll()
    Logger.log("Fetched " + toString(List.length(tasks)) + " tasks")

    // These computations are pure - could be parallelized
    let completed = countCompleted(tasks)
    let pending = List.length(tasks) - completed
    let highPriority = List.length(filterByPriority(tasks, "high"))

    { total: List.length(tasks), completed: completed, pending: pending, highPriority: highPriority }
}


// =============================================================================
// PART 4: EFFECT HANDLERS - SWAP IMPLEMENTATIONS
// =============================================================================

// In-memory handler for testing
// This handler stores tasks in a mutable list - perfect for unit tests
handler InMemoryTaskStore: TaskStore {
    let tasks: List<Task@latest> = []

    fn save(task: Task@latest): Result<Task@latest, String> = {
        // Remove existing task with same ID (if any), then add new
        tasks = List.filter(tasks, fn(t: Task@latest): Bool => t.id != task.id)
        tasks = List.concat(tasks, [task])
        Ok(task)
    }

    fn getById(id: String): Option<Task@latest> = {
        List.find(tasks, fn(t: Task@latest): Bool => t.id == id)
    }

    fn getAll(): TaskList = tasks

    fn delete(id: String): Bool = {
        let before = List.length(tasks)
        tasks = List.filter(tasks, fn(t: Task@latest): Bool => t.id != task.id)
        List.length(tasks) < before
    }
}

// Logging handler - wraps another handler with logging
handler LoggingTaskStore(inner: TaskStore): TaskStore with {Logger} {
    fn save(task: Task@latest): Result<Task@latest, String> = {
        Logger.log("Saving task: " + task.id)
        inner.save(task)
    }

    fn getById(id: String): Option<Task@latest> = {
        Logger.log("Getting task: " + id)
        inner.getById(id)
    }

    fn getAll(): TaskList = {
        Logger.log("Getting all tasks")
        inner.getAll()
    }

    fn delete(id: String): Bool = {
        Logger.log("Deleting task: " + id)
        inner.delete(id)
    }
}

// Simple logger effect and handler
effect Logger {
    fn log(message: String): Unit
}

handler ConsoleLogger: Logger with {Console} {
    fn log(message: String): Unit = {
        Console.print("[LOG] " + message)
    }
}

handler SilentLogger: Logger {
    fn log(message: String): Unit = {
        // Do nothing - useful for tests
    }
}


// =============================================================================
// PART 5: TESTING - SWAP HANDLERS, NO MOCKS NEEDED
// =============================================================================

// Test helper: creates a controlled environment
fn runTestScenario(): Unit with {Console} = {
    Console.print("=== Running Test Scenario ===")
    Console.print("")

    // Use in-memory storage and silent logging for tests
    // No database, no file I/O, no network - pure in-memory testing
    let result = run {
        // Create some tasks
        let task1 = createTask("Write documentation", "high")
        let task2 = createTask("Fix bug #123", "medium")
        let task3 = createTask("Review PR", "low")

        // Complete one task
        completeTask(task1.id)

        // Get summary
        getTaskSummary()
    } with {
        TaskStore = InMemoryTaskStore,
        Logger = SilentLogger,
        Random = {
            // Deterministic "random" for tests
            let counter = 0
            fn int(min: Int, max: Int): Int = {
                counter = counter + 1
                min + (counter * 12345) % (max - min)
            }
        }
    }

    Console.print("Test Results:")
    Console.print("  Total tasks: " + toString(result.total))
    Console.print("  Completed: " + toString(result.completed))
    Console.print("  Pending: " + toString(result.pending))
    Console.print("  High priority: " + toString(result.highPriority))
    Console.print("")

    // Verify results
    if result.total == 3 &&
       result.completed == 1 &&
       result.pending == 2 &&
       result.highPriority == 1 {
        Console.print("All tests passed!")
    } else {
        Console.print("Test failed!")
    }
}


// =============================================================================
// PART 6: SCHEMA MIGRATION DEMO
// =============================================================================

fn demonstrateMigration(): Unit with {Console} = {
    Console.print("=== Schema Evolution Demo ===")
    Console.print("")

    // Simulate loading a v1 task (from old database/API)
    let oldTask = Schema.versioned("Task", 1, {
        id: "legacy_001",
        title: "Old task from v1",
        done: false
    })

    Console.print("Loaded v1 task:")
    Console.print("  Version: " + toString(Schema.getVersion(oldTask)))
    Console.print("")

    // Migrate to latest version automatically
    let migratedTask = Schema.migrate(oldTask, 3)

    Console.print("After migration to v3:")
    Console.print("  Version: " + toString(Schema.getVersion(migratedTask)))
    Console.print("  Has priority: " + migratedTask.priority)  // Added by v2 migration
    Console.print("  Has tags: " + toString(List.length(migratedTask.tags)) + " tags")  // Added by v3
    Console.print("")
    Console.print("Old data seamlessly upgraded!")
}


// =============================================================================
// PART 7: MAIN - PUTTING IT ALL TOGETHER
// =============================================================================

fn main(): Unit with {Console} = {
    Console.print("╔═══════════════════════════════════════════════════════════╗")
    Console.print("║         Lux Task Manager - Feature Showcase               ║")
    Console.print("╚═══════════════════════════════════════════════════════════╝")
    Console.print("")

    // Demonstrate pure functions
    Console.print("--- Pure Functions (Behavioral Types) ---")
    let sampleTask = {
        id: "demo",
        title: "Learn Lux",
        done: false,
        priority: "high",
        dueDate: None,
        tags: ["learning", "programming"]
    }
    Console.print("Formatted task: " + formatTask(sampleTask))
    Console.print("Normalized title: " + normalizeTitle("  HELLO WORLD  "))
    Console.print("")

    // Demonstrate schema evolution
    demonstrateMigration()
    Console.print("")

    // Run tests with swapped handlers
    runTestScenario()
    Console.print("")

    Console.print("╔═══════════════════════════════════════════════════════════╗")
    Console.print("║  Key Takeaways:                                           ║")
    Console.print("║                                                           ║")
    Console.print("║  1. Effects in signatures = no hidden side effects        ║")
    Console.print("║  2. Behavioral types = compile-time guarantees            ║")
    Console.print("║  3. Handler swapping = easy testing without mocks         ║")
    Console.print("║  4. Schema evolution = safe data migrations               ║")
    Console.print("╚═══════════════════════════════════════════════════════════╝")
}

// Run the showcase
let _ = run main() with {}
