/*
 * Copyright 2026 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "tests/Test.h"

#include "include/core/SkBlendMode.h"
#include "src/gpu/SkBackingFit.h"
#include "src/gpu/graphite/ContextPriv.h"
#include "src/gpu/graphite/ContextUtils.h"
#include "src/gpu/graphite/Device.h"
#include "src/gpu/graphite/Renderer.h"
#include "src/gpu/graphite/TextureInfoPriv.h"
#include "src/gpu/graphite/TextureProxyView.h"

using namespace skgpu;
using namespace skgpu::graphite;

// These are unit tests that ensure the secondary "inner fill" draw is recorded when possible.
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(InnerFillTest, reporter, context,
                                   CtsEnforcement::kNextRelease) {

    std::unique_ptr<Recorder> recorder = context->makeRecorder();
    sk_sp<Device> device = Device::Make(recorder.get(),
                                        SkImageInfo::Make(512, 512,
                                                          kRGBA_8888_SkColorType,
                                                          kPremul_SkAlphaType),
                                        Budgeted::kYes,
                                        Mipmapped::kNo,
                                        SkBackingFit::kExact,
                                        /*surfaceProps=*/{},
                                        LoadOp::kClear,
                                        "inner-fill");

    // Default rrect size is sufficient to avoid the small-size exclusion criteria
    auto testRRect = [&](const SkPaint& paint, bool innerFillExpected, float rrectSize = 128.f) {
        SkRRect rrect = SkRRect::MakeRectXY(SkRect::MakeXYWH(0.f, 0.f, rrectSize, rrectSize),
                                            0.1f * rrectSize, 0.1f * rrectSize);
        int initialRenderSteps = device->testingOnly_pendingRenderSteps();
        device->drawRRect(rrect, paint);
        int actualRenderSteps = device->testingOnly_pendingRenderSteps();

        if (actualRenderSteps == 1) {
            // This either represents the first draw of the tests, or it represents the draw after
            // a dst-read texture copy flush (which should only happen if we are not expecting an
            // inner fill draw as well).
            SkASSERT(initialRenderSteps == 0 || !innerFillExpected);
            initialRenderSteps = 0;
        }

        // TODO(michaelludwig): After migrating to the layer draw tracking system, having a way to
        // traverse the recorded draws and inspect them could let these checks be more robust, e.g.
        // one of the steps is actually the CoverBoundsRenderStep AND it's in the front.
        int expectedRenderSteps = initialRenderSteps + (innerFillExpected ? 2 : 1);
        REPORTER_ASSERT(reporter, actualRenderSteps == expectedRenderSteps,
                        "Expected (%d) vs Actual (%d)", expectedRenderSteps, actualRenderSteps);
    };

    const bool srcWithCoverageIsHW =
            CanUseHardwareBlending(context->priv().caps(),
                                   TextureInfoPriv::ViewFormat(device->target().proxy()->textureInfo()),
                                   SkBlendMode::kSrc,
                                   Coverage::kSingleChannel);

    // ** Test cases that should not produce an inner fill:
    // 1. A transparent paint, so the draw is not eligible for an inner fill
    {
        skiatest::ReporterContext label{reporter, "transparent paint"};
        SkPaint transparentPaint;
        transparentPaint.setAlphaf(0.5f);
        testRRect(transparentPaint, /*innerFillExpected=*/false);
    }
    // 2. A very small rounded rectangle that would otherwise have an inner fill
    {
        skiatest::ReporterContext label{reporter, "small rrect"};
        testRRect(SkPaint(), /*innerFillExpected=*/false, /*rrectSize=*/8.f);
    }
    // 3. An opaque paint that has a blend mode that still mixes with the dst
    {
        skiatest::ReporterContext label{reporter, "fancy blend"};
        SkPaint opaqueBlendedPaint;
        opaqueBlendedPaint.setBlendMode(SkBlendMode::kSrcIn);
        testRRect(opaqueBlendedPaint, /*innerFillExpected=*/false);
    }
    // 4. An opaque paint but with an analytic clip
    {
        skiatest::ReporterContext label{reporter, "analytic clip"};
        device->pushClipStack();
        device->clipShader(SkShaders::Color({0.1f, 0.2f, 0.3f, 0.5f}, nullptr),
                           SkClipOp::kIntersect);
        testRRect(SkPaint(), /*innerFillExpected=*/false);
        device->popClipStack();
    }
    // 5. A kSrc paint but the device requires shader blending since there's coverage
    if (!srcWithCoverageIsHW) {
        skiatest::ReporterContext label{reporter, "src w/o dual-src blending"};
        SkPaint srcPaint;
        srcPaint.setBlendMode(SkBlendMode::kSrc);
        testRRect(srcPaint, /*innerFillExpected=*/false);
    }

    // ** Test cases that should produce an inner fill:
    // 1. A kSrcOver paint with opaque color (or shader)
    {
        skiatest::ReporterContext label{reporter, "opaque src-over"};
        testRRect(SkPaint(), /*innerFillExpected=*/true);
    }
    // 2. A kSrc paint when the device supports HW blending regardless of coverage
    if (srcWithCoverageIsHW) {
        skiatest::ReporterContext label{reporter, "src w/ dual-src blending"};
        SkPaint srcPaint;
        srcPaint.setBlendMode(SkBlendMode::kSrc);
        srcPaint.setAlphaf(0.5f); // emphasize it's src color is not opaque
        testRRect(srcPaint, /*innerFillExpected=*/true);
    }

    // We don't actually care about rendering, so just throw everything away
}
