<?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';
require_once dirname(__DIR__) . '/lib/ModuleToggle.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_booking_db(); // ensures sys_feature_toggles + all booking tables exist on every request

    // ── 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();

            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 === 'logout') { session_start_safe(); session_destroy(); json_out(['ok'=>true]); }
        if ($id === 'me') {
            $authed = admin_logged_in();
            // Issue a CSRF token tied to this session
            if ($authed) {
                if (empty($_SESSION['csrf_token'])) {
                    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
                }
            }
            json_out(['ok'=>true,'authed'=>$authed,'csrf'=>$_SESSION['csrf_token'] ?? null,'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') {
        ModuleToggle::requireEnabled('module_forms');
        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') {
        ModuleToggle::requireEnabled('module_forms');
        $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 — atomic: re-verified inside transaction at INSERT time
        if ($form['submission_limit'] > 0) {
            $cur = db()->prepare('SELECT COUNT(*) FROM fb_submissions WHERE form_id=? FOR UPDATE');
            db()->beginTransaction();
            $cur->execute([$form['id']]);
            if ((int)$cur->fetchColumn() >= (int)$form['submission_limit']) {
                db()->rollBack();
                json_out(['ok'=>false,'error'=>'This form is no longer accepting submissions.'],403);
            }
            // transaction stays open; INSERT below will commit it
        }

        // 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);
                        $ext_safe = preg_replace('/[^a-z0-9]/', '', $ext);
                        $dest  = $dir . '/' . bin2hex(random_bytes(16)) . '.' . $ext_safe;
                        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 (commit the open transaction if submission_limit was checked)
        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)]);
        if (db()->inTransaction()) db()->commit();

        $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') {
        // Stripe webhook must bypass the module gate (Stripe won't retry
        // if we return 403, and we need to activate memberships even if
        // the booking UI is toggled off).
        $bSegs = array_values(array_filter(array_slice($segs, 1)));
        $isStripeWebhook = ($bSegs[0] ?? '') === 'stripe' && ($bSegs[1] ?? '') === 'webhook';
        if (!$isStripeWebhook) {
            ModuleToggle::requireEnabled('module_booking');
        }
        $GLOBALS['BOOKING_ROUTE_SEGS'] = $bSegs;
        require_once __DIR__ . '/booking.php';
        exit;
    }

    // ── Admin: require auth for everything below ──────────────
    api_require_admin();

    // ── Admin: Module toggles ─────────────────────────────────
    if ($res === 'admin' && $id === 'modules') {
        require_once __DIR__ . '/admin/modules.php';
        exit;
    }

    // ── Stats ─────────────────────────────────────────────────
    if ($res === 'stats' && $method === 'GET') {
        $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') {
        ModuleToggle::requireEnabled('module_forms');
        // 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') {
        ModuleToggle::requireEnabled('module_forms');
        $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') {
        ModuleToggle::requireEnabled('module_forms');
        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();

            // ── Admin password: update fb_users in DB, never write to config ──
            $newPw = trim($b['admin_password'] ?? '');
            if ($newPw !== '') {
                if (strlen($newPw) < 8)
                    json_out(['ok'=>false,'error'=>'Password must be at least 8 characters'],422);
                $uid = $_SESSION['admin_user_id'] ?? null;
                if (!$uid) json_out(['ok'=>false,'error'=>'Session expired'],401);
                db()->prepare('UPDATE fb_users SET password_hash=?, updated_at=NOW() WHERE id=?')
                    ->execute([password_hash($newPw, PASSWORD_DEFAULT), (int)$uid]);
            }

            // ── All other settings: patch config.php safely ───────────────────
            $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',
                'rate_limit_max'     => 'RATE_LIMIT_MAX',
                'rate_limit_window'  => 'RATE_LIMIT_WINDOW',
            ];
            $numeric = ['SMTP_PORT','RATE_LIMIT_MAX','RATE_LIMIT_WINDOW'];
            $src = file_get_contents($cfg_file);
            if ($src === false) json_out(['ok'=>false,'error'=>'Cannot read config.php'],500);
            foreach ($b as $key => $val) {
                if (!isset($map[$key])) continue;
                if ($key === 'smtp_pass' && $val === '••••••••') continue;
                $const = $map[$key];
                // Use var_export for safe PHP string encoding (handles quotes, backslashes, etc.)
                $encoded = in_array($const, $numeric)
                    ? (int)$val
                    : var_export((string)$val, true);
                $src = preg_replace(
                    "/define\(\s*'" . preg_quote($const, '/') . "'\s*,\s*[^)]+\)/",
                    "define('" . $const . "', " . $encoded . ")",
                    $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);
}