<?php
// ─────────────────────────────────────────────────────────────
//  api/index.php — REST API router
//  Routes:
//   POST   /api/auth/login
//   POST   /api/auth/logout
//   GET    /api/auth/me
//   GET    /api/forms               (admin)
//   POST   /api/forms               (admin)
//   GET    /api/forms/:id           (admin)
//   PUT    /api/forms/:id           (admin)
//   DELETE /api/forms/:id           (admin)
//   GET    /api/forms/:id/submissions  (admin)
//   GET    /api/submissions/:id     (admin)
//   DELETE /api/submissions/:id     (admin)
//   GET    /api/submissions/:id/pdf (admin)
//   GET    /api/stats               (admin)
//   POST   /api/submit/:slug        (public — form submission)
//   GET    /api/public/forms        (public — list active public forms)
//   GET    /api/public/forms/:slug  (public — form schema)
// ─────────────────────────────────────────────────────────────
require_once dirname(__DIR__) . '/lib.php';
cors();
session_start_safe();

/* Extract a human-readable display name from a submission data JSON blob.
   Tries common name/email keys, falls back to ref. */
function extract_display_name(string $data_json, string $ref = ''): string {
    $data = json_decode($data_json, true);
    if (!is_array($data)) return $ref;
    $name_keys  = ['name','full_name','fullname','your_name','contact_name','sender_name','client_name','applicant_name','student_name','customer_name'];
    foreach ($name_keys as $k) {
        $v = trim((string)($data[$k] ?? ''));
        if ($v && $v !== 'null') return $v;
    }
    // try first + last name combination
    $fn = trim((string)($data['first_name'] ?? $data['firstname'] ?? ''));
    $ln = trim((string)($data['last_name']  ?? $data['lastname']  ?? ''));
    if ($fn) return trim("$fn $ln");
    // try email as fallback identifier
    $email_keys = ['email','email_address','your_email','contact_email','e_mail'];
    foreach ($email_keys as $k) {
        $v = trim((string)($data[$k] ?? ''));
        if ($v && $v !== 'null' && filter_var($v, FILTER_VALIDATE_EMAIL)) return $v;
    }
    // last resort: first non-empty scalar value that looks like a name (< 60 chars, no newlines)
    foreach ($data as $v) {
        if (is_string($v) && strlen($v) < 60 && !str_contains($v, "\n") && $v !== 'null' && trim($v)) {
            return trim($v);
        }
    }
    return $ref;
}

$method = $_SERVER['REQUEST_METHOD'];
$uri    = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// Strip base path to get clean route segments
$base = rtrim(dirname(dirname($_SERVER['SCRIPT_NAME'])), '/');
$path = '/' . trim(substr($uri, strlen($base)), '/');
$segs = array_values(array_filter(explode('/', $path)));
// segs[0] = 'api', segs[1] = resource, segs[2] = id, segs[3] = sub-resource

array_shift($segs); // remove 'api'
$res  = $segs[0] ?? '';
$id   = $segs[1] ?? null;
$sub  = $segs[2] ?? null;

try {
    init_db();
    init_sys_db();

    // ── Module gating (single business) ───────────────────────
    $formsEnabled    = ModuleToggle::isEnabled('forms');
    $bookingsEnabled = ModuleToggle::isEnabled('bookings');

    // ── Auth ──────────────────────────────────────────────────
    if ($res === 'auth') {
        if ($id === 'login' && $method === 'POST') {
            $b      = body();
            $email  = strtolower(trim($b['email'] ?? ''));
            $pass   = $b['password'] ?? '';
            if (!$email || !$pass) json_out(['ok'=>false,'error'=>'Email and password are required'], 422);

            $stmt = db()->prepare('SELECT id, username, email, password_hash, role FROM fb_users WHERE email = ? AND is_active = 1 LIMIT 1');
            $stmt->execute([$email]);
            $user = $stmt->fetch();

            // Recovery fallback for legacy installs:
            // if fb_users has no matching row yet, allow login using ADMIN_EMAIL + ADMIN_PASSWORD
            // and auto-create the admin user record.
            if (!$user) {
                $legacyAdminEmail = defined('ADMIN_EMAIL') ? strtolower(trim((string)ADMIN_EMAIL)) : '';
                $legacyAdminPass  = defined('ADMIN_PASSWORD') ? (string)ADMIN_PASSWORD : '';
                if ($legacyAdminEmail && $legacyAdminPass && $email === $legacyAdminEmail && hash_equals($legacyAdminPass, $pass)) {
                    $username = 'Admin';
                    db()->prepare('INSERT INTO fb_users (username, email, password_hash, role, is_active) VALUES (?, ?, ?, "admin", 1)')
                        ->execute([$username, $email, password_hash($pass, PASSWORD_BCRYPT, ['cost'=>12])]);
                    $newId = (int)db()->lastInsertId();
                    $user = [
                        'id' => $newId,
                        'username' => $username,
                        'email' => $email,
                        'role' => 'admin',
                    ];
                }
            }

            if (!$user || !password_verify($pass, $user['password_hash'])) {
                json_out(['ok'=>false,'error'=>'Invalid email or password'], 401);
            }

            session_start_safe();
            session_regenerate_id(true);
            $_SESSION['admin_authed']  = true;
            $_SESSION['admin_time']    = time();
            $_SESSION['admin_user_id'] = (int)$user['id'];
            $_SESSION['admin_name']    = $user['username'];
            $_SESSION['admin_email']   = $user['email'];
            $_SESSION['admin_role']    = $user['role'];
            json_out(['ok'=>true,'user'=>['name'=>$user['username'],'email'=>$user['email'],'role'=>$user['role']]]);
        }
        if ($id === 'admin-forgot' && $method === 'POST') {
            init_db();
            $b = body();
            $email = strtolower(trim((string)($b['email'] ?? '')));
            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) json_out(['ok'=>false,'error'=>'Valid email required'], 422);

            // Always respond ok (avoid account enumeration)
            $stmt = db()->prepare('SELECT id, username, email FROM fb_users WHERE email=? AND is_active=1 LIMIT 1');
            $stmt->execute([$email]);
            $u = $stmt->fetch();
            if ($u) {
                $token = bin2hex(random_bytes(32));
                $exp   = date('Y-m-d H:i:s', time() + 3600);
                db()->prepare('UPDATE fb_users SET reset_token=?, reset_expires=? WHERE id=?')->execute([$token, $exp, $u['id']]);
                $url = rtrim(APP_URL,'/') . '/admin-reset.php?token=' . urlencode($token);
                $html = "<h2>Reset your admin password</h2><p>Click the link below to set a new password.</p><p><a href=\"$url\">Reset password</a></p><p>This link expires in 1 hour.</p>";
                $text = "Reset your admin password:\n$url\n\nExpires in 1 hour.";
                @send_mail($u['email'], $u['username'] ?: 'Admin', 'Reset your admin password', $html, $text);
            }
            json_out(['ok'=>true,'message'=>"If that admin email exists, you'll receive a reset link shortly."]);
        }
        if ($id === 'admin-reset' && $method === 'POST') {
            init_db();
            $b = body();
            $token = (string)($b['token'] ?? '');
            $pw    = (string)($b['password'] ?? '');
            if (strlen($token) < 10) json_out(['ok'=>false,'error'=>'Invalid token'], 422);
            if (strlen($pw) < 8) json_out(['ok'=>false,'error'=>'Password must be at least 8 characters'], 422);
            $stmt = db()->prepare('SELECT id FROM fb_users WHERE reset_token=? AND reset_expires > NOW() AND is_active=1 LIMIT 1');
            $stmt->execute([$token]);
            $u = $stmt->fetch();
            if (!$u) json_out(['ok'=>false,'error'=>'This reset link is invalid or expired'], 400);
            $hash = password_hash($pw, PASSWORD_BCRYPT, ['cost'=>12]);
            db()->prepare('UPDATE fb_users SET password_hash=?, reset_token=NULL, reset_expires=NULL WHERE id=?')->execute([$hash, $u['id']]);
            json_out(['ok'=>true,'message'=>'Password updated. You can now sign in.']);
        }
        if ($id === 'logout') { session_start_safe(); session_destroy(); json_out(['ok'=>true]); }
        if ($id === 'me') {
            $authed = admin_logged_in();
            json_out(['ok'=>true,'authed'=>$authed,'user'=>$authed ? [
                'name'  => $_SESSION['admin_name']  ?? 'Admin',
                'email' => $_SESSION['admin_email'] ?? '',
                'role'  => $_SESSION['admin_role']  ?? 'admin',
            ] : null]);
        }
    }

    // ── Customer auth (forwarded to api/auth.php) ───────────────
    // When .htaccess routes api/auth.php through the router, forward it here.
    // body() is cached so the read is safe to reuse in auth.php.
    if ($res === 'auth' && $method === 'POST' && isset(body()['action'])) {
        require_once __DIR__ . '/auth.php';
        exit;
    }

    // ── Public: list forms ────────────────────────────────────
    if ($res === 'public' && $id === 'forms' && $method === 'GET') {
        if (!$formsEnabled) json_out(['ok'=>false,'error'=>'module_disabled','module'=>'forms'], 404);
        if ($sub) {
            // single form schema by slug
            $slug = $sub;
            $form = db()->prepare('SELECT id,slug,title,description,fields,is_public,form_password,submit_label,success_url,submission_limit FROM fb_forms WHERE slug=? AND is_active=1');
            $form->execute([$slug]); $row = $form->fetch();
            if (!$row) json_out(['ok'=>false,'error'=>'Form not found'],404);
            $row['fields'] = json_decode($row['fields'],true) ?: [];
            // strip passwords from field options for security
            $row['requires_password'] = (bool)$row['form_password'];
            unset($row['form_password']);
            // Add submission count so frontend can show limit progress
            $cnt = db()->prepare('SELECT COUNT(*) FROM fb_submissions WHERE form_id=?');
            $cnt->execute([$row['id']]);
            $row['submission_count'] = (int)$cnt->fetchColumn();
            json_out(['ok'=>true,'form'=>$row]);
        }
        $rows = db()->query("SELECT slug,title,description,is_public FROM fb_forms WHERE is_active=1 AND is_public=1 ORDER BY id DESC")->fetchAll();
        json_out(['ok'=>true,'forms'=>$rows]);
    }

    // ── Public: submit ────────────────────────────────────────
    if ($res === 'submit' && $method === 'POST') {
        if (!$formsEnabled) json_out(['ok'=>false,'error'=>'module_disabled','module'=>'forms'], 404);
        $slug = $id;
        if (!$slug) json_out(['ok'=>false,'error'=>'Missing form slug'],400);
        $stmt = db()->prepare('SELECT * FROM fb_forms WHERE slug=? AND is_active=1');
        $stmt->execute([$slug]); $form = $stmt->fetch();
        if (!$form) json_out(['ok'=>false,'error'=>'Form not found'],404);

        // rate limit
        if (!check_rate(client_ip())) json_out(['ok'=>false,'error'=>'Too many submissions. Try again later.'],429);

        // submission limit check
        if ($form['submission_limit'] > 0) {
            $cur = db()->prepare('SELECT COUNT(*) FROM fb_submissions WHERE form_id=?');
            $cur->execute([$form['id']]);
            if ((int)$cur->fetchColumn() >= (int)$form['submission_limit'])
                json_out(['ok'=>false,'error'=>'This form is no longer accepting submissions.'],403);
        }

        // password check
        if (!$form['is_public'] && $form['form_password']) {
            $b = body();
            if (($b['_form_password']??'') !== $form['form_password'])
                json_out(['ok'=>false,'error'=>'Incorrect form password'],403);
        }

        $fields = json_decode($form['fields'],true) ?: [];
        $data   = []; $errors = []; $file_map = [];

        // Process each field
        foreach ($fields as $field) {
            $fid  = $field['id'] ?? ''; $type = $field['type'] ?? 'text';
            $req  = !empty($field['required']);
            $label= $field['label'] ?? $fid;

            if ($type === 'file') {
                $uploaded = [];
                if (isset($_FILES[$fid])) {
                    $fs = $_FILES[$fid];
                    $names = is_array($fs['name']) ? $fs['name'] : [$fs['name']];
                    $tmps  = is_array($fs['tmp_name']) ? $fs['tmp_name'] : [$fs['tmp_name']];
                    $errs  = is_array($fs['error']) ? $fs['error'] : [$fs['error']];
                    $sizes = is_array($fs['size']) ? $fs['size'] : [$fs['size']];
                    $allowed = array_map('trim', explode(',', ALLOWED_EXTENSIONS));
                    foreach ($names as $k => $orig) {
                        if ($errs[$k] !== UPLOAD_ERR_OK) continue;
                        $ext = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
                        if (!in_array($ext, $allowed)) { $errors[] = "$label: file type .$ext not allowed"; continue; }
                        if ($sizes[$k] > UPLOAD_MAX_MB*1024*1024) { $errors[] = "$label: file too large (max ".UPLOAD_MAX_MB."MB)"; continue; }
                        $dir = __DIR__ . '/../data/uploads/' . date('Y/m');
                        @mkdir($dir, 0755, true);
                        $fname = preg_replace('/[^A-Za-z0-9\-_\.]/','-',$orig);
                        $dest  = $dir . '/' . uniqid() . '_' . $fname;
                        if (move_uploaded_file($tmps[$k], $dest)) {
                            $rel  = 'data/uploads/' . date('Y/m') . '/' . basename($dest);
                            $url  = (defined('APP_URL')?APP_URL:'') . '/' . $rel;
                            $uploaded[] = ['original_name'=>$orig,'path'=>$dest,'url'=>$url,'size'=>$sizes[$k]];
                        }
                    }
                }
                if ($req && !$uploaded) $errors[] = "$label is required";
                $file_map[$fid] = $uploaded;
                continue;
            }

            if ($type === 'signature') {
                $b    = body(); $val = $b[$fid] ?? ($_POST[$fid] ?? '');
                if ($req && !$val) $errors[] = "$label is required";
                $data[$fid] = $val; continue;
            }

            $b   = body(); $val = $b[$fid] ?? ($_POST[$fid] ?? '');
            if ($type === 'checkbox' && is_string($val)) $val = explode(',', $val);
            if ($req && (is_array($val) ? !count(array_filter($val)) : !strlen(trim((string)$val))))
                $errors[] = "$label is required";
            if ($type === 'email' && $val && !filter_var($val, FILTER_VALIDATE_EMAIL))
                $errors[] = "$label: invalid email address";
            $data[$fid] = $val;
        }

        if ($errors) json_out(['ok'=>false,'errors'=>$errors,'error'=>implode('; ',$errors)],422);

        $ref = gen_ref();
        $at  = date('Y-m-d H:i:s');

        // Generate PDF
        $pdf_path = generate_pdf($form, $fields, $data, $file_map, $ref, $at);

        // Save submission
        db()->prepare('INSERT INTO fb_submissions (form_id,ref,data,files,pdf_path,ip,user_agent) VALUES (?,?,?,?,?,?,?)')
            ->execute([$form['id'],$ref,json_encode($data),json_encode($file_map),$pdf_path,client_ip(),substr($_SERVER['HTTP_USER_AGENT']??'',0,255)]);

        $sub_row = db()->prepare('SELECT * FROM fb_submissions WHERE ref=?');
        $sub_row->execute([$ref]); $sub_row = $sub_row->fetch();

        // Send emails
        try { send_submission_emails($form, $fields, $sub_row); $sent=true; }
        catch (\Throwable $e) { error_log('Email error: '.$e->getMessage()); $sent=false; }
        db()->prepare('UPDATE fb_submissions SET email_sent=? WHERE ref=?')->execute([$sent?1:0,$ref]);

        $success_url = $form['success_url'] ?: null;
        json_out(['ok'=>true,'ref'=>$ref,'success_url'=>$success_url]);
    }

    // ── Booking ──────────────────────────────────────────────
    if ($res === 'booking') {
        // Pass route segments after "booking" so booking.php does not
        // re-parse SCRIPT_NAME when included via this router.
        $GLOBALS['BOOKING_ROUTE_SEGS'] = array_slice($segs, 1);
        require_once __DIR__ . '/booking.php';
        exit;
    }

    // ── Admin: require auth for everything below ──────────────
    api_require_admin();
    $resourceMap = [
        'stats' => 'stats',
        'forms' => 'forms',
        'submissions' => 'submissions',
        'settings' => 'settings',
    ];
    if (isset($resourceMap[$res])) {
        api_require_role_for($resourceMap[$res], $method);
    }

    // ── Stats ─────────────────────────────────────────────────
    if ($res === 'stats' && $method === 'GET') {
        if (!$formsEnabled) json_out(['ok'=>false,'error'=>'module_disabled','module'=>'forms'], 404);
        $total_forms = db()->query('SELECT COUNT(*) FROM fb_forms')->fetchColumn();
        $total_subs  = db()->query('SELECT COUNT(*) FROM fb_submissions')->fetchColumn();
        $today       = db()->query("SELECT COUNT(*) FROM fb_submissions WHERE DATE(created_at)=CURDATE()")->fetchColumn();
        $week        = db()->query("SELECT COUNT(*) FROM fb_submissions WHERE created_at>=DATE_SUB(NOW(),INTERVAL 7 DAY)")->fetchColumn();
        $per_form    = db()->query("SELECT f.title,COUNT(s.id) as n FROM fb_forms f LEFT JOIN fb_submissions s ON s.form_id=f.id GROUP BY f.id ORDER BY n DESC LIMIT 10")->fetchAll();
        $recent_rows = db()->query("SELECT s.id,s.ref,s.data,s.email_sent,s.read_at,s.created_at,f.title FROM fb_submissions s JOIN fb_forms f ON f.id=s.form_id ORDER BY s.id DESC LIMIT 5")->fetchAll();
        $recent = [];
        foreach ($recent_rows as $r) {
            $r['display_name'] = extract_display_name($r['data'] ?? '{}', $r['ref']);
            $r['is_unread'] = ($r['read_at'] === null);
            unset($r['data'],$r['read_at']);
            $recent[] = $r;
        }
        $unread_count = (int)db()->query("SELECT COUNT(*) FROM fb_submissions WHERE read_at IS NULL")->fetchColumn();
        json_out(['ok'=>true,'total_forms'=>(int)$total_forms,'total_submissions'=>(int)$total_subs,'today'=>(int)$today,'this_week'=>(int)$week,'per_form'=>$per_form,'recent'=>$recent,'unread_count'=>$unread_count]);
    }

    // ── Forms CRUD ────────────────────────────────────────────
    if ($res === 'forms') {
        if (!$formsEnabled) json_out(['ok'=>false,'error'=>'module_disabled','module'=>'forms'], 404);
        // List
        if (!$id && $method === 'GET') {
            $rows = db()->query("SELECT id,slug,title,description,is_public,is_active,created_at,(SELECT COUNT(*) FROM fb_submissions WHERE form_id=fb_forms.id) as sub_count FROM fb_forms ORDER BY id DESC")->fetchAll();
            json_out(['ok'=>true,'forms'=>$rows]);
        }
        // Create
        if (!$id && $method === 'POST') {
            $b = body();
            $title  = trim($b['title'] ?? ''); if (!$title) json_out(['ok'=>false,'error'=>'Title required'],400);
            $slug   = unique_slug($b['slug'] ?? $title);
            $fields = json_encode($b['fields'] ?? []);
            db()->prepare('INSERT INTO fb_forms (slug,title,description,fields,is_public,form_password,submit_label,success_url,notify_email,submission_limit) VALUES (?,?,?,?,?,?,?,?,?,?)')
                ->execute([$slug,$title,$b['description']??'',$fields,$b['is_public']??1,$b['form_password']??null,$b['submit_label']??'Submit',$b['success_url']??null,$b['notify_email']??null,isset($b['submission_limit'])&&$b['submission_limit']>0?(int)$b['submission_limit']:null]);
            $fid = db()->lastInsertId();
            json_out(['ok'=>true,'id'=>(int)$fid,'slug'=>$slug]);
        }
        // Duplicate form
        if ($id && $sub === 'duplicate' && $method === 'POST') {
            $s = db()->prepare('SELECT * FROM fb_forms WHERE id=?'); $s->execute([$id]); $row = $s->fetch();
            if (!$row) json_out(['ok'=>false,'error'=>'Form not found'],404);
            $new_slug  = unique_slug($row['slug'].'-copy');
            $new_title = $row['title'].' (Copy)';
            db()->prepare('INSERT INTO fb_forms (slug,title,description,fields,is_public,form_password,submit_label,success_url,notify_email,submission_limit,is_active) VALUES (?,?,?,?,?,?,?,?,?,?,?)')
                ->execute([$new_slug,$new_title,$row['description'],$row['fields'],$row['is_public'],$row['form_password'],$row['submit_label'],$row['success_url'],$row['notify_email'],$row['submission_limit'],$row['is_active']]);
            json_out(['ok'=>true,'id'=>(int)db()->lastInsertId(),'slug'=>$new_slug]);
        }
        // Submissions for a form — CSV export
        if ($id && $sub === 'submissions' && ($segs[3] ?? '') === 'csv' && $method === 'GET') {
            require_once dirname(__DIR__) . '/lib/CsvExporter.php';
            $fstmt = db()->prepare('SELECT title FROM fb_forms WHERE id=?');
            $fstmt->execute([$id]); $frow = $fstmt->fetch();
            if (!$frow) json_out(['ok'=>false,'error'=>'Form not found'], 404);
            CsvExporter::streamCsv((int)$id, $frow['title'], db());
        }
        // Submissions for a form — JSON list with search/filter
        if ($id && $sub === 'submissions' && $method === 'GET') {
            $page   = max(1,(int)($_GET['page']??1));
            $limit  = min(100,(int)($_GET['limit']??25));
            $off    = ($page-1)*$limit;
            $sortDir= ($_GET['sort']??'desc')==='asc' ? 'ASC' : 'DESC';
            $emailF = isset($_GET['email_sent']) && $_GET['email_sent']!=='' ? (int)$_GET['email_sent'] : null;
            $search = trim($_GET['search']??'');

            $where = ['s.form_id=?']; $vals = [$id];
            if ($emailF !== null) { $where[] = 's.email_sent=?'; $vals[] = $emailF; }
            if ($search) {
                $where[] = "(s.ref LIKE ? OR s.data LIKE ? OR s.ip LIKE ?)";
                $like = '%'.$search.'%'; $vals[] = $like; $vals[] = $like; $vals[] = $like;
            }
            $wq = implode(' AND ', $where);

            $totalStmt = db()->prepare("SELECT COUNT(*) FROM fb_submissions s WHERE $wq");
            $totalStmt->execute($vals); $total = $totalStmt->fetchColumn();

            $rowStmt = db()->prepare("SELECT s.id,s.ref,s.data,s.pdf_path,s.ip,s.email_sent,s.created_at,f.title as form_title
                FROM fb_submissions s JOIN fb_forms f ON f.id=s.form_id
                WHERE $wq ORDER BY s.id $sortDir LIMIT $limit OFFSET $off");
            $rowStmt->execute($vals); $raw = $rowStmt->fetchAll();
            $rows = [];
            foreach ($raw as $r) {
                $r['display_name'] = extract_display_name($r['data'] ?? '{}', $r['ref']);
                unset($r['data']);
                $rows[] = $r;
            }
            json_out(['ok'=>true,'total'=>(int)$total,'page'=>$page,'limit'=>$limit,'rows'=>$rows]);
        }
        // Single form
        if ($id && !$sub && $method === 'GET') {
            $s=$pdo=db()->prepare('SELECT * FROM fb_forms WHERE id=?');$s->execute([$id]);$row=$s->fetch();
            if (!$row) json_out(['ok'=>false,'error'=>'Not found'],404);
            $row['fields']=json_decode($row['fields'],true)?:[];
            json_out(['ok'=>true,'form'=>$row]);
        }
        // Update
        if ($id && $method === 'PUT') {
            $b=body(); $sets=[]; $vals=[];
            foreach(['title','description','fields','is_public','form_password','submit_label','success_url','notify_email','is_active','submission_limit'] as $k) {
                if (array_key_exists($k,$b)) { $sets[]="$k=?"; $vals[]=($k==='fields'&&is_array($b[$k]))?json_encode($b[$k]):$b[$k]; }
            }
            if (!$sets) json_out(['ok'=>false,'error'=>'Nothing to update'],400);
            $vals[]=$id;
            db()->prepare('UPDATE fb_forms SET '.implode(',',$sets).' WHERE id=?')->execute($vals);
            json_out(['ok'=>true]);
        }
        // Delete
        if ($id && $method === 'DELETE') {
            db()->prepare('DELETE FROM fb_forms WHERE id=?')->execute([$id]);
            json_out(['ok'=>true]);
        }
    }

    // ── Submissions — cross-form list (all forms) ─────────────
    if ($res === 'submissions' && $id === 'all' && $method === 'GET') {
        if (!$formsEnabled) json_out(['ok'=>false,'error'=>'module_disabled','module'=>'forms'], 404);
        $page   = max(1,(int)($_GET['page']??1));
        $limit  = min(100,(int)($_GET['limit']??30));
        $off    = ($page-1)*$limit;
        $sortDir= ($_GET['sort']??'desc')==='asc' ? 'ASC' : 'DESC';
        $emailF = isset($_GET['email_sent']) && $_GET['email_sent']!=='' ? (int)$_GET['email_sent'] : null;
        $search = trim($_GET['search']??'');

        $where = ['1=1']; $vals = [];
        if ($emailF !== null) { $where[] = 's.email_sent=?'; $vals[] = $emailF; }
        if ($search) {
            $where[] = "(s.ref LIKE ? OR s.data LIKE ? OR s.ip LIKE ? OR f.title LIKE ?)";
            $like = '%'.$search.'%'; $vals[] = $like; $vals[] = $like; $vals[] = $like; $vals[] = $like;
        }
        $wq = implode(' AND ', $where);

        $totalStmt = db()->prepare("SELECT COUNT(*) FROM fb_submissions s JOIN fb_forms f ON f.id=s.form_id WHERE $wq");
        $totalStmt->execute($vals); $total = $totalStmt->fetchColumn();

        $rowStmt = db()->prepare("SELECT s.id,s.ref,s.data,s.pdf_path,s.ip,s.email_sent,s.created_at,f.title as form_title,f.id as form_id
            FROM fb_submissions s JOIN fb_forms f ON f.id=s.form_id
            WHERE $wq ORDER BY s.id $sortDir LIMIT $limit OFFSET $off");
        $rowStmt->execute($vals); $raw = $rowStmt->fetchAll();
        $rows = [];
        foreach ($raw as $r) {
            $r['display_name'] = extract_display_name($r['data'] ?? '{}', $r['ref']);
            unset($r['data']);
            $rows[] = $r;
        }
        json_out(['ok'=>true,'total'=>(int)$total,'page'=>$page,'limit'=>$limit,'rows'=>$rows]);
    }

    // ── Submissions ───────────────────────────────────────────
    if ($res === 'submissions') {
        if ($id && $sub === 'pdf' && $method === 'GET') {
            $s=db()->prepare('SELECT pdf_path,ref FROM fb_submissions WHERE id=?');$s->execute([$id]);$row=$s->fetch();
            if (!$row||!$row['pdf_path']||!file_exists($row['pdf_path'])) json_out(['ok'=>false,'error'=>'PDF not found'],404);
            $name='Submission-'.$row['ref'].'.pdf';
            header('Content-Type: application/pdf');
            header('Content-Disposition: attachment; filename="'.$name.'"');
            header('Content-Length: '.filesize($row['pdf_path']));
            readfile($row['pdf_path']); exit;
        }
        // Mark all submissions read
        if ($id === 'mark-all-read' && $method === 'POST') {
            db()->exec("UPDATE fb_submissions SET read_at=NOW() WHERE read_at IS NULL");
            json_out(['ok'=>true]);
        }
        if ($id && $method === 'GET') {
            $s=db()->prepare('SELECT s.*,f.title as form_title,f.fields FROM fb_submissions s JOIN fb_forms f ON f.id=s.form_id WHERE s.id=?');
            $s->execute([$id]);$row=$s->fetch();
            if (!$row) json_out(['ok'=>false,'error'=>'Not found'],404);
            // mark as read
            if ($row['read_at'] === null) db()->prepare('UPDATE fb_submissions SET read_at=NOW() WHERE id=?')->execute([$id]);
            $row['data']=json_decode($row['data'],true)?:[];
            $row['files']=json_decode($row['files'],true)?:[];
            $row['fields']=json_decode($row['fields'],true)?:[];
            json_out(['ok'=>true,'submission'=>$row]);
        }
        if ($id && $method === 'DELETE') {
            $s=db()->prepare('SELECT pdf_path,files FROM fb_submissions WHERE id=?');$s->execute([$id]);$row=$s->fetch();
            if ($row) {
                if ($row['pdf_path']&&file_exists($row['pdf_path'])) @unlink($row['pdf_path']);
                foreach(json_decode($row['files'],true)?:[] as $group)
                    foreach((array)$group as $fi) if(!empty($fi['path'])&&file_exists($fi['path'])) @unlink($fi['path']);
            }
            db()->prepare('DELETE FROM fb_submissions WHERE id=?')->execute([$id]);
            json_out(['ok'=>true]);
        }
    }

    // ── Settings ─────────────────────────────────────────────
    if ($res === 'settings') {
        $cfg_file = dirname(__DIR__) . '/config.php';
        if ($method === 'GET') {
            json_out(['ok'=>true,'settings'=>[
                'app_name'    => defined('APP_NAME')    ? APP_NAME    : '',
                'app_url'     => defined('APP_URL')     ? APP_URL     : '',
                'from_email'  => defined('FROM_EMAIL')  ? FROM_EMAIL  : '',
                'from_name'   => defined('FROM_NAME')   ? FROM_NAME   : '',
                'admin_email' => defined('ADMIN_EMAIL') ? ADMIN_EMAIL : '',
                'smtp_host'   => defined('SMTP_HOST')   ? SMTP_HOST   : '',
                'smtp_port'   => defined('SMTP_PORT')   ? SMTP_PORT   : 587,
                'smtp_secure' => defined('SMTP_SECURE') ? SMTP_SECURE : '',
                'smtp_user'   => defined('SMTP_USER')   ? SMTP_USER   : '',
                'smtp_pass'   => defined('SMTP_PASS')   ? SMTP_PASS   : '',
                'rate_limit_max'    => defined('RATE_LIMIT_MAX')    ? RATE_LIMIT_MAX    : 100,
                'rate_limit_window' => defined('RATE_LIMIT_WINDOW') ? RATE_LIMIT_WINDOW : 3600,
                'php_version' => PHP_VERSION,
                'db_version'  => db()->query('SELECT VERSION()')->fetchColumn(),
            ]]);
        }
        if ($method === 'POST') {
            $b = body();
            $allowed = ['app_name','app_url','from_email','from_name','admin_email',
                        'smtp_host','smtp_port','smtp_secure','smtp_user','smtp_pass',
                        'admin_password','rate_limit_max','rate_limit_window'];
            // Read current config
            $src = file_get_contents($cfg_file);
            if ($src === false) json_out(['ok'=>false,'error'=>'Cannot read config.php'],500);
            $map = [
                'app_name'           => 'APP_NAME',
                'app_url'            => 'APP_URL',
                'from_email'         => 'FROM_EMAIL',
                'from_name'          => 'FROM_NAME',
                'admin_email'        => 'ADMIN_EMAIL',
                'smtp_host'          => 'SMTP_HOST',
                'smtp_port'          => 'SMTP_PORT',
                'smtp_secure'        => 'SMTP_SECURE',
                'smtp_user'          => 'SMTP_USER',
                'smtp_pass'          => 'SMTP_PASS',
                'admin_password'     => 'ADMIN_PASSWORD',
                'rate_limit_max'     => 'RATE_LIMIT_MAX',
                'rate_limit_window'  => 'RATE_LIMIT_WINDOW',
            ];
            $numeric = ['SMTP_PORT','RATE_LIMIT_MAX','RATE_LIMIT_WINDOW'];
            foreach ($b as $key => $val) {
                if (!isset($map[$key])) continue;
                $const = $map[$key];
                $safe  = addslashes((string)$val);
                // Numeric constants keep no quotes; strings get single quotes
                $replacement = in_array($const, $numeric)
                    ? "define('{$const}', " . (int)$val . ")"
                    : "define('{$const}', '{$safe}')";
                $src = preg_replace(
                    "/define\('{$const}',\s*[^;]+\)/",
                    $replacement,
                    $src
                );
            }
            if (file_put_contents($cfg_file, $src) === false)
                json_out(['ok'=>false,'error'=>'Cannot write config.php — check file permissions'],500);
            json_out(['ok'=>true]);
        }
    }

    json_out(['ok'=>false,'error'=>'Not found'],404);
} catch (\Throwable $e) {
    error_log('API error: '.$e->getMessage());
    json_out(['ok'=>false,'error'=>'Server error: '.$e->getMessage()],500);
}