// AdvancedStreams.java — collectors, groupingBy, partitioningBy, flatMap, reduce.
import java.util.*;
import java.util.stream.*;

public class AdvancedStreams {

    record Line(String sku, int qty, double price) {
        double total() { return qty * price; }
    }

    record Order(String id, String customer, String status, List<Line> lines) {
        double total() { return lines.stream().mapToDouble(Line::total).sum(); }
    }

    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("PO-1", "Acme", "SHIPPED", List.of(new Line("AX-200", 24, 8.5), new Line("BX-310", 2, 40))),
                new Order("PO-2", "Globex", "NEW", List.of(new Line("AX-200", 10, 8.5))),
                new Order("PO-3", "Acme", "NEW", List.of(new Line("CX-420", 1, 999), new Line("AX-200", 5, 8.5))));

        // groupingBy: customer -> list of orders
        Map<String, List<Order>> byCustomer = orders.stream()
                .collect(Collectors.groupingBy(Order::customer));
        System.out.println(byCustomer.keySet());

        // groupingBy with a downstream collector: customer -> total spend
        Map<String, Double> spend = orders.stream()
                .collect(Collectors.groupingBy(Order::customer, TreeMap::new,
                        Collectors.summingDouble(Order::total)));
        System.out.println(spend);                                  // {Acme=1325.5, Globex=85.0}

        // counting per status
        Map<String, Long> perStatus = orders.stream()
                .collect(Collectors.groupingBy(Order::status, TreeMap::new, Collectors.counting()));
        System.out.println(perStatus);                              // {NEW=2, SHIPPED=1}

        // partitioningBy: two groups, true and false
        Map<Boolean, List<String>> bigOrSmall = orders.stream()
                .collect(Collectors.partitioningBy(o -> o.total() > 200,
                        Collectors.mapping(Order::id, Collectors.toList())));
        System.out.println(bigOrSmall);                             // {false=[PO-2], true=[PO-1, PO-3]}

        // flatMap: from orders to all their lines
        Map<String, Integer> qtyPerSku = orders.stream()
                .flatMap(o -> o.lines().stream())
                .collect(Collectors.toMap(Line::sku, Line::qty, Integer::sum, TreeMap::new));
        System.out.println(qtyPerSku);                              // {AX-200=39, BX-310=2, CX-420=1}

        // joining
        String ids = orders.stream().map(Order::id).collect(Collectors.joining(", ", "[", "]"));
        System.out.println(ids);                                    // [PO-1, PO-2, PO-3]

        // reduce: combine everything into one value
        double grandTotal = orders.stream().map(Order::total).reduce(0.0, Double::sum);
        System.out.println("Grand total: " + grandTotal);

        // summary statistics in one pass
        DoubleSummaryStatistics stats = orders.stream().mapToDouble(Order::total).summaryStatistics();
        System.out.printf("min %.1f max %.1f avg %.1f%n", stats.getMin(), stats.getMax(), stats.getAverage());

        // teeing (Java 12+): two collectors at once
        String report = orders.stream().collect(Collectors.teeing(
                Collectors.counting(),
                Collectors.averagingDouble(Order::total),
                (count, average) -> count + " orders, average " + String.format("%.2f", average)));
        System.out.println(report);

        // Most ordered SKU
        orders.stream()
                .flatMap(o -> o.lines().stream())
                .collect(Collectors.groupingBy(Line::sku, Collectors.summingInt(Line::qty)))
                .entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .ifPresent(e -> System.out.println("Top SKU: " + e.getKey() + " (" + e.getValue() + ")"));
    }
}
