<?php

declare(strict_types=1);

namespace PpPhp\Modules\Events\Controllers;

use PpPhp\Core\Config;
use PpPhp\Core\ModuleManager;
use PpPhp\Core\DB;
use PpPhp\Core\Security;
use PpPhp\Core\ProjectNav;
use PpPhp\Core\ModuleState;
use PpPhp\Core\Events\CustomButtonEvents;
use PpPhp\Core\Events\EventCatalogue;
use PpPhp\Core\Events\GlobalEvent;
use PpPhp\Core\Events\TableEvent;

/**
 * Events module — visual event designer.
 *
 * Persists per-project event code (PHP/JS snippets) keyed by scope
 * (global.auth.beforeLogin, tables.customers.page.beforeAddSave, etc.)
 * in config/events.json via ModuleState.
 */
class EventsController
{
    private array $project = [];
    private string $slug   = '';

    public function __construct(
        private Config $config,
        private ModuleManager $moduleManager
    ) {}

    public function index(string $slug): string
    {
        // Events are table-record hooks — meaningless on a database-free app.
        // A direct hit (bookmark/old link) would render blank, so send the user
        // to the Designer where standalone pages are actually built.
        if (\PpPhp\Core\ProjectDatabaseProfile::isStandaloneSlug($slug)) {
            header('Location: ' . $this->config->get('base_url') . '/project/' . urlencode($slug) . '/designer');
            exit;
        }
        $this->initProject($slug);

        $project    = $this->project;
        $navItems   = ProjectNav::items('/events');
        $baseUrl    = $this->config->get('base_url');
        $assetsUrl  = $this->config->get('assets_url');
        $user       = $_SESSION['user'];
        $pageTitle  = 'Events';

        $tables       = $this->loadTables();
        $events       = ModuleState::load($slug, 'events');
        $globalEvent  = new GlobalEvent();
        $tableEvent   = new TableEvent('');
        $globalGroups  = $globalEvent->groups();
        $tableGroups   = $tableEvent->groups();
        $fieldGroups   = $tableEvent->fieldGroups();
        $tableSections  = EventCatalogue::tableSections();   // page-wise display grouping
        $globalSections = EventCatalogue::globalSections();  // grouped global-event headers

        ob_start();
        include __DIR__ . '/../Views/index.php';
        return ob_get_clean();
    }

    public function load(string $slug): void
    {
        header('Content-Type: application/json');
        $this->initProject($slug);
        echo json_encode([
            'success' => true,
            'events'  => ModuleState::load($slug, 'events'),
        ]);
        exit;
    }

    public function save(string $slug): void
    {
        header('Content-Type: application/json');
        $this->initProject($slug);
        Security::RequireCsrf();

        $input = json_decode(file_get_contents('php://input'), true) ?? [];
        $state = ModuleState::load($slug, 'events');

        if (isset($input['events']) && is_array($input['events'])) {
            $state = $this->deepMerge($state, $input['events']);
        }

        ModuleState::save($slug, 'events', $state);

        // Mirror any Custom Button handler edited here back onto the matching
        // Designer component, so button logic stays identical wherever it is
        // authored. Guarded — a bridge hiccup never fails the events save.
        try {
            $designer = ModuleState::load($slug, 'designer') ?: [];
            if ($designer !== []) {
                [$updated, $changed] = CustomButtonEvents::applyEventsToDesigner($state, $designer);
                if ($changed) {
                    ModuleState::save($slug, 'designer', $updated);
                }
            }
        } catch (\Throwable $e) {
            // non-fatal
        }

        echo json_encode(['success' => true, 'events' => $state]);
        exit;
    }

    public function fields(string $slug): void
    {
        header('Content-Type: application/json');
        $this->initProject($slug);

        $table = preg_replace('/[^a-zA-Z0-9_]/', '', $_GET['table'] ?? '');
        if ($table === '') {
            echo json_encode(['success' => false, 'message' => 'No table specified']);
            exit;
        }

        echo json_encode(['success' => true, 'fields' => $this->tableFields($table)]);
        exit;
    }

    /**
     * Return the selected event contract and context-aware editor completions.
     */
    public function intellisense(string $slug): void
    {
        header('Content-Type: application/json');
        $this->initProject($slug);

        $scope = strtolower((string) ($_GET['scope'] ?? 'global'));
        $group = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($_GET['group'] ?? ''));
        $event = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($_GET['event'] ?? ''));
        $language = strtolower((string) ($_GET['lang'] ?? 'php'));

        if (!in_array($language, [EventCatalogue::LANG_PHP, EventCatalogue::LANG_JS], true)) {
            $language = EventCatalogue::LANG_PHP;
        }

        if ($scope === 'global') {
            $handler = new GlobalEvent();
            $context = $handler->describe($group, $event, $language);
            $suggestions = $handler->intellisense($group, $event, $language);
        } else {
            $table = preg_replace('/[^a-zA-Z0-9_]/', '', (string) ($_GET['table'] ?? $scope));
            $field = preg_replace('/[^a-zA-Z0-9_]/', '', (string) ($_GET['field'] ?? ''));
            $fieldGroup = preg_replace('/[^a-zA-Z0-9_-]/', '', (string) ($_GET['fieldGroup'] ?? ''));

            if ($table === '') {
                http_response_code(400);
                echo json_encode(['success' => false, 'message' => 'Table is required for table event IntelliSense.']);
                exit;
            }

            $handler = new TableEvent($table, $this->tableFields($table));
            $context = $handler->describe(
                $group,
                $event,
                $language,
                $field !== '' ? $field : null,
                $fieldGroup !== '' ? $fieldGroup : null
            );
            $suggestions = $handler->intellisense(
                $group,
                $event,
                $language,
                $field !== '' ? $field : null,
                $fieldGroup !== '' ? $fieldGroup : null
            );
        }

        echo json_encode([
            'success' => true,
            'context' => $context,
            'suggestions' => $suggestions,
        ]);
        exit;
    }

    /**
     * Phase-1 code checker: syntax + "did you mean" for an event body.
     * PHP is linted server-side (only PHP can parse PHP); JS is linted in the
     * browser, so a lang=js request simply returns no server diagnostics.
     */
    public function lint(string $slug): void
    {
        header('Content-Type: application/json');
        $this->initProject($slug);

        $input = json_decode(file_get_contents('php://input'), true) ?? [];
        $code = (string) ($input['code'] ?? '');
        $lang = strtolower((string) ($input['lang'] ?? 'php'));
        $vars = [];
        foreach ((array) ($input['vars'] ?? []) as $v) {
            $v = (string) $v;
            // Only well-formed $variable names — never trust the body blindly.
            if (preg_match('/^\$[A-Za-z_][A-Za-z0-9_]*$/', $v)) {
                $vars[] = $v;
            }
        }

        // Guard against a runaway body — the parser is fast but bound it anyway.
        if (strlen($code) > 200000) {
            echo json_encode(['success' => true, 'diagnostics' => []]);
            exit;
        }

        $diagnostics = $lang === EventCatalogue::LANG_PHP
            ? \PpPhp\Core\Events\EventLinter::php($code, $vars)
            : [];

        echo json_encode(['success' => true, 'diagnostics' => $diagnostics]);
        exit;
    }

    /**
     * Pre-deploy sweep: run the checker over EVERY saved event in the project
     * and return a grouped report so mistakes surface before a build.
     */
    public function scan(string $slug): void
    {
        header('Content-Type: application/json');
        $this->initProject($slug);
        echo json_encode(['success' => true] + \PpPhp\Core\Events\EventScanner::scan($slug));
        exit;
    }

    private function loadTables(): array
    {
        $tablesState = ModuleState::load($this->slug, 'tables');
        $selected    = $tablesState['selectedTables'] ?? [];

        if (empty($selected)) {
            $rows   = DB::Query('SHOW FULL TABLES')->fetchAll(\PDO::FETCH_NUM);
            $tables = [];
            foreach ($rows as $row) {
                $tables[] = ['name' => $row[0], 'type' => ($row[1] === 'VIEW') ? 'VIEW' : 'TABLE'];
            }
            usort($tables, fn($a, $b) => $a['name'] <=> $b['name']);
            return $tables;
        }

        return array_map(fn($n) => ['name' => $n, 'type' => 'TABLE'], $selected);
    }

    /** @return array<int, string> */
    private function tableFields(string $table): array
    {
        $columns = DB::FetchAll("SHOW COLUMNS FROM `{$table}`");
        if (!is_array($columns)) {
            return [];
        }

        return array_values(array_filter(
            array_map('strval', array_column($columns, 'Field')),
            static fn(string $field): bool => $field !== ''
        ));
    }

    private function deepMerge(array $base, array $patch): array
    {
        foreach ($patch as $k => $v) {
            if (is_array($v) && isset($base[$k]) && is_array($base[$k]) && !array_is_list($v)) {
                $base[$k] = $this->deepMerge($base[$k], $v);
            } else {
                $base[$k] = $v;
            }
        }
        return $base;
    }

    private function initProject(string $slug): void
    {
        Security::RequireAuth();
        DB::Init();

        $project = DB::Select('projects', ['slug' => $slug, 'created_by' => Security::CurrentUserData()['id']]);
        if (empty($project) && Security::IsAdmin()) {
            $project = DB::Select('projects', ['slug' => $slug]);
        }
        if (empty($project)) {
            http_response_code(404);
            echo 'Project not found.';
            exit;
        }

        $this->project = $project[0];
        $this->slug    = $slug;
        DB::Init($slug);
        DB::SetConnection($slug);
    }
}
