<?php

namespace MintyPHP\Tests\Architecture;

use PHPUnit\Framework\TestCase;

/**
 * Every query key declared in a list's `filter-schema.php` must also appear
 * in the `toolbar` section (either as a visible field or hidden). Reason:
 *
 * - Backend reads filter values via `gridParseFiltersFromSchemaFile()`, which
 *   parses against the `query` schema.
 * - Frontend's Grid.js URL-sync only forwards params that are declared in the
 *   `toolbar` section (derived via `gridSchemaClientFilters()`). If a query
 *   param is not in `toolbar`, Grid.js drops it on every subsequent request
 *   (pagination, sort, filter change) — the filter appears to work on page
 *   load but silently breaks after any user interaction.
 *
 * This test ran us into real production pain before it existed: the address
 * book's tenant-register tabs worked on first load but lost the filter on
 * every grid refetch. Declaring `tenant` as a hidden toolbar field fixed it.
 * This test prevents repeat occurrences.
 *
 * Exempted keys (framework-provided, always preserved by Grid.js):
 *   limit, offset, order, dir
 */
class FilterSchemaConsistencyTest extends TestCase
{
    use ProjectFileAssertionSupport;

    private const FRAMEWORK_KEYS = ['limit', 'offset', 'order', 'dir'];

    public function testEveryQueryKeyHasToolbarDeclaration(): void
    {
        $root = $this->projectRootPath();
        $schemaFiles = [];
        $this->collectFilterSchemas($root . '/pages', $schemaFiles);
        $this->collectFilterSchemas($root . '/modules', $schemaFiles);

        $this->assertNotEmpty($schemaFiles, 'No filter-schema.php files found — regex stale?');

        $violations = [];

        foreach ($schemaFiles as $schemaFile) {
            $schema = @include $schemaFile;
            if (!is_array($schema)) {
                $violations[] = sprintf('%s — filter-schema.php did not return an array', $this->relative($schemaFile, $root));
                continue;
            }

            $queryKeys = array_keys(is_array($schema['query'] ?? null) ? $schema['query'] : []);
            $toolbarFields = is_array($schema['toolbar'] ?? null) ? $schema['toolbar'] : [];

            // Schemas without a toolbar are for non-Grid endpoints (e.g. global
            // search data endpoint consumed by a custom JS dialog). Skip those —
            // they never participate in Grid.js URL sync.
            if ($toolbarFields === []) {
                continue;
            }

            $toolbarKeys = [];
            foreach ($toolbarFields as $field) {
                if (!is_array($field)) {
                    continue;
                }
                $key = trim((string) ($field['key'] ?? ''));
                if ($key !== '') {
                    $toolbarKeys[] = $key;
                }
            }

            $missing = array_diff($queryKeys, $toolbarKeys, self::FRAMEWORK_KEYS);
            if ($missing) {
                $violations[] = sprintf(
                    "%s — query keys missing from toolbar: %s.\n  Add them to the 'toolbar' section (type 'hidden' is fine) so Grid.js forwards them on refetch.",
                    $this->relative($schemaFile, $root),
                    implode(', ', $missing)
                );
            }
        }

        $this->assertSame(
            [],
            $violations,
            "Filter-schema consistency violations:\n" . implode("\n\n", $violations)
        );
    }

    /**
     * @param list<string> &$out
     */
    private function collectFilterSchemas(string $base, array &$out): void
    {
        if (!is_dir($base)) {
            return;
        }
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($base, \FilesystemIterator::SKIP_DOTS));
        /** @var \SplFileInfo $file */
        foreach ($iterator as $file) {
            if ($file->isFile() && $file->getFilename() === 'filter-schema.php') {
                $out[] = $file->getPathname();
            }
        }
    }

    private function relative(string $path, string $root): string
    {
        return str_replace($root . '/', '', $path);
    }
}
