0) { return $this->paginate( 'SELECT st.*, CASE WHEN tst.sheet_template_id IS NULL THEN 0 ELSE 1 END AS assigned_to_tournament FROM sheet_templates st LEFT JOIN tournament_sheet_templates tst ON tst.sheet_template_id = st.id AND tst.tournament_id = :tournament_id ORDER BY assigned_to_tournament DESC, st.active DESC, st.name ASC', ['tournament_id' => $tournamentId] ); } return $this->paginate('SELECT *, 0 AS assigned_to_tournament FROM sheet_templates ORDER BY active DESC, name ASC'); } public function create(array $data): array { $stmt = $this->db->prepare( 'INSERT INTO sheet_templates (code, name, image_path, page_width, page_height, config_json, active) VALUES (:code, :name, :image_path, :page_width, :page_height, :config_json, :active)' ); $stmt->execute([ 'code' => $data['code'], 'name' => $data['name'], 'image_path' => $data['image_path'], 'page_width' => (int) $data['page_width'], 'page_height' => (int) $data['page_height'], 'config_json' => is_string($data['config_json']) ? $data['config_json'] : json_encode($data['config_json']), 'active' => (int) ($data['active'] ?? 1), ]); return $this->find((int) $this->db->lastInsertId()); } public function update(int $id, array $data): ?array { $allowed = ['code', 'name', 'image_path', 'page_width', 'page_height', 'config_json', 'active']; $fields = []; $params = ['id' => $id]; foreach ($allowed as $field) { if (array_key_exists($field, $data)) { $fields[] = "$field = :$field"; $params[$field] = $field === 'config_json' && !is_string($data[$field]) ? json_encode($data[$field]) : $data[$field]; } } if ($fields) { $this->db->prepare('UPDATE sheet_templates SET ' . implode(', ', $fields) . ' WHERE id = :id')->execute($params); } return $this->find($id); } public function assignToTournament(int $tournamentId, int $templateId): void { $this->db->prepare('DELETE FROM tournament_sheet_templates WHERE tournament_id = :id')->execute(['id' => $tournamentId]); $this->db->prepare( 'INSERT INTO tournament_sheet_templates (tournament_id, sheet_template_id, is_default) VALUES (:tournament_id, :sheet_template_id, 1)' )->execute(['tournament_id' => $tournamentId, 'sheet_template_id' => $templateId]); } public function overrideMatch(int $matchId, int $templateId): void { $this->db->prepare( 'INSERT INTO match_sheet_template_overrides (match_id, sheet_template_id) VALUES (:match_id, :sheet_template_id) ON DUPLICATE KEY UPDATE sheet_template_id = VALUES(sheet_template_id)' )->execute(['match_id' => $matchId, 'sheet_template_id' => $templateId]); } public function find(int $id): ?array { $stmt = $this->db->prepare('SELECT * FROM sheet_templates WHERE id = :id'); $stmt->execute(['id' => $id]); return $stmt->fetch() ?: null; } public function resolveForMatch(int $matchId): ?array { $stmt = $this->db->prepare( 'SELECT st.* FROM matches m LEFT JOIN match_sheet_template_overrides mo ON mo.match_id = m.id LEFT JOIN tournament_sheet_templates tt ON tt.tournament_id = m.tournament_id AND tt.is_default = 1 JOIN sheet_templates st ON st.id = COALESCE(mo.sheet_template_id, tt.sheet_template_id) WHERE m.id = :id AND st.active = 1 LIMIT 1' ); $stmt->execute(['id' => $matchId]); $template = $stmt->fetch(); if ($template) { return $template; } $stmt = $this->db->query('SELECT * FROM sheet_templates WHERE active = 1 ORDER BY id LIMIT 1'); return $stmt->fetch() ?: null; } public function effectiveConfig(array $template): array { $config = json_decode($template['config_json'] ?? '{}', true); return is_array($config) ? $config : []; } }