# CourtKulture Matchmaking Design

## Goal

Add original CourtKulture matchmaking modes for open-play sessions while keeping the existing Laravel session, queue, court, match, and result workflow intact.

This feature is inspired by common pickleball open-play workflows, not by copying another product's branding, UI, wording, source code, or proprietary behavior.

## Approved Product Decisions

- Staff can set a session default matchmaking mode and override the mode when generating a specific match.
- Reclub import is a paste-based staff tool: one player per line in a textarea.
- Skill levels use fixed MVP buckets: `beginner`, `intermediate`, and `advanced`.
- Player gender values are `male`, `female`, and `unspecified`.
- King and Queen of the Court keeps the winning doubles team on the same court, rotates the losing team out, and brings in the next two queued challengers.

## Scope

The MVP will support these staff-facing modes:

- `auto_balance`
- `skill_separated`
- `winner_loser_group`
- `mixed_doubles`
- `skill_courts`
- `king_queen`

Existing singles and doubles generation remains available. The new modes focus on doubles because every requested matchmaking style depends on pairing four players or keeping a doubles team on court. Singles may continue to use the existing FIFO generation path.

## Data Model

Add fields to existing tables:

- `open_play_sessions.matchmaking_mode`
  - Stores the session default matchmaking mode.
  - Default: `auto_balance`.
- `players.gender`
  - Stores `male`, `female`, or `unspecified`.
  - Default: `unspecified`.
- `players.import_source`
  - Stores `manual` or `reclub_paste`.
  - Default: `manual`.
- `courts.skill_level`
  - Nullable skill bucket for Skill Courts.
  - Values: `beginner`, `intermediate`, `advanced`, or null for any skill.
- `courts.rotation_mode`
  - Stores `standard` or `king_queen`.
  - Default: `standard`.
- `courts.holder_player_one_id`
  - Nullable player reference for the first player on the current King/Queen holder team.
- `courts.holder_player_two_id`
  - Nullable player reference for the second player on the current King/Queen holder team.
- `matches.matchmaking_mode`
  - Stores the mode used to generate the match for auditability and display.

The existing `players.skill_level` field will be treated as a fixed bucket field through request validation and UI selects.

## Backend Design

### MatchGenerationService

Keep match generation in `App\Services\MatchGenerationService`, but split the mode-specific logic into small private selector methods so the controller stays thin.

Public entry point:

- `generateMatchForMode(OpenPlaySession $session, string $format, ?string $mode = null): CourtMatch`

Behavior:

- If `$mode` is null, use `$session->matchmaking_mode`.
- If `$format` is `singles`, preserve the existing two-player FIFO singles generation.
- If `$format` is `doubles`, select four players using the requested mode.
- Persist the selected mode on `matches.matchmaking_mode`.
- Mark the selected court `in_use`.
- Mark selected queue entries and players `playing`.
- Throw a clear `RuntimeException` when there is no available court or not enough eligible players.

Selector rules:

- `auto_balance`
  - Select the next four waiting players by queue order.
  - Assign players to teams by minimizing total skill strength difference.
  - Skill weights: beginner = 1, intermediate = 2, advanced = 3, missing/unknown = 2.
- `skill_separated`
  - Find the first skill bucket, in queue order, with at least four waiting players.
  - Use the first four players from that bucket.
  - Apply auto-balance within that same-skill group.
- `winner_loser_group`
  - Prefer a group of four players whose most recent completed result status matches: winners with winners, losers with losers.
  - If no group has enough eligible players, throw a clear message so staff can switch the override to Auto-Balance.
- `mixed_doubles`
  - Require at least two waiting male players and two waiting female players.
  - Create two mixed teams.
  - Use skill balancing when choosing pair combinations.
  - Players marked `unspecified` are not eligible for this mode.
- `skill_courts`
  - Select the first available court with a skill bucket that has at least four waiting players.
  - If a court has no skill bucket, it can be used by any skill group after skill-specific courts are considered.
  - Use same-skill players when possible; otherwise throw a clear message that no skill court has enough eligible players.
- `king_queen`
  - Prefer an available court with `rotation_mode = king_queen`.
  - If the court has no current holder team, generate a balanced doubles match normally.
  - After a result, write the winning team's player IDs to `courts.holder_player_one_id` and `courts.holder_player_two_id`.
  - The next generated match on that court uses the holder team plus the next two waiting challengers.
  - If there are not two waiting challengers, throw a clear message and keep the holder team on the court.
  - Losing players return to the queue if staff chooses to queue players after result.

### MatchResultService

Extend result recording to support King and Queen:

- Continue updating scores, winner flags, player wins/losses, and games played.
- Standard courts become `available` after result as they do today.
- King/Queen courts remain logically available for the next challenge while retaining the winner team as holder data.
- Losing players rotate out according to the existing `return_players_to_queue` checkbox.
- If a standard match completes on a court with holder fields set, clear the holder fields.

### Reclub Paste Import

Create a small import service:

- `App\Services\PlayerRosterImportService`

Input format:

- One player per line.
- Supported line forms:
  - `Name`
  - `Name, Skill`
  - `Name, Skill, Gender`
  - `Name, Email, Skill, Gender`

Parsing behavior:

- Trim whitespace.
- Ignore blank lines.
- Normalize skill and gender aliases case-insensitively.
- Skip duplicate display names within the same session.
- Create players with `import_source = reclub_paste`.
- Optionally check imported players into the queue.
- Return an import summary with created count, skipped duplicates, invalid lines, and queued count.

Security and validation:

- Limit paste payload length.
- Limit number of imported lines per request.
- Escape all output through Blade defaults.
- Do not execute or render pasted content as HTML.
- Reuse staff middleware and Form Request authorization.

## UI Design

### Session Create

Add a matchmaking mode select:

- Label: `Default Matchmaking`
- Default: `Auto-Balance`

### Staff Session Dashboard

Keep the current mobile-first Bootstrap card layout and add:

- Quick Add Player:
  - Skill bucket select.
  - Gender select.
- Reclub Paste Import card:
  - Textarea.
  - `Check imported players into queue` checkbox.
  - Submit button.
  - Summary flash message after import.
- Courts card:
  - Skill bucket select for Skill Courts.
  - Rotation mode select: Standard or King/Queen.
- Generate Match card:
  - Format select or buttons for Singles/Doubles.
  - Matchmaking mode override select, defaulting to the session mode.
  - Clear validation/error message area.
- Queue and standings:
  - Show skill and gender badges.
  - Show current mode/court type where helpful without making the table crowded.

### Public Live Session

Show read-only labels for:

- Court skill bucket.
- King/Queen court status.
- Active match mode.
- Player skill badges in queue, active matches, and standings.

## Routes and Requests

Add staff route:

- `POST /staff/sessions/{session}/players/import`
  - Controller action: `Staff\PlayerImportController@store`
  - Route name: `staff.sessions.players.import`

Add request classes:

- `ImportPlayersRequest`
- Extend existing `StorePlayerRequest`, `StoreCourtRequest`, `StoreSessionRequest`, and `GenerateMatchRequest`.

Controllers remain thin and delegate business logic to services.

## Testing Plan

Unit tests:

- Auto-balance splits four players into the closest team strength.
- Skill separated selects four players from the same skill bucket.
- Mixed doubles requires two male and two female players and creates two mixed teams.
- Skill Courts selects an available court that matches the chosen skill group.
- King/Queen keeps winners as court holders and rotates challengers in.
- Paste import creates valid players, skips duplicates, reports invalid lines, and can queue imported players.

Feature tests:

- Staff can set session default matchmaking mode.
- Staff can add player with fixed skill and gender.
- Staff can paste-import roster lines.
- Staff can override matchmaking mode when generating a match.
- Staff dashboard renders mode, skill, gender, court type, and import controls.
- Non-staff users cannot import players or generate matches.

Verification:

- Run focused tests for services and staff dashboard.
- Run full `php artisan test`.
- Run `npm run build`.

## Files Likely Affected

- `routes/web.php`
- `app/Http/Controllers/Staff/MatchController.php`
- `app/Http/Controllers/Staff/PlayerController.php`
- `app/Http/Controllers/Staff/PlayerImportController.php`
- `app/Http/Controllers/Staff/CourtController.php`
- `app/Http/Controllers/Staff/SessionController.php`
- `app/Http/Requests/Staff/GenerateMatchRequest.php`
- `app/Http/Requests/Staff/ImportPlayersRequest.php`
- `app/Http/Requests/Staff/StoreCourtRequest.php`
- `app/Http/Requests/Staff/StorePlayerRequest.php`
- `app/Http/Requests/Staff/StoreSessionRequest.php`
- `app/Models/Court.php`
- `app/Models/CourtMatch.php`
- `app/Models/OpenPlaySession.php`
- `app/Models/Player.php`
- `app/Services/MatchGenerationService.php`
- `app/Services/MatchResultService.php`
- `app/Services/PlayerRosterImportService.php`
- `database/migrations/*_add_matchmaking_fields_to_open_play_tables.php`
- `database/factories/CourtFactory.php`
- `database/factories/CourtMatchFactory.php`
- `database/factories/OpenPlaySessionFactory.php`
- `database/factories/PlayerFactory.php`
- `resources/views/staff/sessions/create.blade.php`
- `resources/views/staff/sessions/show.blade.php`
- `resources/views/public/sessions/show.blade.php`
- `resources/css/app.css`
- `tests/Unit/Services/MatchGenerationServiceTest.php`
- `tests/Unit/Services/MatchResultServiceTest.php`
- `tests/Unit/Services/PlayerRosterImportServiceTest.php`
- `tests/Feature/StaffDashboardTest.php`
- `tests/Feature/PublicLiveSessionTest.php`

## Out of Scope

- Copying PickleQ UI, colors, exact text, logo, proprietary source code, or layout.
- Direct Reclub API integration.
- CSV upload.
- DUPR/numeric ratings.
- Complex tournament brackets.
- Real-time websocket updates.
- Player self check-in.

## Acceptance Criteria

- Staff can choose a session default matchmaking mode.
- Staff can override matchmaking mode when generating a match.
- Staff can paste a roster and import players one per line.
- Imported players can optionally enter the queue immediately.
- Doubles generation supports Auto-Balance, Skill Separated, Winner/Loser Group, Mixed Doubles, Skill Courts, and King/Queen MVP behavior.
- The dashboard remains mobile-friendly and uses CourtKulture branding.
- Business logic is covered by service tests.
- Staff workflows are covered by feature tests.
- `php artisan test` passes.
- `npm run build` passes.
