// OptionalDemo.java — Optional instead of null.
import java.util.*;

public class OptionalDemo {

    record Address(String city) { }
    record Customer(String name, Address address, String email) { }

    static final Map<String, Customer> DB = Map.of(
            "C1", new Customer("Acme", new Address("Delhi"), "ap@acme.test"),
            "C2", new Customer("Globex", null, null));

    // Return Optional when "no result" is a normal outcome
    static Optional<Customer> findCustomer(String id) {
        return Optional.ofNullable(DB.get(id));
    }

    public static void main(String[] args) {
        // Creating Optionals
        Optional<String> some = Optional.of("value");
        Optional<String> none = Optional.empty();
        Optional<String> maybe = Optional.ofNullable(null);
        System.out.println(some.isPresent() + " " + none.isEmpty() + " " + maybe.isPresent()); // true true false

        // Getting a value safely
        System.out.println(findCustomer("C1").map(Customer::name).orElse("unknown"));   // Acme
        System.out.println(findCustomer("C9").map(Customer::name).orElse("unknown"));   // unknown

        // Chaining through possibly-missing data with map / flatMap
        String city = findCustomer("C2")
                .map(Customer::address)          // null address -> empty Optional
                .map(Address::city)
                .orElse("no city on file");
        System.out.println(city);                                                       // no city on file

        // filter
        findCustomer("C1")
                .map(Customer::email)
                .filter(e -> e.endsWith(".test"))
                .ifPresent(e -> System.out.println("Test email: " + e));

        // ifPresentOrElse (Java 9+)
        findCustomer("C9").ifPresentOrElse(
                c -> System.out.println("Found " + c.name()),
                () -> System.out.println("No such customer"));

        // orElseGet: compute the fallback only when needed
        String name = findCustomer("C9").map(Customer::name).orElseGet(() -> "Guest-" + 42);
        System.out.println(name);

        // orElseThrow: when absence is really an error
        try {
            findCustomer("C9").orElseThrow(() -> new NoSuchElementException("Customer C9 not found"));
        } catch (NoSuchElementException e) {
            System.out.println(e.getMessage());
        }

        // or (Java 9+): try another source
        Optional<Customer> c = findCustomer("C9").or(() -> findCustomer("C1"));
        System.out.println(c.map(Customer::name).get());                                 // Acme

        // stream() (Java 9+): turn many Optionals into the present values
        List<String> emails = List.of("C1", "C2", "C9").stream()
                .map(OptionalDemo::findCustomer)
                .flatMap(Optional::stream)
                .map(Customer::email)
                .filter(Objects::nonNull)
                .toList();
        System.out.println(emails);                                                       // [ap@acme.test]
    }
}
