<?php
declare(strict_types=1); namespace App\Application; use App\Interfaces\FileSystemInterface; use App\Exceptions\FileNotFoundException; use App\Exceptions\NotFoundException; class BinderService { private FileSystemInterface $fileSystem; private \App\Interfaces\ClockInterface $clock; public function __construct(FileSystemInterface $fileSystem, \App\Interfaces\ClockInterface $clock) { $this->fileSystem = $fileSystem; $this->clock = $clock; } public function findAll(): array { $files = $this->fileSystem->list('meta'); $binderes = []; foreach ($files as $file) { if (str_starts_with($file, 'binder_') && str_ends_with($file, '.json')) { $id = substr($file, 0, -5); try { $binderes[] = $this->findById($id); } catch (NotFoundException $e) { } } } return $binderes; } public function findById(string $id): array { try { $json = $this->fileSystem->read("meta/{$id}.json"); $data = json_decode($json, true); if (!is_array($data)) { throw new NotFoundException("Invalid binder data format for {$id}"); } return $data; } catch (FileNotFoundException $e) { throw new NotFoundException("Binder not found: {$id}", [], $e); } } public function findOrCreateInitial(): array { $binderes = $this->findAll(); if (count($binderes) > 0) { return $binderes[0]; } $id = $this->createBinder([ 'title' => 'New Workspace', 'description' => 'Main workspace' ]); return $this->findById($id); } public function createBinder(array $data): string { $id = $data['id'] ?? uniqid('binder_'); $data['id'] = $id; $data['title'] = $data['title'] ?? 'New Binder'; $data['description'] = $data['description'] ?? ''; $data['placements'] = $data['placements'] ?? []; $data['pinned'] = (bool)($data['pinned'] ?? false); $data['created_at'] = date('c', $this->clock->now()); $data['updated_at'] = date('c', $this->clock->now()); $this->saveBinder($id, $data); return $id; } public function updateBinder(string $id, array $data): void { $existing = $this->findById($id); $existing['title'] = $data['title'] ?? $existing['title']; $existing['description'] = $data['description'] ?? $existing['description'] ?? ''; if (isset($data['placements'])) { $existing['placements'] = $data['placements']; } if (isset($data['scale'])) { $existing['scale'] = (float)$data['scale']; } if (isset($data['translateX'])) { $existing['translateX'] = (float)$data['translateX']; } if (isset($data['translateY'])) { $existing['translateY'] = (float)$data['translateY']; } if (array_key_exists('pinned', $data)) { $existing['pinned'] = (bool)$data['pinned']; } $existing['updated_at'] = date('c', $this->clock->now()); $this->saveBinder($id, $existing); } public function deleteBinder(string $id): void { if ($this->fileSystem->exists("meta/{$id}.json")) { $this->fileSystem->delete("meta/{$id}.json"); } } public function addPlacement(string $binderId, string $targetId, string $targetType, int $x, int $y): void { $binder = $this->findById($binderId); $found = false; foreach ($binder['placements'] as &$p) { if ($p['id'] === $targetId && $p['type'] === $targetType) { $p['x'] = $x; $p['y'] = $y; $found = true; break; } } if (!$found) { $binder['placements'][] = [ 'id' => $targetId, 'type' => $targetType, 'x' => $x, 'y' => $y ]; } $binder['updated_at'] = date('c', $this->clock->now()); $this->saveBinder($binderId, $binder); } public function removePlacement(string $binderId, string $targetId, string $targetType): void { $binder = $this->findById($binderId); $binder['placements'] = array_values(array_filter($binder['placements'], function($p) use ($targetId, $targetType) { return !($p['id'] === $targetId && $p['type'] === $targetType); })); $binder['updated_at'] = date('c', $this->clock->now()); $this->saveBinder($binderId, $binder); } private function saveBinder(string $id, array $data): void { $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); $this->fileSystem->write("meta/{$id}.json", $json); } } 