#!/usr/bin/env catnip
# DuckDB + Catnip : analytics declaratif et composable
# DuckDB execute le SQL, Catnip orchestre les scenarios analytiques.

duckdb = import('duckdb')

line = () => { print("-" * 60) }

# Donnees sources typees
struct Product {
    name
    category
    price
    stock

    inventory(self) => { self.price * self.stock }
    to_row(self) => { list(self.name, self.category, self.price, self.stock) }
}

products = list(
    Product("Laptop", "Hardware", 999.99, 15),
    Product("Mouse", "Accessory", 29.99, 150),
    Product("Keyboard", "Accessory", 79.99, 80),
    Product("Monitor", "Hardware", 299.99, 35),
    Product("Dock", "Accessory", 149.99, 40),
)

# Interop : Catnip structs -> DuckDB table
db = duckdb.connect()
db.execute("CREATE TABLE products (name TEXT, category TEXT, price DOUBLE, stock INT)")

for p in products {
    db.execute("INSERT INTO products VALUES (?, ?, ?, ?)", p.to_row())
}

print(f"⇒ {len(products)} produits chargés dans DuckDB")
line()

# KPI : specs declaratives
struct Metric {
    label
    sql
    unit

    run(self, db) => {
        value = db.execute(self.sql).fetchone()[0]
        f"  {self.label} : {value}{self.unit}"
    }
}

metrics = list(
    Metric("Produits", "SELECT COUNT(*) FROM products", ""),
    Metric("Prix moyen", "SELECT ROUND(AVG(price), 2) FROM products", " €"),
    Metric("Valeur stock", "SELECT ROUND(SUM(price * stock), 2) FROM products", " €"),
    Metric("Prix max", "SELECT ROUND(MAX(price), 2) FROM products", " €"),
)

print("⇒ KPI globaux")
# Broadcast sur structs : chaque Metric est une feuille, passé entier à la lambda
results = metrics.[(m) => { m.run(db) }]
for r in results { print(r) }
line()

# Ranking composable
ranking_sql = (order_expr, n) => {
    "SELECT name, category, price, stock, ROUND(price * stock, 2) " +
        f"FROM products ORDER BY {order_expr} DESC LIMIT {n}"
}

top_by = (order_expr, n) => {
    rows = db.execute(ranking_sql(order_expr, n)).fetchall()
    out = list()
    for row in rows {
        out.append(f"  {row[0]} | {row[1]} | {row[4]} €")
    }
    out
}

print("⇒ Top 3 par valeur de stock")
for item in top_by("price * stock", 3) { print(item) }
line()

# Segmentation : SQL window + pattern matching Catnip
ranked = db.execute("SELECT name, category, price, RANK() OVER (ORDER BY price DESC) as r FROM products").fetchall()

struct Segment {
    name
    price
    rank
    tier

    display(self) => { f"  #{self.rank} {self.name} ({self.price} €) => {self.tier}" }
}

tier_for = (price, rank) => {
    match True {
        _ if rank <= 2 => { "premium" }
        _ if price >= 100 => { "mid" }
        _ => { "entry" }
    }
}

segments = list()
for row in ranked {
    segments.append(Segment(row[0], row[2], row[3], tier_for(row[2], row[3])))
}

print("⇒ Segmentation sur résultat SQL")
# Broadcast explicite : les structs sont des feuilles, pas de descente récursive
segments.[(s) => { print(s.display()) }]

line()
db.close()