<?php

declare(strict_types=1);

/**
 * APO-193 Card 6 — Service-layer for site-level Custom CSS authoring.
 *
 * Per-section custom CSS rides on section data (SectionVariantRepository);
 * this service handles the site-wide custom CSS layer.
 */
final class MyronCustomCssActionService
{
    public function __construct(
        private readonly MyronCustomCssRepository $repository,
        private readonly ?MyronAiIntegrationRepository $aiIntegrations = null,
        private readonly ?MyronAuthRepository $authRepository = null,
    ) {
    }

    /**
     * Save the site-wide custom CSS layer.
     *
     * @return array{bytes: int}
     */
    public function saveSiteCss(string $css, MyronActionCallerContext $caller): array
    {
        $this->repository->saveSiteCss($css, $this->callerUserId($caller));
        return ['bytes' => strlen($css)];
    }

    /**
     * Save operator-declared creative dependencies (external JS libraries).
     *
     * @param list<array{name: string, cdnUrl: string, sri: string}> $deps
     * @return array{count: int}
     */
    public function saveCreativeDeps(array $deps, MyronActionCallerContext $caller): array
    {
        $this->repository->saveCreativeDeps($deps, $this->callerUserId($caller));
        return ['count' => count($this->repository->creativeDepsSetting())];
    }

    /**
     * @param list<array<string, mixed>> $externalLibraries
     * @return array{externalLibraries: int, uploadedLibraries: int, customJsBytes: int}
     */
    public function saveCustomInteractions(array $externalLibraries, string $customJs, MyronActionCallerContext $caller): array
    {
        $userId = $this->callerUserId($caller);
        $this->repository->saveExternalLibraries($externalLibraries, $userId);
        $this->repository->saveCustomJs($customJs, $userId);
        $state = $this->repository->customInteractions();

        return [
            'externalLibraries' => count($state['externalLibraries']),
            'uploadedLibraries' => count($state['uploadedLibraries']),
            'customJsBytes' => strlen($state['customJs']),
        ];
    }

    /**
     * @param list<array<string, mixed>> $externalFonts
     * @param list<array<string, mixed>>|null $uploadedFonts
     * @return array{externalFonts: int, uploadedFonts: int}
     */
    public function saveCustomInteractionFonts(array $externalFonts, ?array $uploadedFonts, MyronActionCallerContext $caller): array
    {
        $userId = $this->callerUserId($caller);
        $this->repository->saveExternalFontStylesheets($externalFonts, $userId);
        if ($uploadedFonts !== null) {
            $this->repository->saveUploadedFonts($uploadedFonts, $userId);
        }
        $state = $this->repository->customInteractions();

        return [
            'externalFonts' => count($state['externalFonts']),
            'uploadedFonts' => count($state['uploadedFonts']),
        ];
    }

    /**
     * @param array<string, mixed> $uploadedFile
     * @return array<string, mixed>
     */
    public function uploadInteractionLibraryFromMultipart(array $uploadedFile, string $label, MyronActionCallerContext $caller): array
    {
        return $this->repository->uploadInteractionLibrary($uploadedFile, $this->callerUserId($caller), $label);
    }

    /**
     * @param array<string, mixed> $envelope
     * @return array<string, mixed>
     */
    public function uploadInteractionLibraryFromEnvelope(array $envelope, string $label, MyronActionCallerContext $caller): array
    {
        $decoder = new MyronJsonContentEnvelope(
            dirname(__DIR__, 2) . '/tmp/uploads',
            ['application/javascript', 'text/javascript', 'application/x-javascript', 'text/plain'],
            MyronCustomCssRepository::INTERACTION_LIBRARY_MAX_BYTES
        );
        $decoded = $decoder->decode($envelope);
        try {
            return $this->repository->uploadInteractionLibrary($decoded->asUploadedFileArray(), $this->callerUserId($caller), $label);
        } finally {
            $decoded->cleanup();
        }
    }

    /**
     * @return array{libraryId:string,deleted:bool,storedFilename:string,fileDeleted:bool,fileMissing:bool}
     */
    public function deleteInteractionLibrary(string $libraryId, MyronActionCallerContext $caller): array
    {
        return $this->repository->deleteInteractionLibrary($libraryId, $this->callerUserId($caller));
    }

    /**
     * @param array<string, mixed> $uploadedFile
     * @return array<string, mixed>
     */
    public function uploadInteractionFontFromMultipart(
        array $uploadedFile,
        string $label,
        string $familyName,
        string $weight,
        string $style,
        string $display,
        MyronActionCallerContext $caller
    ): array {
        return $this->repository->uploadInteractionFont(
            $uploadedFile,
            $this->callerUserId($caller),
            $label,
            $familyName,
            $weight,
            $style,
            $display
        );
    }

    /**
     * @param array<string, mixed> $envelope
     * @return array<string, mixed>
     */
    public function uploadInteractionFontFromEnvelope(
        array $envelope,
        string $label,
        string $familyName,
        string $weight,
        string $style,
        string $display,
        MyronActionCallerContext $caller
    ): array {
        $decoder = new MyronJsonContentEnvelope(
            dirname(__DIR__, 2) . '/tmp/uploads',
            ['font/woff2', 'font/woff', 'font/ttf', 'font/otf', 'application/font-woff', 'application/font-woff2', 'application/octet-stream'],
            MyronCustomCssRepository::FONT_FILE_MAX_BYTES
        );
        $decoded = $decoder->decode($envelope);
        try {
            return $this->repository->uploadInteractionFont(
                $decoded->asUploadedFileArray(),
                $this->callerUserId($caller),
                $label,
                $familyName,
                $weight,
                $style,
                $display
            );
        } finally {
            $decoded->cleanup();
        }
    }

    /**
     * @return array{fontId:string,deleted:bool,storedFilename:string,fileDeleted:bool,fileMissing:bool,remainingFonts:int}
     */
    public function deleteInteractionFont(string $fontId, MyronActionCallerContext $caller): array
    {
        return $this->repository->deleteInteractionFont($fontId, $this->callerUserId($caller));
    }

    private function callerUserId(MyronActionCallerContext $caller): string
    {
        if ($caller->callerType === 'human-session') {
            return $caller->callerId;
        }

        if ($caller->callerType === 'ai-bearer') {
            if ($caller->callerId === 'dev-token') {
                return '';
            }
            if ($this->aiIntegrations === null || $this->authRepository === null) {
                return '';
            }

            $integration = $this->aiIntegrations->findById($caller->callerId);
            if ($integration === null || $integration->isRevoked()) {
                return '';
            }

            $userId = trim($integration->createdByUserId);
            if ($userId === '') {
                return '';
            }

            $user = $this->authRepository->findUserById($userId);
            if (!is_array($user) || ($user['is_active'] ?? false) !== true) {
                return '';
            }

            return $userId;
        }

        return '';
    }
}
