* SPDX-License-Identifier: AGPL-3.0-or-later */ namespace KTXM\ProviderJmapc\Providers\Document; use Generator; use KTXF\Resource\Provider\ResourceServiceIdentityInterface; use KTXF\Resource\Provider\ResourceServiceLocationInterface; use KTXF\Resource\Delta\Delta; use KTXF\Resource\Documents\Collection\CollectionBaseInterface; use KTXF\Resource\Documents\Collection\CollectionMutableInterface; use KTXF\Resource\Documents\Service\ServiceBaseInterface; use KTXF\Resource\Documents\Service\ServiceCollectionMutableInterface; use KTXF\Resource\Documents\Service\ServiceConfigurableInterface; use KTXF\Resource\Documents\Service\ServiceMutableInterface; use KTXF\Resource\Filter\Filter; use KTXF\Resource\Filter\IFilter; use KTXF\Resource\Range\IRange; use KTXF\Resource\Range\Range; use KTXF\Resource\Range\RangeType; use KTXF\Resource\Sort\ISort; use KTXF\Resource\Sort\Sort; use KTXM\ProviderJmapc\Providers\ServiceIdentityBasic; use KTXM\ProviderJmapc\Providers\ServiceLocation; use KTXM\ProviderJmapc\Service\Remote\RemoteFilesService; use KTXM\ProviderJmapc\Service\Remote\RemoteService; class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceConfigurableInterface, ServiceCollectionMutableInterface { public const JSON_TYPE = ServiceBaseInterface::JSON_TYPE; private const PROVIDER_IDENTIFIER = 'jmap'; private ?string $serviceTenantId = null; private ?string $serviceUserId = null; private ?string $serviceIdentifier = null; private ?string $serviceLabel = null; private bool $serviceEnabled = false; private bool $serviceDebug = false; private ?ServiceLocation $location = null; private ?ServiceIdentityBasic $identity = null; private array $auxiliary = []; private array $serviceAbilities = [ self::CAPABILITY_COLLECTION_LIST => true, self::CAPABILITY_COLLECTION_LIST_FILTER => [ self::CAPABILITY_COLLECTION_FILTER_LABEL => 's:100:256:256', ], self::CAPABILITY_COLLECTION_LIST_SORT => [ self::CAPABILITY_COLLECTION_SORT_LABEL, ], self::CAPABILITY_COLLECTION_EXTANT => true, self::CAPABILITY_COLLECTION_FETCH => true, self::CAPABILITY_COLLECTION_CREATE => true, self::CAPABILITY_COLLECTION_UPDATE => true, self::CAPABILITY_COLLECTION_DELETE => true, self::CAPABILITY_ENTITY_LIST => true, self::CAPABILITY_ENTITY_LIST_FILTER => [ self::CAPABILITY_ENTITY_FILTER_ALL => 's:200:256:256', ], self::CAPABILITY_ENTITY_LIST_SORT => [ self::CAPABILITY_ENTITY_SORT_LABEL ], self::CAPABILITY_ENTITY_LIST_RANGE => [ 'tally' => ['absolute', 'relative'] ], self::CAPABILITY_ENTITY_DELTA => true, self::CAPABILITY_ENTITY_EXTANT => true, self::CAPABILITY_ENTITY_FETCH => true, ]; private readonly RemoteFilesService $remoteService; public function __construct( ) {} private function initialize(): void { if (!isset($this->remoteService)) { $client = RemoteService::freshClient($this); $this->remoteService = RemoteService::documentsService($client); } } public function toStore(): array { return array_filter([ 'tid' => $this->serviceTenantId, 'uid' => $this->serviceUserId, 'sid' => $this->serviceIdentifier, 'label' => $this->serviceLabel, 'enabled' => $this->serviceEnabled, 'debug' => $this->serviceDebug, 'location' => $this->location?->toStore(), 'identity' => $this->identity?->toStore(), 'auxiliary' => $this->auxiliary, ], fn($v) => $v !== null); } public function fromStore(array $data): static { $this->serviceTenantId = $data['tid'] ?? null; $this->serviceUserId = $data['uid'] ?? null; $this->serviceIdentifier = $data['sid']; $this->serviceLabel = $data['label'] ?? ''; $this->serviceEnabled = $data['enabled'] ?? false; $this->serviceDebug = $data['debug'] ?? false; if (isset($data['location'])) { $this->location = (new ServiceLocation())->fromStore($data['location']); } if (isset($data['identity'])) { $this->identity = (new ServiceIdentityBasic())->fromStore($data['identity']); } if (isset($data['auxiliary']) && is_array($data['auxiliary'])) { $this->auxiliary = $data['auxiliary']; } return $this; } public function jsonSerialize(): array { return array_filter([ self::JSON_PROPERTY_TYPE => self::JSON_TYPE, self::JSON_PROPERTY_PROVIDER => self::PROVIDER_IDENTIFIER, self::JSON_PROPERTY_IDENTIFIER => $this->serviceIdentifier, self::JSON_PROPERTY_LABEL => $this->serviceLabel, self::JSON_PROPERTY_ENABLED => $this->serviceEnabled, self::JSON_PROPERTY_CAPABILITIES => $this->serviceAbilities, self::JSON_PROPERTY_LOCATION => $this->location?->jsonSerialize(), self::JSON_PROPERTY_IDENTITY => $this->identity?->jsonSerialize(), self::JSON_PROPERTY_AUXILIARY => $this->auxiliary, ], fn($v) => $v !== null); } public function jsonDeserialize(array|string $data): static { if (is_string($data)) { $data = json_decode($data, true, 512, JSON_THROW_ON_ERROR); } if (isset($data[self::JSON_PROPERTY_LABEL])) { $this->setLabel($data[self::JSON_PROPERTY_LABEL]); } if (isset($data[self::JSON_PROPERTY_ENABLED])) { $this->setEnabled($data[self::JSON_PROPERTY_ENABLED]); } if (isset($data[self::JSON_PROPERTY_LOCATION])) { $this->setLocation($this->freshLocation(null, $data[self::JSON_PROPERTY_LOCATION])); } if (isset($data[self::JSON_PROPERTY_IDENTITY])) { $this->setIdentity($this->freshIdentity(null, $data[self::JSON_PROPERTY_IDENTITY])); } if (isset($data[self::JSON_PROPERTY_AUXILIARY]) && is_array($data[self::JSON_PROPERTY_AUXILIARY])) { $this->setAuxiliary($data[self::JSON_PROPERTY_AUXILIARY]); } return $this; } public function capable(string $value): bool { return isset($this->serviceAbilities[$value]); } public function capabilities(): array { return $this->serviceAbilities; } public function provider(): string { return self::PROVIDER_IDENTIFIER; } public function identifier(): string|int { return $this->serviceIdentifier; } public function getLabel(): string|null { return $this->serviceLabel; } public function setLabel(string $label): static { $this->serviceLabel = $label; return $this; } public function getEnabled(): bool { return $this->serviceEnabled; } public function setEnabled(bool $enabled): static { $this->serviceEnabled = $enabled; return $this; } public function getLocation(): ServiceLocation { return $this->location; } public function setLocation(ResourceServiceLocationInterface $location): static { $this->location = $location; return $this; } public function freshLocation(string|null $type = null, array $data = []): ServiceLocation { $location = new ServiceLocation(); $location->jsonDeserialize($data); return $location; } public function getIdentity(): ServiceIdentityBasic { return $this->identity; } public function setIdentity(ResourceServiceIdentityInterface $identity): static { $this->identity = $identity; return $this; } public function freshIdentity(string|null $type, array $data = []): ServiceIdentityBasic { $identity = new ServiceIdentityBasic(); $identity->jsonDeserialize($data); return $identity; } public function getDebug(): bool { return $this->serviceDebug; } public function setDebug(bool $debug): static { $this->serviceDebug = $debug; return $this; } public function getAuxiliary(): array { return $this->auxiliary; } public function setAuxiliary(array $auxiliary): static { $this->auxiliary = $auxiliary; return $this; } // Collection operations public function collectionList(string|int|null $location, ?IFilter $filter = null, ?ISort $sort = null): array { $this->initialize(); $collections = $this->remoteService->collectionList($location, $filter, $sort); foreach ($collections as &$collection) { if (is_array($collection) && isset($collection['id'])) { $object = new CollectionResource(provider: $this->provider(), service: $this->identifier()); $object->fromJmap($collection); $collection = $object; } } return $collections; } public function collectionListFilter(): Filter { return new Filter($this->serviceAbilities[self::CAPABILITY_COLLECTION_LIST_FILTER] ?? []); } public function collectionListSort(): Sort { return new Sort($this->serviceAbilities[self::CAPABILITY_COLLECTION_LIST_SORT] ?? []); } public function collectionExtant(string|int|null $location, string|int ...$identifiers): array { $this->initialize(); return $this->remoteService->collectionExtant($location, ...$identifiers); } public function collectionFetch(string|int|null $identifier): ?CollectionBaseInterface { $this->initialize(); $collection = $this->remoteService->collectionFetch($identifier); if (is_array($collection) && isset($collection['id'])) { $object = new CollectionResource(provider: $this->provider(), service: $this->identifier()); $object->fromJmap($collection); $collection = $object; } return $collection; } public function collectionFresh(): CollectionMutableInterface { return new CollectionResource(provider: $this->provider(), service: $this->identifier()); } public function collectionCreate(string|int|null $location, CollectionMutableInterface $collection, array $options = []): CollectionBaseInterface { $this->initialize(); if ($collection instanceof CollectionResource === false) { $object = new CollectionResource(provider: $this->provider(), service: $this->identifier()); $object->jsonDeserialize($collection->jsonSerialize()); $collection = $object; } $collection = $collection->toJmap(); $collection = $this->remoteService->collectionCreate($location, $collection, $options); $object = new CollectionResource(provider: $this->provider(), service: $this->identifier()); $object->fromJmap($collection); return $object; } public function collectionUpdate(string|int $identifier, CollectionMutableInterface $collection): CollectionBaseInterface { $this->initialize(); if ($collection instanceof CollectionResource === false) { $object = new CollectionResource(provider: $this->provider(), service: $this->identifier()); $object->jsonDeserialize($collection->jsonSerialize()); $collection = $object; } $collection = $collection->toJmap(); $collection = $this->remoteService->collectionModify($identifier, $collection); $object = new CollectionResource(provider: $this->provider(), service: $this->identifier()); $object->fromJmap($collection); return $object; } public function collectionDelete(string|int $identifier, bool $force = false, bool $recursive = false): bool { $this->initialize(); return $this->remoteService->collectionDestroy($identifier, $force, $recursive) !== null; } public function collectionMove(string|int $identifier, string|int|null $targetLocation): CollectionBaseInterface { // TODO: Implement collection move $this->initialize(); $collection = new CollectionResource(provider: $this->provider(), service: $this->identifier()); return $collection; } // Entity operations public function entityList(string|int|null $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array { $this->initialize(); $result = $this->remoteService->entityList($collection, $filter, $sort, $range, $properties); $list = []; foreach ($result['list'] as $index => $entry) { if (is_array($entry) && isset($entry['id'])) { $object = new EntityResource(provider: $this->provider(), service: $this->identifier()); $object->fromJmap($entry); $list[$object->identifier()] = $object; } unset($result['list'][$index]); } return $list; } public function entityListStream(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): Generator { $this->initialize(); $result = $this->remoteService->entityList($collection, $filter, $sort, $range, $properties); foreach ($result['list'] as $index => $entry) { if (is_array($entry) && isset($entry['id'])) { $object = new EntityResource(provider: $this->provider(), service: $this->identifier()); $object->fromJmap($entry); yield $object; } unset($result['list'][$index]); } } public function entityListFilter(): Filter { return new Filter($this->serviceAbilities[self::CAPABILITY_ENTITY_LIST_FILTER] ?? []); } public function entityListSort(): Sort { return new Sort($this->serviceAbilities[self::CAPABILITY_ENTITY_LIST_SORT] ?? []); } public function entityListRange(RangeType $type): IRange { return new Range(); } public function entityDelta(string|int|null $collection, string $signature, string $detail = 'ids'): Delta { $this->initialize(); return $this->remoteService->entityDelta($collection, $signature, $detail); } public function entityExtant(string|int|null $collection, string|int ...$identifiers): array { $this->initialize(); return $this->remoteService->entityExtant(...$identifiers); } public function entityFetch(string|int|null $collection, string|int ...$identifiers): array { $this->initialize(); $entities = $this->remoteService->entityFetch(...$identifiers); foreach ($entities as &$entity) { if (is_array($entity) && isset($entity['id'])) { $object = new EntityResource(provider: $this->provider(), service: $this->identifier()); $object->fromJmap($entity); $entity = $object; } } return $entities; } public function entityRead(string|int|null $collection, string|int $identifier): ?string { return null; } // Node operations public function nodeList(string|int|null $collection, bool $recursive = false, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array { return []; } public function nodeListFilter(): IFilter { return new Filter(['']); } public function nodeListSort(): ISort { return new Sort(['']); } public function nodeDelta(string|int|null $collection, string $signature, string $detail = 'ids'): Delta { return new Delta(); } }