// StreamsBasics.java — creating streams, filter/map/sorted, terminal operations.
import java.util.*;
import java.util.stream.*;

public class StreamsBasics {

    record Order(String id, String customer, double amount, String status) { }

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("PO-1", "Acme", 250.0, "SHIPPED"),
                new Order("PO-2", "Globex", 90.0, "NEW"),
                new Order("PO-3", "Acme", 1200.0, "NEW"),
                new Order("PO-4", "Initech", 40.0, "CANCELLED"));

        // source -> intermediate operations -> terminal operation
        List<String> bigNewOrders = orders.stream()
                .filter(o -> o.status().equals("NEW"))        // keep some
                .filter(o -> o.amount() > 100)
                .map(Order::id)                               // transform each
                .toList();                                    // collect (Java 16+)
        System.out.println(bigNewOrders);                     // [PO-3]

        // Sorting and limiting
        orders.stream()
                .sorted(Comparator.comparingDouble(Order::amount).reversed())
                .limit(2)
                .forEach(o -> System.out.println(o.id() + " " + o.amount()));

        // Numbers: mapToDouble gives sum/average/max without boxing
        double total = orders.stream().mapToDouble(Order::amount).sum();
        OptionalDouble avg = orders.stream().mapToDouble(Order::amount).average();
        System.out.printf("Total %.1f, average %.1f%n", total, avg.orElse(0));

        // Matching and finding
        boolean anyCancelled = orders.stream().anyMatch(o -> o.status().equals("CANCELLED"));
        boolean allPositive = orders.stream().allMatch(o -> o.amount() > 0);
        Optional<Order> firstAcme = orders.stream().filter(o -> o.customer().equals("Acme")).findFirst();
        System.out.println(anyCancelled + " " + allPositive + " " + firstAcme.map(Order::id).orElse("-"));

        // distinct, count
        long customers = orders.stream().map(Order::customer).distinct().count();
        System.out.println(customers + " customers");          // 3 customers

        // Other ways to create streams
        Stream.of("a", "b", "c").map(String::toUpperCase).forEach(System.out::print);
        System.out.println();
        IntStream.rangeClosed(1, 5).map(n -> n * n).forEach(n -> System.out.print(n + " "));
        System.out.println();
        Arrays.stream(new int[] {3, 1, 2}).sorted().forEach(System.out::print);
        System.out.println();

        // Streams are lazy: nothing runs until the terminal operation
        Stream<String> lazy = orders.stream().map(o -> {
            System.out.println("  mapping " + o.id());
            return o.id();
        });
        System.out.println("Nothing mapped yet...");
        System.out.println(lazy.findFirst().orElse(""));       // maps only PO-1

        // A stream can be used only once
        Stream<Order> once = orders.stream();
        once.count();
        try {
            once.count();
        } catch (IllegalStateException e) {
            System.out.println("Streams can't be reused: " + e.getMessage());
        }
    }
}
