<?php

declare(strict_types=1);

final class MyronRedirectFallbackValidator
{
    public function __construct(
        private readonly PDO $pdo,
        private readonly DisabledPageRedirectsRepository $redirects,
    ) {
    }

    /**
     * @param list<string> $excludedSlugs
     */
    public function validate(string $fallbackUrl, array $excludedSlugs = []): string
    {
        $fallback = trim($fallbackUrl) ?: '/';
        if (preg_match('#^https?://#i', $fallback) === 1 || str_starts_with($fallback, '//')) {
            throw new InvalidArgumentException('Redirect fallback must be a local published page path.');
        }

        $path = myronNormalizePath($fallback);
        if ($path === '/') {
            return '/';
        }

        $excluded = array_fill_keys(array_map(static fn (string $slug): string => myronNormalizePath($slug), $excludedSlugs), true);
        if (isset($excluded[$path])) {
            throw new InvalidArgumentException('Redirect fallback cannot point at a page being disabled.');
        }
        if ($this->redirects->exists($path)) {
            throw new InvalidArgumentException('Redirect fallback cannot point at another disabled page redirect.');
        }

        $disabledFilter = $this->hasColumn('pages', 'disabled_by_bundle_removal_at')
            ? ' AND pages.disabled_by_bundle_removal_at IS NULL'
            : '';
        $statement = $this->pdo->prepare(
            'SELECT id
             FROM pages
             WHERE path = :path
               AND publication_state = \'published\'
               AND current_published_version_id IS NOT NULL'
             . $disabledFilter .
            ' LIMIT 1'
        );
        $statement->execute([':path' => $path]);
        if ($statement->fetchColumn() === false) {
            throw new InvalidArgumentException('Redirect fallback must point at the site root or a currently published page.');
        }

        return $path;
    }

    /**
     * @param array<string, mixed> $redirectMap
     * @param list<string> $disabledSlugs
     * @return array{default_fallback_url:string, per_slug_overrides:array<string,string>}
     */
    public function validateMap(array $redirectMap, array $disabledSlugs): array
    {
        $default = $this->validate((string) ($redirectMap['default_fallback_url'] ?? '/'), $disabledSlugs);
        $overrides = [];
        $rawOverrides = is_array($redirectMap['per_slug_overrides'] ?? null) ? $redirectMap['per_slug_overrides'] : [];
        foreach ($rawOverrides as $slug => $fallback) {
            $normalizedSlug = myronNormalizePath((string) $slug);
            if (!in_array($normalizedSlug, $disabledSlugs, true)) {
                continue;
            }
            $overrides[$normalizedSlug] = $this->validate((string) $fallback, $disabledSlugs);
        }

        return [
            'default_fallback_url' => $default,
            'per_slug_overrides' => $overrides,
        ];
    }

    private function hasColumn(string $table, string $column): bool
    {
        $driver = (string) $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
        if ($driver === 'sqlite') {
            $statement = $this->pdo->query('PRAGMA table_info(' . $table . ')');
            foreach (($statement ? $statement->fetchAll(PDO::FETCH_ASSOC) : []) as $row) {
                if ((string) ($row['name'] ?? '') === $column) {
                    return true;
                }
            }
            return false;
        }

        $statement = $this->pdo->prepare(
            'SELECT COUNT(*)
             FROM information_schema.columns
             WHERE table_schema = DATABASE()
               AND table_name = :table
               AND column_name = :column'
        );
        $statement->execute([':table' => $table, ':column' => $column]);
        return (int) $statement->fetchColumn() > 0;
    }
}

function myronValidateRedirectFallback(string $url): bool
{
    try {
        myronRedirectFallbackValidator()->validate($url);
        return true;
    } catch (Throwable) {
        return false;
    }
}
