<?php

namespace Tests\Support;

use ProPresenter\Parser\Slide;
use Rv\Data\Action\ActionType;
use Rv\Data\Action\LayerType;

/**
 * Test helpers for the COMBINED image rule:
 *
 *   - BACKGROUND service image / key-visual → media ACTION on the BACKGROUND
 *     layer. Assert via the parser API: $slide->hasBackgroundMedia() and
 *     $slide->getBackgroundMediaUrl().
 *   - UPLOADED content slide image → full-frame fill.media CONTENT element.
 *     Assert via fillMediaUrlAt()/fillMediaUrls()/isFullFrameAt() and the
 *     foreground-action absence via hasForegroundImageMediaAction() == false.
 *   - Text (songtext / name-tag) → text CONTENT element (elementHasTextAt()).
 *
 * Content elements are full-frame Graphics\Element fills whose z-order is the
 * base-slide element array order (index 0 = bottom layer): text first, the
 * uploaded foreground image appended on top. The background image is NOT an
 * element; it renders beneath the stack from its BACKGROUND media action.
 */
trait InspectsSlideFillMedia
{
    /**
     * Ordered base-slide content elements (z-order; index 0 = bottom layer).
     *
     * @return array<int, \Rv\Data\Slide\Element>
     */
    protected function baseSlideElements(Slide $slide): array
    {
        foreach ($slide->getCue()->getActions() as $action) {
            if ($action->getType() !== ActionType::ACTION_TYPE_PRESENTATION_SLIDE) {
                continue;
            }

            $baseSlide = $action->getSlide()?->getPresentation()?->getBaseSlide();
            if ($baseSlide === null) {
                return [];
            }

            $elements = [];
            foreach ($baseSlide->getElements() as $element) {
                $elements[] = $element;
            }

            return $elements;
        }

        return [];
    }

    /** Bundle-relative fill-media URL of the element at $index, or null when it has no media fill. */
    protected function fillMediaUrlAt(Slide $slide, int $index): ?string
    {
        $elements = $this->baseSlideElements($slide);
        $graphics = ($elements[$index] ?? null)?->getElement();
        $fill = $graphics?->getFill();

        if ($fill === null || ! $fill->hasMedia()) {
            return null;
        }

        return $fill->getMedia()?->getUrl()?->getAbsoluteString();
    }

    /**
     * Bounds [x, y, width, height] of the element at $index, or null.
     *
     * @return array{0: float, 1: float, 2: float, 3: float}|null
     */
    protected function fillBoundsAt(Slide $slide, int $index): ?array
    {
        $elements = $this->baseSlideElements($slide);
        $bounds = ($elements[$index] ?? null)?->getElement()?->getBounds();
        if ($bounds === null) {
            return null;
        }

        return [
            (float) ($bounds->getOrigin()?->getX() ?? 0.0),
            (float) ($bounds->getOrigin()?->getY() ?? 0.0),
            (float) ($bounds->getSize()?->getWidth() ?? 0.0),
            (float) ($bounds->getSize()?->getHeight() ?? 0.0),
        ];
    }

    /** True when the element at $index is a full-frame (0,0 → 1920×1080) fill. */
    protected function isFullFrameAt(Slide $slide, int $index): bool
    {
        return $this->fillBoundsAt($slide, $index) === [0.0, 0.0, 1920.0, 1080.0];
    }

    /**
     * All fill-media URLs on the slide, in z-order (bottom → top).
     *
     * @return array<int, string>
     */
    protected function fillMediaUrls(Slide $slide): array
    {
        $urls = [];
        foreach ($this->baseSlideElements($slide) as $element) {
            $fill = $element->getElement()?->getFill();
            if ($fill !== null && $fill->hasMedia()) {
                $urls[] = $fill->getMedia()?->getUrl()?->getAbsoluteString() ?? '';
            }
        }

        return $urls;
    }

    /** Whether any base-slide element carries a fill-media image with the given URL. */
    protected function hasFillMediaUrl(Slide $slide, string $url): bool
    {
        return in_array($url, $this->fillMediaUrls($slide), true);
    }

    /** True while the element at $index carries text (used to assert text sits above the background). */
    protected function elementHasTextAt(Slide $slide, int $index): bool
    {
        $graphics = ($this->baseSlideElements($slide)[$index] ?? null)?->getElement();

        return $graphics !== null && $graphics->hasText();
    }

    /**
     * Whether the cue carries ANY image MEDIA action (either layer). Useful only
     * when no service background is configured; when a background IS present it
     * survives as a BACKGROUND action, so prefer hasForegroundImageMediaAction().
     */
    protected function hasImageMediaAction(Slide $slide): bool
    {
        foreach ($slide->getCue()->getActions() as $action) {
            if ($action->getType() !== ActionType::ACTION_TYPE_MEDIA) {
                continue;
            }

            $mediaElement = $action->getMedia()?->getElement();
            if ($mediaElement !== null && $mediaElement->hasImage()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Whether the cue still carries a FOREGROUND-layer image MEDIA action. Must be
     * false after conversion — uploaded images become content elements. A
     * BACKGROUND-layer action (service background / key-visual) is ignored here.
     */
    protected function hasForegroundImageMediaAction(Slide $slide): bool
    {
        foreach ($slide->getCue()->getActions() as $action) {
            if ($action->getType() !== ActionType::ACTION_TYPE_MEDIA) {
                continue;
            }

            $mediaType = $action->getMedia();
            $mediaElement = $mediaType?->getElement();
            if (
                $mediaElement !== null
                && $mediaElement->hasImage()
                && $mediaType->getLayerType() !== LayerType::LAYER_TYPE_BACKGROUND
            ) {
                return true;
            }
        }

        return false;
    }
}
