TaskApp.java

package com.guinetik.hexafun.examples.tasks;

import static com.guinetik.hexafun.examples.tasks.TaskInputs.*;
import static com.guinetik.hexafun.examples.tasks.TaskUseCases.*;

import com.guinetik.hexafun.HexaApp;
import com.guinetik.hexafun.HexaFun;
import com.guinetik.hexafun.fun.Result;
import java.util.List;

/**
 * Task application demonstrating the port registry feature.
 *
 * <p>Key concepts demonstrated:
 * <ul>
 *   <li>Port registration: {@code .withPort(TaskRepository.class, impl)}</li>
 *   <li>Port retrieval: {@code app.port(TaskRepository.class)}</li>
 *   <li>Use cases using ports for persistence</li>
 *   <li>Separation of domain logic from infrastructure</li>
 * </ul>
 *
 * <p>The repository is injected as a port, allowing easy swapping
 * between in-memory (for testing) and persistent implementations.
 */
public class TaskApp {

    private final HexaApp app;

    /**
     * Create a TaskApp with the given repository implementation.
     */
    public TaskApp(TaskRepository repository) {
        this.app = HexaFun.dsl()
            // Register the repository as a port
            .withPort(TaskRepository.class, repository)
            // CREATE: validate title, then save
            .useCase(CREATE)
            .validate(TaskValidators::validateCreateTitle)
            .validate(TaskValidators::validateCreateTitleLength)
            .handle(input -> {
                Task task = Task.create(input.title(), input.description());
                return Result.ok(repository.save(task));
            })
            // START: validate ID, find task, move to DOING
            .useCase(START)
            .validate(TaskValidators::validateStartTaskId)
            .handle(input ->
                repository
                    .findById(input.taskId())
                    .map(task -> Result.ok(repository.save(task.start())))
                    .orElse(Result.fail("Task not found: " + input.taskId()))
            )
            // COMPLETE: validate ID, find task, move to DONE
            .useCase(COMPLETE)
            .validate(TaskValidators::validateCompleteTaskId)
            .handle(input ->
                repository
                    .findById(input.taskId())
                    .map(task -> Result.ok(repository.save(task.complete())))
                    .orElse(Result.fail("Task not found: " + input.taskId()))
            )
            // UPDATE: validate ID and title, find and update
            .useCase(UPDATE)
            .validate(TaskValidators::validateUpdateTaskId)
            .validate(TaskValidators::validateUpdateTitle)
            .handle(input ->
                repository
                    .findById(input.taskId())
                    .map(task -> {
                        Task updated = task
                            .withTitle(input.title())
                            .withDescription(input.description());
                        return Result.ok(repository.save(updated));
                    })
                    .orElse(Result.fail("Task not found: " + input.taskId()))
            )
            // DELETE: validate ID, delete from repo
            .useCase(DELETE)
            .validate(TaskValidators::validateDeleteTaskId)
            .handle(input -> Result.ok(repository.delete(input.taskId())))
            // FIND: validate ID, look up in repo
            .useCase(FIND)
            .validate(TaskValidators::validateFindTaskId)
            .handle(input ->
                repository
                    .findById(input.taskId())
                    .map(Result::ok)
                    .orElse(Result.fail("Task not found: " + input.taskId()))
            )
            // LIST: no validation needed, just return all
            .useCase(LIST)
            .handle(input -> repository.findAll())
            .build();
    }

    /**
     * Create a TaskApp with an in-memory repository.
     */
    public static TaskApp withInMemoryRepo() {
        return new TaskApp(new InMemoryTaskRepository());
    }

    // ----- Public API -----

    public Result<Task> createTask(String title, String description) {
        return app.invoke(CREATE, new CreateTask(title, description));
    }

    public Result<Task> startTask(String taskId) {
        return app.invoke(START, new StartTask(taskId));
    }

    public Result<Task> completeTask(String taskId) {
        return app.invoke(COMPLETE, new CompleteTask(taskId));
    }

    public Result<Task> updateTask(
        String taskId,
        String title,
        String description
    ) {
        return app.invoke(UPDATE, new UpdateTask(taskId, title, description));
    }

    public Result<Boolean> deleteTask(String taskId) {
        return app.invoke(DELETE, new DeleteTask(taskId));
    }

    public Result<Task> findTask(String taskId) {
        return app.invoke(FIND, new FindTask(taskId));
    }

    public List<Task> listTasks() {
        return app.invoke(LIST, null);
    }

    /**
     * Get the underlying HexaApp (for testing/introspection).
     */
    public HexaApp getApp() {
        return app;
    }

    /**
     * Get the repository port directly.
     */
    public TaskRepository getRepository() {
        return app.port(TaskRepository.class);
    }

    // ----- Demo -----

    public static void main(String[] args) {
        System.out.println("=== TaskApp Demo ===\n");

        // Create app with in-memory repository
        TaskApp taskApp = TaskApp.withInMemoryRepo();

        // Show that the port is registered
        System.out.println(
            "Registered ports: " + taskApp.getApp().registeredPorts()
        );
        System.out.println(
            "Has TaskRepository: " +
                taskApp.getApp().hasPort(TaskRepository.class)
        );
        System.out.println();

        // Create some tasks
        System.out.println("Creating tasks...");
        Result<Task> task1 = taskApp.createTask(
            "Learn HexaFun",
            "Study the fluent DSL"
        );
        Result<Task> task2 = taskApp.createTask(
            "Write tests",
            "Add comprehensive tests"
        );
        Result<Task> task3 = taskApp.createTask(
            "Deploy app",
            "Push to production"
        );

        System.out.println("Created: " + task1.get().title());
        System.out.println("Created: " + task2.get().title());
        System.out.println("Created: " + task3.get().title());
        System.out.println();

        // List all tasks
        System.out.println("All tasks:");
        taskApp
            .listTasks()
            .forEach(t ->
                System.out.println(
                    "  - [" + (t.completed() ? "X" : " ") + "] " + t.title()
                )
            );
        System.out.println();

        // Complete a task
        String taskId = task1.get().id();
        System.out.println("Completing task: " + task1.get().title());
        taskApp.completeTask(taskId);
        System.out.println();

        // List again to see the change
        System.out.println("Tasks after completion:");
        taskApp
            .listTasks()
            .forEach(t ->
                System.out.println(
                    "  - [" + (t.completed() ? "X" : " ") + "] " + t.title()
                )
            );
        System.out.println();

        // Try validation - empty title
        System.out.println("Trying to create task with empty title...");
        Result<Task> invalid = taskApp.createTask("", "No title");
        System.out.println(
            "Result: " +
                (invalid.isSuccess() ? "OK" : "FAILED: " + invalid.error())
        );
        System.out.println();

        // Try validation - title too long
        System.out.println("Trying to create task with very long title...");
        String longTitle = "A".repeat(101);
        Result<Task> tooLong = taskApp.createTask(longTitle, "Too long");
        System.out.println(
            "Result: " +
                (tooLong.isSuccess() ? "OK" : "FAILED: " + tooLong.error())
        );
        System.out.println();

        // Find a task
        System.out.println("Finding task by ID: " + taskId);
        Result<Task> found = taskApp.findTask(taskId);
        System.out.println(
            "Found: " +
                found.get().title() +
                " (completed: " +
                found.get().completed() +
                ")"
        );
        System.out.println();

        // Delete a task
        System.out.println("Deleting task: " + task3.get().title());
        taskApp.deleteTask(task3.get().id());
        System.out.println("Tasks remaining: " + taskApp.listTasks().size());
    }
}