# BatchPurchaseFacilitator

Enables bulk ticket purchases with a mix of user-defined static tickets and randomly-generated dynamic tickets. Orders are prepaid and executed by keepers.

## State Variables

| Variable             | Type                             | Description                             |
| -------------------- | -------------------------------- | --------------------------------------- |
| `jackpot`            | `IJackpot`                       | Immutable reference to Jackpot contract |
| `usdc`               | `IERC20`                         | Immutable USDC token reference          |
| `batchOrders`        | `mapping(address => BatchOrder)` | Active batch orders per user            |
| `staticTickets`      | `mapping(address => Ticket[])`   | Static tickets per user                 |
| `minimumTicketCount` | `uint256`                        | Minimum tickets required per order      |
| `executionNonce`     | `uint256`                        | Nonce for dynamic ticket generation     |

## Structs

### BatchOrder

```solidity
struct BatchOrder {
    uint256 orderDrawingId;      // Drawing this order was created for
    uint64 remainingUSDC;        // USDC balance for remaining tickets
    uint64 remainingTickets;     // Tickets still to be purchased
    uint64 totalTicketsOrdered;  // Original total ticket count
    uint64 dynamicTicketCount;   // Number of dynamic (random) tickets
    address[] referrers;         // Referrer addresses
    uint256[] referralSplit;     // PRECISE_UNIT-scaled weights
}
```

### BatchOrderInfo

```solidity
struct BatchOrderInfo {
    BatchOrder batchOrder;       // Order details
    IJackpot.Ticket[] staticTickets;  // User's static tickets
}
```

## Enums

### ExecutionAction

```solidity
enum ExecutionAction {
    EXECUTE_PARTIAL,           // Execute some tickets, order continues
    EXECUTE_FINAL,             // Execute remaining tickets, order completed
    CANCEL_WRONG_DRAWING,      // Order is for different drawing
    CANCEL_DRAWING_LOCKED,     // Drawing locked, auto-cancelled
    CANCEL_TOO_MANY_REFERRERS, // Too many referrers, auto-cancelled
    CANCEL_USER_REQUESTED      // User requested cancellation
}
```

## Events

### BatchOrderCreated

```solidity
event BatchOrderCreated(
    address indexed payer,
    address indexed recipient,
    uint256 indexed drawingId,
    uint256 totalCost,
    uint256 dynamicTicketCount,
    uint256 staticTicketCount
);
```

### BatchOrderCancelled

```solidity
event BatchOrderCancelled(
    address indexed recipient,
    ExecutionAction indexed executionAction,
    uint256 refundAmount
);
```

### BatchOrderExecuted

```solidity
event BatchOrderExecuted(
    address indexed user,
    uint256 indexed drawingId,
    uint256[] ticketIds,
    uint256 ticketsExecuted,
    uint256 remainingTickets,
    uint256 remainingUSDC
);
```

## Functions

### createBatchOrder

Create a prepaid batch order for the current drawing.

```solidity
function createBatchOrder(
    address _recipient,
    uint64 _dynamicTicketCount,
    IJackpot.Ticket[] calldata _userStaticTickets,
    address[] calldata _referrers,
    uint256[] calldata _referralSplit
) external
```

**Parameters:**

| Name                  | Type        | Description                                |
| --------------------- | ----------- | ------------------------------------------ |
| `_recipient`          | `address`   | Address to receive ticket NFTs             |
| `_dynamicTicketCount` | `uint64`    | Number of random tickets to generate       |
| `_userStaticTickets`  | `Ticket[]`  | User-defined static tickets                |
| `_referrers`          | `address[]` | Referrer addresses for fee sharing         |
| `_referralSplit`      | `uint256[]` | PRECISE\_UNIT-scaled weights (sum to 1e18) |

**Requirements:**

* Jackpot must be initialized and not locked
* Recipient must not have an active order
* Total tickets (dynamic + static) >= `minimumTicketCount`
* Each static ticket must have valid numbers
* Caller must have approved sufficient USDC

**Example:**

```solidity
// Create 100 tickets: 90 random + 10 specific
uint64 dynamicCount = 90;

IJackpot.Ticket[] memory staticTickets = new IJackpot.Ticket[](10);
for (uint256 i = 0; i < 10; i++) {
    staticTickets[i] = IJackpot.Ticket({
        normals: myFavoriteNumbers[i],
        bonusball: myBonusBall
    });
}

// Calculate cost
uint256 ticketPrice = jackpot.getDrawingState(jackpot.currentDrawingId()).ticketPrice;
uint256 totalCost = 100 * ticketPrice;

// Approve and create order
usdc.approve(address(batchFacilitator), totalCost);
batchFacilitator.createBatchOrder(
    msg.sender,           // recipient
    dynamicCount,         // dynamic tickets
    staticTickets,        // static tickets
    new address[](0),     // no referrers
    new uint256[](0)      // no splits
);
```

***

### cancelBatchOrder

Cancel your active batch order and receive a refund.

```solidity
function cancelBatchOrder() external
```

**Requirements:**

* Caller must have an active batch order

**Example:**

```solidity
// Cancel order and get remaining USDC back
batchFacilitator.cancelBatchOrder();
```

***

### getBatchOrderInfo

Get batch order details for a user.

```solidity
function getBatchOrderInfo(address _recipient) external view returns (BatchOrderInfo memory)
```

**Parameters:**

| Name         | Type      | Description      |
| ------------ | --------- | ---------------- |
| `_recipient` | `address` | Address to query |

**Returns:**

| Type             | Description                      |
| ---------------- | -------------------------------- |
| `BatchOrderInfo` | Order details and static tickets |

**Example:**

```solidity
IBatchPurchaseFacilitator.BatchOrderInfo memory info =
    batchFacilitator.getBatchOrderInfo(msg.sender);

uint256 remainingTickets = info.batchOrder.remainingTickets;
uint256 remainingUSDC = info.batchOrder.remainingUSDC;
```

***

### hasActiveBatchOrder

Check if a recipient has an active batch order.

```solidity
function hasActiveBatchOrder(address _recipient) external view returns (bool)
```

**Parameters:**

| Name         | Type      | Description      |
| ------------ | --------- | ---------------- |
| `_recipient` | `address` | Address to check |

**Returns:**

| Type   | Description                 |
| ------ | --------------------------- |
| `bool` | True if active order exists |

**Example:**

```solidity
if (batchFacilitator.hasActiveBatchOrder(msg.sender)) {
    // User already has an active order
    revert("Cancel existing order first");
}
```

***

### getBatchOrderActions

Get recommended actions for a batch of users (for keepers).

```solidity
function getBatchOrderActions(
    address[] calldata _recipients,
    uint256 _maxTicketsPerBatch
) external view returns (ExecutionAction[] memory)
```

**Parameters:**

| Name                  | Type        | Description                    |
| --------------------- | ----------- | ------------------------------ |
| `_recipients`         | `address[]` | Addresses to evaluate          |
| `_maxTicketsPerBatch` | `uint256`   | Max tickets per execution call |

**Returns:**

| Type                | Description                            |
| ------------------- | -------------------------------------- |
| `ExecutionAction[]` | Recommended actions for each recipient |

## Batch Order Flow

1. **Create Order**: User approves USDC and calls `createBatchOrder`
2. **Keeper Execution**: Keepers call `executeBatchOrder` to process tickets
3. **Partial Execution**: Large orders may be executed across multiple transactions
4. **Completion/Cancellation**: Order completes when all tickets are bought or is cancelled

### Cancellation Scenarios

Orders are automatically cancelled with full refund when:

* Drawing is locked (drawing in progress)
* Order was created for a previous drawing
* Too many referrers (exceeds max allowed)

Users can manually cancel at any time before execution completes.
